/** * 简历信息管理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 || '未知错误')); } } };