/** * 平台账号管理API - 后台管理 * 提供平台账号的查询和管理功能 */ const Framework = require("../../framework/node-core-framework.js"); const plaAccountService = require('../services/pla_account_service'); module.exports = { 'GET /pla_account/getById': async (ctx) => { const { id } = ctx.getQuery(); const accountData = await plaAccountService.getAccountById(id); return ctx.success(accountData); }, 'POST /account/detail': async (ctx) => { const { id } = ctx.getBody(); const accountData = await plaAccountService.getAccountById(id); return ctx.success(accountData); }, /** * @swagger * /admin_api/account/getBySnCode: * post: * summary: 通过设备SN码获取账号信息 * description: 根据设备SN码直接获取对应的账号信息 * tags: [后台-账号管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - sn_code * properties: * sn_code: * type: string * description: 设备SN码 * responses: * 200: * description: 获取成功 */ 'POST /account/getBySnCode': async (ctx) => { const { sn_code } = ctx.getBody(); const accountData = await plaAccountService.getAccountBySnCode(sn_code); return ctx.success(accountData); }, /** * @swagger * /admin_api/account/list: * post: * summary: 获取账号列表 * description: 分页获取所有平台账号 * tags: [后台-账号管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * page: * type: integer * description: 页码 * pageSize: * type: integer * description: 每页数量 * key: * type: string * description: 搜索字段 * value: * type: string * description: 搜索值 * platform_type: * type: string * description: 平台类型ID * is_online: * type: boolean * description: 是否在线 * responses: * 200: * description: 获取成功 */ 'POST /account/list': async (ctx) => { const body = ctx.getBody(); // 支持两种参数格式:直接传参或通过 seachOption 传递 const seachOption = body.seachOption || {}; const pageOption = body.pageOption || {}; // 获取搜索参数(优先使用 seachOption,兼容直接传参) const key = seachOption.key || body.key; const value = seachOption.value || body.value; const platform_type = seachOption.platform_type || body.platform_type; const is_online = seachOption.is_online !== undefined ? seachOption.is_online : body.is_online; // 获取分页参数 const page = pageOption.page || body.page || 1; const pageSize = pageOption.pageSize || body.pageSize || 20; const limit = pageSize; const offset = (page - 1) * pageSize; const result = await plaAccountService.getAccountList({ key, value, platform_type, is_online, limit, offset }); return ctx.success(result); }, /** * @swagger * /admin_api/account/add: * post: * summary: 新增账号 * description: 创建新的平台账号 * tags: [后台-账号管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - name * - sn_code * - platform_type * - login_name * properties: * name: * type: string * description: 账户名 * sn_code: * type: string * description: 设备SN码 * platform_type: * type: string * description: 平台类型ID * login_name: * type: string * description: 登录名 * pwd: * type: string * description: 密码 * keyword: * type: string * description: 搜索关键词 * search_url: * type: string * description: 搜索页网址 * responses: * 200: * description: 创建成功 */ 'POST /account/add': async (ctx) => { const body = ctx.getBody(); const account = await plaAccountService.createAccount(body); return ctx.success({ message: '账号创建成功', data: account }); }, /** * @swagger * /admin_api/account/update: * post: * summary: 更新账号信息 * description: 更新平台账号信息 * tags: [后台-账号管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - id * properties: * id: * type: integer * description: 账号ID * responses: * 200: * description: 更新成功 */ 'POST /account/update': async (ctx) => { const body = ctx.getBody(); const { id, ...updateData } = body; await plaAccountService.updateAccount(id, updateData); return ctx.success({ message: '账号信息更新成功' }); }, /** * @swagger * /admin_api/account/delete: * post: * summary: 删除账号 * description: 删除指定的平台账号(软删除) * tags: [后台-账号管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - id * properties: * id: * type: integer * description: 账号ID * responses: * 200: * description: 删除成功 */ 'POST /account/delete': async (ctx) => { const body = ctx.getBody(); const { id } = body; await plaAccountService.deleteAccount(id); return ctx.success({ message: '账号删除成功' }); }, /** * @swagger * /admin_api/account/stopTasks: * post: * summary: 停止账号的所有任务 * description: 停止指定账号的所有待执行和正在执行的任务 * tags: [后台-账号管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - id * properties: * id: * type: integer * description: 账号ID * sn_code: * type: string * description: 设备SN码(可选,如果提供id则不需要) * responses: * 200: * description: 停止成功 */ 'POST /account/stopTasks': async (ctx) => { const body = ctx.getBody(); const result = await plaAccountService.stopTasks(body); return ctx.success(result); }, /** * @swagger * /admin_api/pla_account/tasks: * get: * summary: 获取账号的任务列表 * description: 根据账号ID获取该账号的所有任务列表(支持分页) * tags: [后台-账号管理] * parameters: * - in: query * name: id * required: true * schema: * type: integer * description: 账号ID * - in: query * name: page * schema: * type: integer * description: 页码 * - in: query * name: pageSize * schema: * type: integer * description: 每页数量 * responses: * 200: * description: 获取成功 */ 'GET /pla_account/tasks': async (ctx) => { const { id } = ctx.getQuery(); const { limit, offset } = ctx.getPageSize(); const result = await plaAccountService.getAccountTasks({ id, limit, offset }); return ctx.success(result); }, /** * @swagger * /admin_api/pla_account/commands: * get: * summary: 获取账号的指令列表 * description: 根据账号ID获取该账号的所有指令列表(支持分页) * tags: [后台-账号管理] * parameters: * - in: query * name: id * required: true * schema: * type: integer * description: 账号ID * - in: query * name: page * schema: * type: integer * description: 页码 * - in: query * name: pageSize * schema: * type: integer * description: 每页数量 * responses: * 200: * description: 获取成功 */ 'GET /pla_account/commands': async (ctx) => { const { id } = ctx.getQuery(); const { limit, offset } = ctx.getPageSize(); const result = await plaAccountService.getAccountCommands({ id, limit, offset }); return ctx.success(result); }, /** * @swagger * /admin_api/pla_account/runTask: * post: * summary: 执行账号指令 * description: 为指定账号直接执行指令(如用户登录、获取简历等) * tags: [后台-账号管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - id * - taskType * properties: * id: * type: integer * description: 账号ID * taskType: * type: string * description: 指令类型: get_login_qr_code-登录检查, get_resume-获取简历, search_jobs-搜索岗位 * taskName: * type: string * description: 指令名称 * responses: * 200: * description: 指令执行成功 */ 'POST /pla_account/runTask': async (ctx) => { const body = ctx.getBody(); const task = await plaAccountService.runTask(body); return ctx.success(task); }, /** * @swagger * /admin_api/pla_account/runCommand: * post: * summary: 执行账号指令 * description: 为指定账号直接执行指令(如用户登录、获取简历等) * tags: [后台-账号管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - id * - commandType * properties: * id: * type: integer * description: 账号ID * commandType: * type: string * description: 指令类型: get_login_qr_code-登录检查, get_resume-获取简历, search_jobs-搜索岗位 * commandName: * type: string * description: 指令名称 * responses: * 200: * description: 指令执行成功 */ 'POST /pla_account/runCommand': async (ctx) => { const body = ctx.getBody(); const result = await plaAccountService.runCommand(body); return ctx.success(result); }, /** * @swagger * /admin_api/pla_account/commandDetail: * get: * summary: 获取指令详情 * description: 根据账号ID和指令ID获取指令的详细信息 * tags: [后台-账号管理] * parameters: * - in: query * name: accountId * required: true * schema: * type: integer * description: 账号ID * - in: query * name: commandId * required: true * schema: * type: integer * description: 指令ID * responses: * 200: * description: 获取成功 */ 'GET /pla_account/commandDetail': async (ctx) => { const { accountId, commandId } = ctx.getQuery(); const commandDetail = await plaAccountService.getCommandDetail({ accountId, commandId }); return ctx.success(commandDetail); }, /** * @swagger * /admin_api/pla_account/retryCommand: * post: * summary: 重试指令 * description: 重新执行失败的指令 * tags: [后台-账号管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - commandId * properties: * commandId: * type: integer * description: 指令ID * responses: * 200: * description: 重试成功 */ 'POST /pla_account/retryCommand': async (ctx) => { const body = ctx.getBody(); const result = await plaAccountService.retryCommand(body); return ctx.success(result); }, /** * @swagger * /admin_api/pla_account/parseLocation: * post: * summary: 解析地址并更新经纬度 * description: 根据账号ID解析地址,获取经纬度并更新到账号信息中 * tags: [后台-账号管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - id * properties: * id: * type: integer * description: 账号ID * address: * type: string * description: 地址(可选,如果不提供则使用账号中的地址) * responses: * 200: * description: 解析成功 */ 'POST /pla_account/parseLocation': async (ctx) => { const body = ctx.getBody(); const result = await plaAccountService.parseLocation(body); return ctx.success(result); }, /** * @swagger * /admin_api/pla_account/batchParseLocation: * post: * summary: 批量解析地址并更新经纬度 * description: 批量解析多个账号的地址,获取经纬度并更新到账号信息中 * tags: [后台-账号管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - ids * properties: * ids: * type: array * items: * type: integer * description: 账号ID数组 * responses: * 200: * description: 批量解析完成 */ 'POST /pla_account/batchParseLocation': async (ctx) => { const body = ctx.getBody(); const { ids } = body; const result = await plaAccountService.batchParseLocation(ids); return ctx.success(result); }, /** * @swagger * /admin_api/account/check-authorization: * post: * summary: 检查账号授权状态 * description: 根据账号ID或SN码检查账号的授权状态(剩余天数、是否过期等) * tags: [后台-账号管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * id: * type: integer * description: 账号ID(可选,如果提供id则不需要sn_code) * sn_code: * type: string * description: 设备SN码(可选,如果提供sn_code则不需要id) * responses: * 200: * description: 检查成功 */ 'POST /account/check-authorization': async (ctx) => { const { id, sn_code } = ctx.getBody(); const models = await Framework.getModels(); const { pla_account } = models; const dayjs = require('dayjs'); if (!id && !sn_code) { return ctx.fail('请提供账号ID或SN码'); } const where = id ? { id } : { sn_code }; const account = await pla_account.findOne({ where }); if (!account) { return ctx.fail('账号不存在'); } const accountData = account.toJSON(); const authDate = accountData.authorization_date; const authDays = accountData.authorization_days || 0; // 计算剩余天数 const { calculateRemainingDays } = require('../utils/account_utils'); const remaining_days = calculateRemainingDays(authDate, authDays); const is_expired = remaining_days <= 0; let end_date = null; if (authDate && authDays > 0) { const startDate = dayjs(authDate); end_date = startDate.add(authDays, 'day').toDate(); } return ctx.success({ id: accountData.id, sn_code: accountData.sn_code, name: accountData.name, authorization_date: authDate, authorization_days: authDays, remaining_days: remaining_days, end_date: end_date, is_expired: is_expired, is_authorized: !is_expired && remaining_days > 0 }); }, /** * @swagger * /admin_api/account/update-authorization: * post: * summary: 更新账号授权信息 * description: 更新账号的授权日期和授权天数 * tags: [后台-账号管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - id * properties: * id: * type: integer * description: 账号ID * authorization_date: * type: string * format: date-time * description: 授权日期(可选,不提供则使用当前时间) * authorization_days: * type: integer * description: 授权天数(必填) * responses: * 200: * description: 更新成功 */ 'POST /account/update-authorization': async (ctx) => { const { id, authorization_date, authorization_days } = ctx.getBody(); const models = await Framework.getModels(); const { pla_account } = models; const dayjs = require('dayjs'); if (!id) { return ctx.fail('账号ID不能为空'); } if (authorization_days === undefined || authorization_days === null) { return ctx.fail('授权天数不能为空'); } if (authorization_days < 0) { return ctx.fail('授权天数不能为负数'); } const account = await pla_account.findOne({ where: { id } }); if (!account) { return ctx.fail('账号不存在'); } // 如果提供了授权日期,使用提供的日期;否则使用当前时间 const authDate = authorization_date ? new Date(authorization_date) : new Date(); await account.update({ authorization_date: authDate, authorization_days: authorization_days }); // 计算更新后的剩余天数 const { calculateRemainingDays } = require('../utils/account_utils'); const remaining_days = calculateRemainingDays(authDate, authorization_days); const end_date = dayjs(authDate).add(authorization_days, 'day').toDate(); return ctx.success({ message: '授权信息更新成功', data: { id: account.id, authorization_date: authDate, authorization_days: authorization_days, remaining_days: remaining_days, 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 || '未知错误')); } } };