1
This commit is contained in:
@@ -104,10 +104,8 @@ module.exports = {
|
||||
});
|
||||
|
||||
return ctx.success({
|
||||
count: result.count,
|
||||
total: result.count,
|
||||
rows: result.rows,
|
||||
list: result.rows
|
||||
count: result.count
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -150,8 +150,8 @@ module.exports = {
|
||||
});
|
||||
|
||||
return ctx.success({
|
||||
total: result.count,
|
||||
list: list
|
||||
rows: list,
|
||||
count: result.count
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -686,6 +686,86 @@ module.exports = {
|
||||
end_date: end_date
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /admin_api/pla_account/parse-resume:
|
||||
* post:
|
||||
* summary: 解析账号在线简历
|
||||
* description: 获取指定账号的在线简历并进行AI分析,返回简历详情
|
||||
* tags: [后台-账号管理]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - id
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: 账号ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 解析成功,返回简历ID
|
||||
*/
|
||||
'POST /pla_account/parse-resume': async (ctx) => {
|
||||
const { id } = ctx.getBody();
|
||||
const models = await Framework.getModels();
|
||||
const { pla_account } = models;
|
||||
const mqttClient = require('../middleware/mqtt/mqttClient');
|
||||
const resumeManager = require('../middleware/job/resumeManager');
|
||||
|
||||
if (!id) {
|
||||
return ctx.fail('账号ID不能为空');
|
||||
}
|
||||
|
||||
// 获取账号信息
|
||||
const account = await pla_account.findOne({ where: { id } });
|
||||
|
||||
if (!account) {
|
||||
return ctx.fail('账号不存在');
|
||||
}
|
||||
|
||||
const { sn_code, platform_type } = account;
|
||||
|
||||
if (!sn_code) {
|
||||
return ctx.fail('该账号未绑定设备');
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用简历管理器获取并保存简历
|
||||
const resumeData = await resumeManager.get_online_resume(sn_code, mqttClient, {
|
||||
platform: platform_type || 'boss'
|
||||
});
|
||||
|
||||
// 从返回数据中获取 resumeId
|
||||
// resumeManager 已经保存了简历到数据库,我们需要查询获取 resumeId
|
||||
const resume_info = models.resume_info;
|
||||
const savedResume = await resume_info.findOne({
|
||||
where: {
|
||||
sn_code,
|
||||
platform: platform_type || 'boss',
|
||||
isActive: true
|
||||
},
|
||||
order: [['syncTime', 'DESC']]
|
||||
});
|
||||
|
||||
if (!savedResume) {
|
||||
return ctx.fail('简历解析失败:未找到保存的简历记录');
|
||||
}
|
||||
|
||||
return ctx.success({
|
||||
message: '简历解析成功',
|
||||
resumeId: savedResume.resumeId,
|
||||
data: resumeData
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[解析简历] 失败:', error);
|
||||
return ctx.fail('解析简历失败: ' + (error.message || '未知错误'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -69,8 +69,8 @@ const result = await resume_info.findAndCountAll({
|
||||
});
|
||||
|
||||
return ctx.success({
|
||||
total: result.count,
|
||||
list: result.rows
|
||||
rows: result.rows,
|
||||
count: result.count
|
||||
});
|
||||
|
||||
},
|
||||
@@ -153,24 +153,45 @@ return ctx.success({
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
*/
|
||||
'GET /resume/detail': async (ctx) => {
|
||||
'POST /resume/detail': async (ctx) => {
|
||||
const models = Framework.getModels();
|
||||
const { resume_info } = models;
|
||||
const { resumeId } = ctx.query;
|
||||
const { resumeId } = ctx.getBody();
|
||||
|
||||
if (!resumeId) {
|
||||
return ctx.fail('简历ID不能为空');
|
||||
}
|
||||
|
||||
|
||||
const resume = await resume_info.findOne({ where: { resumeId } });
|
||||
const resume = await resume_info.findOne({ where: { resumeId } });
|
||||
|
||||
if (!resume) {
|
||||
return ctx.fail('简历不存在');
|
||||
}
|
||||
if (!resume) {
|
||||
return ctx.fail('简历不存在');
|
||||
}
|
||||
|
||||
return ctx.success(resume);
|
||||
// 解析 JSON 字段
|
||||
const resumeDetail = resume.toJSON();
|
||||
const jsonFields = ['skills', 'certifications', 'projectExperience', 'workExperience', 'aiSkillTags'];
|
||||
|
||||
jsonFields.forEach(field => {
|
||||
if (resumeDetail[field]) {
|
||||
try {
|
||||
resumeDetail[field] = JSON.parse(resumeDetail[field]);
|
||||
} catch (e) {
|
||||
console.error(`解析字段 ${field} 失败:`, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 解析原始数据(如果存在)
|
||||
if (resumeDetail.originalData) {
|
||||
try {
|
||||
resumeDetail.originalData = JSON.parse(resumeDetail.originalData);
|
||||
} catch (e) {
|
||||
console.error('解析原始数据失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.success(resumeDetail);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -269,6 +290,74 @@ return ctx.success({ message: '简历删除成功' });
|
||||
});
|
||||
|
||||
return ctx.success(resumeDetail);
|
||||
},
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /admin_api/resume/analyze-with-ai:
|
||||
* post:
|
||||
* summary: AI 分析简历
|
||||
* description: 使用 AI 分析简历并更新 AI 字段
|
||||
* tags: [后台-简历管理]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - resumeId
|
||||
* properties:
|
||||
* resumeId:
|
||||
* type: string
|
||||
* description: 简历ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 分析成功
|
||||
*/
|
||||
'POST /resume/analyze-with-ai': async (ctx) => {
|
||||
const models = Framework.getModels();
|
||||
const { resume_info } = models;
|
||||
const { resumeId } = ctx.getBody();
|
||||
|
||||
if (!resumeId) {
|
||||
return ctx.fail('简历ID不能为空');
|
||||
}
|
||||
|
||||
const resume = await resume_info.findOne({ where: { resumeId } });
|
||||
|
||||
if (!resume) {
|
||||
return ctx.fail('简历不存在');
|
||||
}
|
||||
|
||||
try {
|
||||
const resumeManager = require('../middleware/job/resumeManager');
|
||||
const resumeData = resume.toJSON();
|
||||
|
||||
// 调用 AI 分析
|
||||
await resumeManager.analyze_resume_with_ai(resumeId, resumeData);
|
||||
|
||||
// 重新获取更新后的数据
|
||||
const updatedResume = await resume_info.findOne({ where: { resumeId } });
|
||||
const resumeDetail = updatedResume.toJSON();
|
||||
|
||||
// 解析 JSON 字段
|
||||
const jsonFields = ['skills', 'certifications', 'projectExperience', 'workExperience', 'aiSkillTags'];
|
||||
jsonFields.forEach(field => {
|
||||
if (resumeDetail[field]) {
|
||||
try {
|
||||
resumeDetail[field] = JSON.parse(resumeDetail[field]);
|
||||
} catch (e) {
|
||||
console.error(`解析字段 ${field} 失败:`, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ctx.success(resumeDetail);
|
||||
} catch (error) {
|
||||
console.error('AI 分析失败:', error);
|
||||
return ctx.fail('AI 分析失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -180,34 +180,50 @@ class aiService {
|
||||
*/
|
||||
async analyzeResume(resumeText) {
|
||||
const prompt = `
|
||||
请分析以下简历内容,提取核心要素:
|
||||
请分析以下简历内容,并返回 JSON 格式的分析结果:
|
||||
|
||||
简历内容:
|
||||
${resumeText}
|
||||
|
||||
请提取以下信息:
|
||||
1. 技能标签(编程语言、框架、工具等)
|
||||
2. 工作经验(年限、行业、项目等)
|
||||
3. 教育背景(学历、专业、证书等)
|
||||
4. 期望薪资范围
|
||||
5. 期望工作地点
|
||||
6. 核心优势
|
||||
7. 职业发展方向
|
||||
请按以下格式返回 JSON 结果:
|
||||
{
|
||||
"skillTags": ["技能1", "技能2", "技能3"], // 技能标签数组(编程语言、框架、工具等)
|
||||
"strengths": "核心优势描述", // 简历的优势和亮点
|
||||
"weaknesses": "不足之处描述", // 简历的不足或需要改进的地方
|
||||
"careerSuggestion": "职业发展建议", // 针对该简历的职业发展方向和建议
|
||||
"competitiveness": 75 // 竞争力评分(0-100的整数),综合考虑工作年限、技能、经验等因素
|
||||
}
|
||||
|
||||
请以JSON格式返回结果。
|
||||
要求:
|
||||
1. skillTags 必须是字符串数组
|
||||
2. strengths、weaknesses、careerSuggestion 是字符串描述
|
||||
3. competitiveness 必须是 0-100 之间的整数
|
||||
4. 所有字段都必须返回,如果没有相关信息,使用空数组或空字符串
|
||||
`;
|
||||
|
||||
const result = await this.callAPI(prompt, {
|
||||
systemPrompt: '你是一个专业的简历分析师,擅长提取简历的核心要素和关键信息。',
|
||||
temperature: 0.2
|
||||
systemPrompt: '你是一个专业的简历分析师,擅长分析简历的核心要素、优势劣势、竞争力评分和职业发展建议。请以 JSON 格式返回分析结果,确保格式正确。',
|
||||
temperature: 0.3,
|
||||
maxTokens: 1500
|
||||
});
|
||||
|
||||
try {
|
||||
const analysis = JSON.parse(result.content);
|
||||
// 尝试从返回内容中提取 JSON
|
||||
let content = result.content.trim();
|
||||
|
||||
// 如果返回内容被代码块包裹,提取其中的 JSON
|
||||
const jsonMatch = content.match(/```(?:json)?\s*(\{[\s\S]*\})\s*```/) || content.match(/(\{[\s\S]*\})/);
|
||||
if (jsonMatch) {
|
||||
content = jsonMatch[1];
|
||||
}
|
||||
|
||||
const analysis = JSON.parse(content);
|
||||
return {
|
||||
analysis: analysis
|
||||
};
|
||||
} catch (parseError) {
|
||||
console.error(`[AI服务] 简历分析结果解析失败:`, parseError);
|
||||
console.error(`[AI服务] 原始内容:`, result.content);
|
||||
return {
|
||||
analysis: {
|
||||
content: result.content,
|
||||
|
||||
@@ -181,12 +181,103 @@ class ResumeManager {
|
||||
console.log(`[简历管理] 简历已创建 - ID: ${resumeId}`);
|
||||
}
|
||||
|
||||
// 二期规划:AI 分析暂时禁用,使用简单的文本匹配
|
||||
console.log(`[简历管理] AI分析已禁用(二期规划),使用文本匹配过滤`);
|
||||
// 调用 AI 分析简历并更新 AI 字段
|
||||
|
||||
try {
|
||||
await this.analyze_resume_with_ai(resumeId, resumeInfo);
|
||||
console.log(`[简历管理] AI 分析完成并已更新到数据库`);
|
||||
} catch (error) {
|
||||
console.error(`[简历管理] AI 分析失败:`, error);
|
||||
// AI 分析失败不影响主流程,继续返回成功
|
||||
}
|
||||
|
||||
return { resumeId, message: existingResume ? '简历更新成功' : '简历创建成功' };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建用于 AI 分析的简历文本
|
||||
* @param {object} resumeInfo - 简历信息对象
|
||||
* @returns {string} 简历文本内容
|
||||
*/
|
||||
build_resume_text_for_ai(resumeInfo) {
|
||||
const parts = [];
|
||||
|
||||
// 基本信息
|
||||
if (resumeInfo.fullName) parts.push(`姓名:${resumeInfo.fullName}`);
|
||||
if (resumeInfo.gender) parts.push(`性别:${resumeInfo.gender}`);
|
||||
if (resumeInfo.age) parts.push(`年龄:${resumeInfo.age}`);
|
||||
if (resumeInfo.location) parts.push(`所在地:${resumeInfo.location}`);
|
||||
|
||||
// 教育背景
|
||||
if (resumeInfo.education) parts.push(`学历:${resumeInfo.education}`);
|
||||
if (resumeInfo.school) parts.push(`毕业院校:${resumeInfo.school}`);
|
||||
if (resumeInfo.major) parts.push(`专业:${resumeInfo.major}`);
|
||||
if (resumeInfo.graduationYear) parts.push(`毕业年份:${resumeInfo.graduationYear}`);
|
||||
|
||||
// 工作经验
|
||||
if (resumeInfo.workYears) parts.push(`工作年限:${resumeInfo.workYears}`);
|
||||
if (resumeInfo.currentPosition) parts.push(`当前职位:${resumeInfo.currentPosition}`);
|
||||
if (resumeInfo.currentCompany) parts.push(`当前公司:${resumeInfo.currentCompany}`);
|
||||
|
||||
// 期望信息
|
||||
if (resumeInfo.expectedPosition) parts.push(`期望职位:${resumeInfo.expectedPosition}`);
|
||||
if (resumeInfo.expectedSalary) parts.push(`期望薪资:${resumeInfo.expectedSalary}`);
|
||||
if (resumeInfo.expectedLocation) parts.push(`期望地点:${resumeInfo.expectedLocation}`);
|
||||
|
||||
// 技能描述
|
||||
if (resumeInfo.skillDescription) parts.push(`技能描述:${resumeInfo.skillDescription}`);
|
||||
|
||||
// 工作经历
|
||||
if (resumeInfo.workExperience) {
|
||||
try {
|
||||
const workExp = JSON.parse(resumeInfo.workExperience);
|
||||
if (Array.isArray(workExp) && workExp.length > 0) {
|
||||
parts.push('\n工作经历:');
|
||||
workExp.forEach(work => {
|
||||
const workText = [
|
||||
work.company && `公司:${work.company}`,
|
||||
work.position && `职位:${work.position}`,
|
||||
work.startDate && work.endDate && `时间:${work.startDate} - ${work.endDate}`,
|
||||
work.content && `工作内容:${work.content}`
|
||||
].filter(Boolean).join(',');
|
||||
if (workText) parts.push(workText);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// 解析失败,忽略
|
||||
}
|
||||
}
|
||||
|
||||
// 项目经验
|
||||
if (resumeInfo.projectExperience) {
|
||||
try {
|
||||
const projectExp = JSON.parse(resumeInfo.projectExperience);
|
||||
if (Array.isArray(projectExp) && projectExp.length > 0) {
|
||||
parts.push('\n项目经验:');
|
||||
projectExp.forEach(project => {
|
||||
const projectText = [
|
||||
project.name && `项目名称:${project.name}`,
|
||||
project.role && `角色:${project.role}`,
|
||||
project.description && `描述:${project.description}`
|
||||
].filter(Boolean).join(',');
|
||||
if (projectText) parts.push(projectText);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// 解析失败,忽略
|
||||
}
|
||||
}
|
||||
|
||||
// 简历完整内容
|
||||
if (resumeInfo.resumeContent) {
|
||||
parts.push('\n简历详细内容:');
|
||||
parts.push(resumeInfo.resumeContent);
|
||||
}
|
||||
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 从描述中提取技能标签
|
||||
* @param {string} description - 描述文本
|
||||
@@ -253,16 +344,23 @@ ${resumeInfo.skillDescription}
|
||||
// 解析AI返回的结果
|
||||
const analysis = this.parse_ai_analysis(aiAnalysis, resumeInfo);
|
||||
|
||||
// 更新简历的AI分析字段
|
||||
await resume_info.update({
|
||||
aiSkillTags: JSON.stringify(analysis.skillTags),
|
||||
aiStrengths: analysis.strengths,
|
||||
aiWeaknesses: analysis.weaknesses,
|
||||
aiCareerSuggestion: analysis.careerSuggestion,
|
||||
aiCompetitiveness: analysis.competitiveness
|
||||
}, { where: { id: resumeId } });
|
||||
// 确保所有字段都有值
|
||||
const updateData = {
|
||||
aiSkillTags: JSON.stringify(analysis.skillTags || []),
|
||||
aiStrengths: analysis.strengths || '',
|
||||
aiWeaknesses: analysis.weaknesses || '',
|
||||
aiCareerSuggestion: analysis.careerSuggestion || '',
|
||||
aiCompetitiveness: parseInt(analysis.competitiveness || 70, 10)
|
||||
};
|
||||
|
||||
console.log(`[简历管理] AI分析完成 - 竞争力评分: ${analysis.competitiveness}`);
|
||||
// 确保竞争力评分在 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;
|
||||
} catch (error) {
|
||||
@@ -271,9 +369,26 @@ ${resumeInfo.skillDescription}
|
||||
fullName: resumeInfo.fullName
|
||||
});
|
||||
|
||||
// 如果AI分析失败,使用基于规则的默认分析
|
||||
// 如果AI分析失败,使用基于规则的默认分析,并保存到数据库
|
||||
const defaultAnalysis = this.get_default_analysis(resumeInfo);
|
||||
|
||||
// 保存默认分析结果到数据库
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -286,39 +401,62 @@ ${resumeInfo.skillDescription}
|
||||
*/
|
||||
parse_ai_analysis(aiResponse, resumeInfo) {
|
||||
try {
|
||||
// 尝试从AI响应中解析JSON
|
||||
const content = aiResponse.content || aiResponse.analysis?.content || '';
|
||||
// aiService.analyzeResume 返回格式: { analysis: {...} } 或 { analysis: { content: "...", parseError: true } }
|
||||
const analysis = aiResponse.analysis;
|
||||
|
||||
// 如果AI返回的是JSON格式
|
||||
if (content.includes('{') && content.includes('}')) {
|
||||
// 如果解析失败,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: parsed.competitiveness || parsed.竞争力评分 || 70
|
||||
competitiveness: parseInt(parsed.competitiveness || parsed.竞争力评分 || 70, 10)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 如果无法解析JSON,尝试从文本中提取信息
|
||||
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+)/);
|
||||
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]) : 70
|
||||
};
|
||||
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);
|
||||
// 解析失败时使用默认分析
|
||||
@@ -378,7 +516,7 @@ ${resumeInfo.skillDescription}
|
||||
|
||||
// 二期规划:AI 分析暂时禁用,使用简单的文本匹配
|
||||
// const analysis = await aiService.analyzeResume(resumeText);
|
||||
|
||||
|
||||
// 使用简单的文本匹配提取技能
|
||||
const skills = this.extract_skills_from_desc(resumeData.skillDescription || resumeText);
|
||||
|
||||
@@ -505,7 +643,7 @@ ${resumeInfo.skillDescription}
|
||||
throw new Error('MQTT客户端未初始化');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 重新获取和分析
|
||||
const resumeData = await this.get_online_resume(sn_code, mqttClient);
|
||||
return await this.analyze_resume(sn_code, resumeData);
|
||||
|
||||
@@ -296,7 +296,7 @@ class MqttDispatcher {
|
||||
isLoggedIn: updateData.isLoggedIn || false,
|
||||
...heartbeatData
|
||||
};
|
||||
console.log(`[MQTT心跳] 传递给 deviceManager 的数据:`, { sn_code, isLoggedIn: heartbeatPayload.isLoggedIn });
|
||||
|
||||
await deviceManager.recordHeartbeat(sn_code, heartbeatPayload);
|
||||
} catch (error) {
|
||||
console.error('[MQTT心跳] 处理心跳消息失败:', error);
|
||||
|
||||
@@ -54,8 +54,8 @@ class ScheduleManager {
|
||||
console.log('[调度管理器] 心跳监听已启动');
|
||||
|
||||
// 5. 启动定时任务
|
||||
this.scheduledJobs.start();
|
||||
console.log('[调度管理器] 定时任务已启动');
|
||||
// this.scheduledJobs.start();
|
||||
// console.log('[调度管理器] 定时任务已启动');
|
||||
|
||||
this.isInitialized = true;
|
||||
|
||||
|
||||
@@ -264,5 +264,5 @@ module.exports = (db) => {
|
||||
|
||||
return job_postings
|
||||
|
||||
// job_postings.sync({ force: true
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user