Files
autoAiWorkSys/api/controller_admin/resume_info.js
张成 6efd77d2b5 1
2025-12-26 13:12:53 +08:00

458 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 简历信息管理API - 后台管理
* 提供简历信息的查询和管理功能
*/
const Framework = require("../../framework/node-core-framework.js");
module.exports = {
/**
* @swagger
* /admin_api/resume/list:
* post:
* summary: 获取简历列表
* description: 分页获取所有简历信息
* tags: [后台-简历管理]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* page:
* type: integer
* description: 页码
* pageSize:
* type: integer
* description: 每页数量
* minCompetitiveness:
* type: integer
* description: 最小竞争力评分(可选)
* responses:
* 200:
* description: 获取成功
*/
'POST /resume/list': async (ctx) => {
const models = Framework.getModels();
const { resume_info, op } = models;
const body = ctx.getBody();
const { minCompetitiveness, searchText} = ctx.getBody();
// 获取分页参数
const { limit, offset } = ctx.getPageSize();
const where = {};
// 竞争力评分筛选
if (minCompetitiveness !== undefined) {
where.aiCompetitiveness = { [op.gte]: minCompetitiveness };
}
// 支持搜索姓名或技能
if (searchText) {
where[op.or] = [
{ fullName: { [op.like]: `%${searchText}%` } },
{ skills: { [op.like]: `%${searchText}%` } }
];
}
const result = await resume_info.findAndCountAll({
where,
limit,
offset,
order: [
['aiCompetitiveness', 'DESC'],
['id', 'DESC']
]
});
return ctx.success({
rows: result.rows,
count: result.count
});
},
/**
* @swagger
* /admin_api/resume/statistics:
* get:
* summary: 获取简历统计
* description: 获取简历信息的统计数据
* tags: [后台-简历管理]
* responses:
* 200:
* description: 获取成功
*/
'GET /resume/statistics': async (ctx) => {
const models = Framework.getModels();
const { resume_info } = models;
const totalResumes = await resume_info.count();
// 平均竞争力
const avgCompetitiveness = await resume_info.findAll({
attributes: [
[models.sequelize.fn('AVG', models.sequelize.col('aiCompetitiveness')), 'avgScore']
],
raw: true
});
// 按工作年限统计
const workYearsStats = await resume_info.findAll({
attributes: [
'workYears',
[models.sequelize.fn('COUNT', models.sequelize.col('*')), 'count']
],
group: ['workYears'],
raw: true
});
// 竞争力分布
const [
highCompetitiveness,
mediumCompetitiveness,
lowCompetitiveness
] = await Promise.all([
resume_info.count({ where: { aiCompetitiveness: { [models.op.gte]: 80 } } }),
resume_info.count({ where: { aiCompetitiveness: { [models.op.gte]: 60, [models.op.lt]: 80 } } }),
resume_info.count({ where: { aiCompetitiveness: { [models.op.lt]: 60 } } })
]);
return ctx.success({
totalResumes,
averageCompetitiveness: parseFloat(avgCompetitiveness[0]?.avgScore || 0).toFixed(2),
workYearsStats,
competitivenessDistribution: {
high: highCompetitiveness,
medium: mediumCompetitiveness,
low: lowCompetitiveness
}
});
},
/**
* @swagger
* /admin_api/resume/detail:
* get:
* summary: 获取简历详情
* description: 根据简历ID获取详细信息
* tags: [后台-简历管理]
* parameters:
* - in: query
* name: resumeId
* required: true
* schema:
* type: string
* description: 简历ID
* responses:
* 200:
* description: 获取成功
*/
'POST /resume/detail': 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('简历不存在');
}
// 解析 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);
},
/**
* @swagger
* /admin_api/resume/delete:
* post:
* summary: 删除简历
* description: 删除指定的简历信息
* tags: [后台-简历管理]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - resumeId
* properties:
* resumeId:
* type: string
* description: 简历ID
* responses:
* 200:
* description: 删除成功
*/
'POST /resume/delete': async (ctx) => {
const models = Framework.getModels();
const { resume_info } = models;
const body = ctx.getBody();
const { resumeId } = body;
if (!resumeId) {
return ctx.fail('简历ID不能为空');
}
const result = await resume_info.destroy({ where: { resumeId } });
if (result === 0) {
return ctx.fail('简历不存在');
}
return ctx.success({ message: '简历删除成功' });
},
/**
* @swagger
* /admin_api/resume/get-by-device:
* get:
* summary: 根据设备获取简历
* description: 获取指定设备和平台的活跃简历
* tags: [后台-简历管理]
* parameters:
* - in: query
* name: sn_code
* required: true
* schema:
* type: string
* description: 设备SN码
* - in: query
* name: platform
* required: true
* schema:
* type: string
* description: 平台
* responses:
* 200:
* description: 获取成功
*/
'GET /resume/get-by-device': async (ctx) => {
const models = Framework.getModels();
const { resume_info } = models;
const { sn_code, platform } = ctx.query;
if (!sn_code || !platform) {
return ctx.fail('设备SN码和平台不能为空');
}
const resume = await resume_info.findOne({
where: { sn_code, platform, isActive: true },
order: [['syncTime', 'DESC']]
});
if (!resume) {
return ctx.fail('未找到活跃简历');
}
const resumeDetail = resume.toJSON();
const jsonFields = ['skills', 'certifications', 'projectExperience', 'workExperience', 'aiSkillTags'];
jsonFields.forEach(field => {
if (resumeDetail[field]) {
resumeDetail[field] = JSON.parse(resumeDetail[field]);
}
});
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);
}
},
/**
* @swagger
* /admin_api/resume/sync-online:
* post:
* summary: 同步在线简历
* description: 通过MQTT指令获取用户在线简历并更新到数据库
* tags: [后台-简历管理]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - resumeId
* properties:
* resumeId:
* type: string
* description: 简历ID
* responses:
* 200:
* description: 同步成功
*/
'POST /resume/sync-online': 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('简历不存在');
}
const { sn_code, platform } = resume;
if (!sn_code) {
return ctx.fail('该简历未绑定设备SN码');
}
try {
const scheduleManager = require('../middleware/schedule');
const resumeManager = require('../middleware/job/resumeManager');
// 检查 MQTT 客户端是否已初始化
if (!scheduleManager.mqttClient) {
return ctx.fail('MQTT客户端未初始化请检查调度系统是否正常启动');
}
// 检查设备是否在线
// const deviceManager = require('../middleware/schedule/deviceManager');
// if (!deviceManager.isDeviceOnline(sn_code)) {
// return ctx.fail('设备离线,无法同步在线简历');
// }
// 调用简历管理器获取并保存简历
const resumeData = await resumeManager.get_online_resume(sn_code, scheduleManager.mqttClient, {
platform: platform || 'boss'
});
// 重新获取更新后的简历数据
const updatedResume = await resume_info.findOne({ where: { resumeId } });
if (!updatedResume) {
return ctx.fail('同步成功但未找到更新后的简历记录');
}
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({
message: '同步在线简历成功',
data: resumeDetail
});
} catch (error) {
console.error('同步在线简历失败:', error);
return ctx.fail('同步在线简历失败: ' + (error.message || '未知错误'));
}
}
};