1
This commit is contained in:
@@ -316,152 +316,168 @@ class ResumeManager {
|
||||
|
||||
const resume_info = db.getModel('resume_info');
|
||||
|
||||
// 解析 JSON 字段
|
||||
let skillsArray = [];
|
||||
let workExpArray = [];
|
||||
let projectExpArray = [];
|
||||
|
||||
try {
|
||||
if (resumeInfo.skills) {
|
||||
skillsArray = typeof resumeInfo.skills === 'string' ? JSON.parse(resumeInfo.skills) : resumeInfo.skills;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[简历管理] 解析技能字段失败:`, e);
|
||||
}
|
||||
|
||||
try {
|
||||
if (resumeInfo.workExperience) {
|
||||
workExpArray = typeof resumeInfo.workExperience === 'string' ? JSON.parse(resumeInfo.workExperience) : resumeInfo.workExperience;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[简历管理] 解析工作经历字段失败:`, e);
|
||||
}
|
||||
|
||||
try {
|
||||
if (resumeInfo.projectExperience) {
|
||||
projectExpArray = typeof resumeInfo.projectExperience === 'string' ? JSON.parse(resumeInfo.projectExperience) : resumeInfo.projectExperience;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[简历管理] 解析项目经历字段失败:`, e);
|
||||
}
|
||||
|
||||
// 构建工作经历文本
|
||||
const workExpText = workExpArray.map((work, index) => {
|
||||
return `${index + 1}. ${work.company || ''} - ${work.position || ''} (${work.startDate || ''} ~ ${work.endDate || '至今'})
|
||||
工作内容:${work.content || ''}
|
||||
行业:${work.industry || ''}`;
|
||||
}).join('\n\n');
|
||||
|
||||
// 构建项目经历文本
|
||||
const projectExpText = projectExpArray.map((project, index) => {
|
||||
return `${index + 1}. ${project.name || ''} - ${project.role || ''} (${project.startDate || ''} ~ ${project.endDate || '至今'})
|
||||
项目描述:${project.description || ''}
|
||||
项目成果:${project.performance || ''}`;
|
||||
}).join('\n\n');
|
||||
|
||||
// 构建分析提示词
|
||||
const prompt = `请分析以下简历,提供专业的评估:
|
||||
|
||||
姓名:${resumeInfo.fullName}
|
||||
工作年限:${resumeInfo.workYears}
|
||||
当前职位:${resumeInfo.currentPosition}
|
||||
期望职位:${resumeInfo.expectedPosition}
|
||||
期望薪资:${resumeInfo.expectedSalary}
|
||||
学历:${resumeInfo.education}
|
||||
技能:${resumeInfo.skills}
|
||||
【基本信息】
|
||||
姓名:${resumeInfo.fullName || ''}
|
||||
性别:${resumeInfo.gender || ''}
|
||||
年龄:${resumeInfo.age || ''}
|
||||
所在地:${resumeInfo.location || ''}
|
||||
工作年限:${resumeInfo.workYears || ''}
|
||||
|
||||
个人优势:
|
||||
${resumeInfo.skillDescription}
|
||||
【教育背景】
|
||||
学历:${resumeInfo.education || ''}
|
||||
专业:${resumeInfo.major || ''}
|
||||
毕业院校:${resumeInfo.school || ''}
|
||||
毕业年份:${resumeInfo.graduationYear || ''}
|
||||
|
||||
请从以下几个方面进行分析:
|
||||
1. 核心技能标签(提取5-10个关键技能)
|
||||
2. 优势分析(100字以内)
|
||||
3. 劣势分析(100字以内)
|
||||
4. 职业建议(150字以内)
|
||||
5. 竞争力评分(0-100分)`;
|
||||
【当前状态】
|
||||
当前职位:${resumeInfo.currentPosition || ''}
|
||||
当前公司:${resumeInfo.currentCompany || ''}
|
||||
当前薪资:${resumeInfo.currentSalary || ''}
|
||||
|
||||
try {
|
||||
// 调用AI服务进行分析
|
||||
const aiAnalysis = await aiService.analyzeResume(prompt);
|
||||
【求职期望】
|
||||
期望职位:${resumeInfo.expectedPosition || ''}
|
||||
期望薪资:${resumeInfo.expectedSalary || ''}
|
||||
期望地点:${resumeInfo.expectedLocation || ''}
|
||||
期望行业:${resumeInfo.expectedIndustry || ''}
|
||||
|
||||
// 解析AI返回的结果
|
||||
const analysis = this.parse_ai_analysis(aiAnalysis, resumeInfo);
|
||||
【技能标签】
|
||||
${skillsArray.length > 0 ? skillsArray.join('、') : '无'}
|
||||
|
||||
// 确保所有字段都有值
|
||||
const updateData = {
|
||||
aiSkillTags: JSON.stringify(analysis.skillTags || []),
|
||||
aiStrengths: analysis.strengths || '',
|
||||
aiWeaknesses: analysis.weaknesses || '',
|
||||
aiCareerSuggestion: analysis.careerSuggestion || '',
|
||||
aiCompetitiveness: parseInt(analysis.competitiveness || 70, 10)
|
||||
};
|
||||
【个人优势描述】
|
||||
${resumeInfo.skillDescription || ''}
|
||||
|
||||
// 确保竞争力评分在 0-100 范围内
|
||||
if (updateData.aiCompetitiveness < 0) updateData.aiCompetitiveness = 0;
|
||||
if (updateData.aiCompetitiveness > 100) updateData.aiCompetitiveness = 100;
|
||||
【工作经历】
|
||||
${workExpText || '无'}
|
||||
|
||||
// 更新简历的AI分析字段
|
||||
await resume_info.update(updateData, { where: { resumeId: resumeId } });
|
||||
【项目经历】
|
||||
${projectExpText || '无'}
|
||||
|
||||
console.log(`[简历管理] AI分析完成 - 竞争力评分: ${updateData.aiCompetitiveness}, 技能标签: ${updateData.aiSkillTags}`);
|
||||
请从以下几个方面进行专业分析,并返回 JSON 格式结果:
|
||||
1. skillTags: 核心技能标签数组(提取5-10个最关键的技术技能,如:Vue、React、Node.js等)
|
||||
2. strengths: 优势分析(100字以内,基于工作经历、项目经历、技能水平等综合评估)
|
||||
3. weaknesses: 劣势分析(100字以内,指出需要改进的地方或不足)
|
||||
4. careerSuggestion: 职业建议(150字以内,基于期望职位和当前能力给出职业发展建议)
|
||||
5. competitiveness: 竞争力评分(0-100的整数,综合考虑工作年限、技能深度、项目经验、学历等因素)
|
||||
|
||||
return analysis;
|
||||
} catch (error) {
|
||||
console.error(`[简历管理] AI分析失败:`, error, {
|
||||
resumeId: resumeId,
|
||||
fullName: resumeInfo.fullName
|
||||
});
|
||||
要求:
|
||||
- 竞争力评分要客观公正,基于实际能力评估
|
||||
- 优势分析要突出核心亮点和技术能力
|
||||
- 劣势分析要指出真实存在的问题
|
||||
- 职业建议要具有针对性和可操作性`;
|
||||
|
||||
// 如果AI分析失败,使用基于规则的默认分析,并保存到数据库
|
||||
const defaultAnalysis = this.get_default_analysis(resumeInfo);
|
||||
// 调用AI服务进行分析
|
||||
const aiAnalysis = await aiService.analyzeResume(prompt);
|
||||
|
||||
// 保存默认分析结果到数据库
|
||||
const updateData = {
|
||||
aiSkillTags: JSON.stringify(defaultAnalysis.skillTags || []),
|
||||
aiStrengths: defaultAnalysis.strengths || '',
|
||||
aiWeaknesses: defaultAnalysis.weaknesses || '',
|
||||
aiCareerSuggestion: defaultAnalysis.careerSuggestion || '',
|
||||
aiCompetitiveness: parseInt(defaultAnalysis.competitiveness || 70, 10)
|
||||
};
|
||||
|
||||
// 确保竞争力评分在 0-100 范围内
|
||||
if (updateData.aiCompetitiveness < 0) updateData.aiCompetitiveness = 0;
|
||||
if (updateData.aiCompetitiveness > 100) updateData.aiCompetitiveness = 100;
|
||||
|
||||
await resume_info.update(updateData, { where: { resumeId: resumeId } });
|
||||
|
||||
console.log(`[简历管理] 使用默认分析结果 - 竞争力评分: ${updateData.aiCompetitiveness}`);
|
||||
|
||||
return defaultAnalysis;
|
||||
// 解析AI返回的结果,如果AI没返回有效数据,直接抛出错误
|
||||
const analysis = this.parse_ai_analysis(aiAnalysis, resumeInfo);
|
||||
|
||||
if (!analysis) {
|
||||
throw new Error('AI分析结果为空,无法处理');
|
||||
}
|
||||
|
||||
// 确保所有字段都有值
|
||||
const updateData = {
|
||||
aiSkillTags: JSON.stringify(analysis.skillTags || []),
|
||||
aiStrengths: analysis.strengths || '',
|
||||
aiWeaknesses: analysis.weaknesses || '',
|
||||
aiCareerSuggestion: analysis.careerSuggestion || '',
|
||||
aiCompetitiveness: parseInt(analysis.competitiveness || 0, 10)
|
||||
};
|
||||
|
||||
// 确保竞争力评分在 0-100 范围内
|
||||
if (updateData.aiCompetitiveness < 0) updateData.aiCompetitiveness = 0;
|
||||
if (updateData.aiCompetitiveness > 100) updateData.aiCompetitiveness = 100;
|
||||
|
||||
// 更新简历的AI分析字段
|
||||
await resume_info.update(updateData, { where: { resumeId: resumeId } });
|
||||
|
||||
console.log(`[简历管理] AI分析完成 - 竞争力评分: ${updateData.aiCompetitiveness}, 技能标签: ${updateData.aiSkillTags}`);
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析AI分析结果
|
||||
* @param {object} aiResponse - AI响应对象
|
||||
* @param {object} resumeInfo - 简历信息
|
||||
* @returns {object} 解析后的分析结果
|
||||
* @returns {object|null} 解析后的分析结果,如果AI没返回有效数据则返回null
|
||||
*/
|
||||
parse_ai_analysis(aiResponse, resumeInfo) {
|
||||
try {
|
||||
// aiService.analyzeResume 返回格式: { analysis: {...} } 或 { analysis: { content: "...", parseError: true } }
|
||||
const analysis = aiResponse.analysis;
|
||||
// aiService.analyzeResume 返回格式: { analysis: {...} } 或 { analysis: { content: "...", parseError: true } }
|
||||
const analysis = aiResponse?.analysis;
|
||||
|
||||
// 如果解析失败,analysis 会有 parseError 标记
|
||||
if (analysis && analysis.parseError) {
|
||||
console.warn(`[简历管理] AI分析结果解析失败,使用默认分析`);
|
||||
return this.get_default_analysis(resumeInfo);
|
||||
}
|
||||
|
||||
// 如果解析成功,analysis 直接是解析后的对象
|
||||
if (analysis && typeof analysis === 'object' && !analysis.parseError) {
|
||||
return {
|
||||
skillTags: analysis.skillTags || analysis.技能标签 || [],
|
||||
strengths: analysis.strengths || analysis.优势 || analysis.优势分析 || '',
|
||||
weaknesses: analysis.weaknesses || analysis.劣势 || analysis.劣势分析 || '',
|
||||
careerSuggestion: analysis.careerSuggestion || analysis.职业建议 || '',
|
||||
competitiveness: parseInt(analysis.competitiveness || analysis.竞争力评分 || 70, 10)
|
||||
};
|
||||
}
|
||||
|
||||
// 如果 analysis 是字符串,尝试解析
|
||||
const content = analysis?.content || analysis || '';
|
||||
if (typeof content === 'string' && content.includes('{') && content.includes('}')) {
|
||||
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
||||
if (jsonMatch) {
|
||||
const parsed = JSON.parse(jsonMatch[0]);
|
||||
return {
|
||||
skillTags: parsed.skillTags || parsed.技能标签 || [],
|
||||
strengths: parsed.strengths || parsed.优势 || parsed.优势分析 || '',
|
||||
weaknesses: parsed.weaknesses || parsed.劣势 || parsed.劣势分析 || '',
|
||||
careerSuggestion: parsed.careerSuggestion || parsed.职业建议 || '',
|
||||
competitiveness: parseInt(parsed.competitiveness || parsed.竞争力评分 || 70, 10)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 如果无法解析JSON,尝试从文本中提取信息
|
||||
if (typeof content === 'string') {
|
||||
const skillTagsMatch = content.match(/技能标签[::](.*?)(?:\n|$)/);
|
||||
const strengthsMatch = content.match(/优势[分析]*[::](.*?)(?:\n|劣势)/s);
|
||||
const weaknessesMatch = content.match(/劣势[分析]*[::](.*?)(?:\n|职业)/s);
|
||||
const suggestionMatch = content.match(/职业建议[::](.*?)(?:\n|竞争力)/s);
|
||||
const scoreMatch = content.match(/竞争力评分[::](\d+)/);
|
||||
|
||||
return {
|
||||
skillTags: skillTagsMatch ? skillTagsMatch[1].split(/[,,、]/).map(s => s.trim()) : [],
|
||||
strengths: strengthsMatch ? strengthsMatch[1].trim() : '',
|
||||
weaknesses: weaknessesMatch ? weaknessesMatch[1].trim() : '',
|
||||
careerSuggestion: suggestionMatch ? suggestionMatch[1].trim() : '',
|
||||
competitiveness: scoreMatch ? parseInt(scoreMatch[1], 10) : 70
|
||||
};
|
||||
}
|
||||
|
||||
// 如果所有解析都失败,使用默认分析
|
||||
console.warn(`[简历管理] 无法解析AI分析结果,使用默认分析`);
|
||||
return this.get_default_analysis(resumeInfo);
|
||||
} catch (error) {
|
||||
console.error(`[简历管理] 解析AI分析结果失败:`, error);
|
||||
// 解析失败时使用默认分析
|
||||
return this.get_default_analysis(resumeInfo);
|
||||
// 如果解析失败,analysis 会有 parseError 标记,直接返回 null
|
||||
if (!analysis || analysis.parseError) {
|
||||
console.warn(`[简历管理] AI分析结果解析失败或为空`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 如果解析成功,analysis 直接是解析后的对象
|
||||
if (typeof analysis === 'object' && !analysis.parseError) {
|
||||
// 检查是否有必要的字段
|
||||
if (analysis.competitiveness === undefined && analysis.竞争力评分 === undefined) {
|
||||
console.warn(`[简历管理] AI分析结果缺少竞争力评分字段`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
skillTags: analysis.skillTags || analysis.技能标签 || [],
|
||||
strengths: analysis.strengths || analysis.优势 || analysis.优势分析 || '',
|
||||
weaknesses: analysis.weaknesses || analysis.劣势 || analysis.劣势分析 || '',
|
||||
careerSuggestion: analysis.careerSuggestion || analysis.职业建议 || '',
|
||||
competitiveness: parseInt(analysis.competitiveness || analysis.竞争力评分 || 0, 10)
|
||||
};
|
||||
}
|
||||
|
||||
// 如果格式不对,返回 null
|
||||
console.warn(`[简历管理] AI分析结果格式不正确`);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user