/** * 任务状态管理API - 后台管理 * 提供任务状态的查询和管理功能 */ const Framework = require("../../framework/node-core-framework.js"); module.exports = { /** * @swagger * /admin_api/task/commands: * post: * summary: 获取任务的指令列表 * description: 获取指定任务的所有指令记录 * tags: [后台-任务管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * taskId: * type: integer * description: 任务ID * responses: * 200: * description: 获取成功 */ 'POST /task/commands': async (ctx) => { const models = Framework.getModels(); const { task_commands, op } = models; const { taskId } = ctx.getBody(); if (!taskId) { return ctx.error('任务ID不能为空'); } // 查询该任务的所有指令,按执行顺序排序 const commands = await task_commands.findAll({ where: { task_id: taskId }, order: [ ['sequence', 'ASC'], ['create_time', 'ASC'] ] }); return ctx.success(commands); }, /** * @swagger * /admin_api/task/list: * post: * summary: 获取任务列表 * description: 分页获取所有任务状态 * tags: [后台-任务管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * page: * type: integer * description: 页码 * pageSize: * type: integer * description: 每页数量 * taskType: * type: string * description: 任务类型(可选) * status: * type: string * description: 任务状态(可选) * responses: * 200: * description: 获取成功 */ 'POST /task/list': async (ctx) => { const models = Framework.getModels(); const { task_status, op } = models; const body = ctx.getBody(); const { taskType, status, taskName, taskId, sn_code } = ctx.getBody(); // 获取分页参数 const { limit, offset } = ctx.getPageSize(); const where = {}; if (taskType) where.taskType = taskType; if (status) where.status = status; // 支持多种搜索字段 if (taskName) { where.taskName = { [op.like]: `%${taskName}%` }; } if (taskId) { // 使用 id 字段搜索(数据库主键) where.id = { [op.like]: `%${taskId}%` }; } if (sn_code) { where.sn_code = { [op.like]: `%${sn_code}%` }; } const result = await task_status.findAndCountAll({ where, limit, offset, order: [ ['status', 'ASC'], ['startTime', 'DESC'] ] }); return ctx.success({ count: result.count, rows: result.rows }); }, /** * @swagger * /admin_api/task/statistics: * get: * summary: 获取任务统计 * description: 获取任务状态的统计数据 * tags: [后台-任务管理] * responses: * 200: * description: 获取成功 */ 'GET /task/statistics': async (ctx) => { const models = Framework.getModels(); const { task_status } = models; const [ totalTasks, runningTasks, completedTasks, failedTasks, pausedTasks ] = await Promise.all([ task_status.count(), task_status.count({ where: { status: 'running' } }), task_status.count({ where: { status: 'completed' } }), task_status.count({ where: { status: 'failed' } }), task_status.count({ where: { status: 'paused' } }) ]); // 按任务类型统计 const typeStats = await task_status.findAll({ attributes: [ 'taskType', [models.sequelize.fn('COUNT', models.sequelize.col('*')), 'count'] ], group: ['taskType'], raw: true }); // 按状态统计 const statusStats = await task_status.findAll({ attributes: [ 'status', [models.sequelize.fn('COUNT', models.sequelize.col('*')), 'count'] ], group: ['status'], raw: true }); // 计算成功率 const successRate = totalTasks > 0 ? ((completedTasks / totalTasks) * 100).toFixed(2) : 0; return ctx.success({ totalTasks, runningTasks, completedTasks, failedTasks, pausedTasks, successRate, typeStats, statusStats }); }, /** * @swagger * /admin_api/task/detail: * get: * summary: 获取任务详情 * description: 根据任务ID获取详细信息 * tags: [后台-任务管理] * parameters: * - in: query * name: taskId * required: true * schema: * type: string * description: 任务ID * responses: * 200: * description: 获取成功 */ 'GET /task/detail': async (ctx) => { const models = Framework.getModels(); const { task_status } = models; const { taskId } = ctx.query; if (!taskId) { return ctx.fail('任务ID不能为空'); } // 使用 id 字段查询(数据库主键) const task = await task_status.findOne({ where: { id: taskId } }); if (!task) { return ctx.fail('任务不存在'); } return ctx.success(task); }, /** * @swagger * /admin_api/task/update: * post: * summary: 更新任务状态 * description: 更新任务的状态或进度 * tags: [后台-任务管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - taskId * properties: * taskId: * type: string * description: 任务ID * status: * type: string * description: 任务状态 * responses: * 200: * description: 更新成功 */ 'POST /task/update': async (ctx) => { const models = Framework.getModels(); const { task_status } = models; const body = ctx.getBody(); const { taskId, status, progress, errorMessage } = body; if (!taskId) { return ctx.fail('任务ID不能为空'); } const updateData = {}; if (status) { updateData.status = status; if (status === 'completed') { updateData.endTime = new Date(); updateData.progress = 100; } else if (status === 'failed') { updateData.endTime = new Date(); } } if (progress !== undefined) updateData.progress = progress; if (errorMessage) updateData.errorMessage = errorMessage; // 使用 id 字段更新(数据库主键) await task_status.update(updateData, { where: { id: taskId } }); return ctx.success({ message: '任务状态更新成功' }); }, /** * @swagger * /admin_api/task/delete: * post: * summary: 删除任务 * description: 删除指定的任务记录 * tags: [后台-任务管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - taskId * properties: * taskId: * type: string * description: 任务ID * responses: * 200: * description: 删除成功 */ 'POST /task/delete': async (ctx) => { const models = Framework.getModels(); const { task_status } = models; const body = ctx.getBody(); const { taskId } = body; if (!taskId) { return ctx.fail('任务ID不能为空'); } // 使用 id 字段删除(数据库主键) const result = await task_status.destroy({ where: { id: taskId } }); if (result === 0) { return ctx.fail('任务不存在'); } return ctx.success({ message: '任务删除成功' }); }, /** * @swagger * /admin_api/task/cancel: * post: * summary: 取消任务 * description: 取消正在执行或待执行的任务 * tags: [后台-任务管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - taskId * properties: * taskId: * type: string * description: 任务ID * responses: * 200: * description: 取消成功 */ 'POST /task/cancel': async (ctx) => { const models = Framework.getModels(); const { task_status } = models; const body = ctx.getBody(); const { taskId } = body; if (!taskId) { return ctx.fail('任务ID不能为空'); } // 使用 id 字段查询(数据库主键) const task = await task_status.findOne({ where: { id: taskId } }); if (!task) { return ctx.fail('任务不存在'); } if (task.status !== 'pending' && task.status !== 'running') { return ctx.fail('只能取消待执行或执行中的任务'); } // 尝试从任务队列中取消任务(如果任务在内存队列中) try { const scheduleManager = require('../middleware/schedule/index'); if (scheduleManager && scheduleManager.taskQueue) { const cancelled = await scheduleManager.taskQueue.cancelTask(taskId); if (!cancelled) { // 如果任务不在队列中,直接更新数据库 await task_status.update({ status: 'cancelled', endTime: new Date(), }, { where: { id: taskId } }); } } else { // 如果没有任务队列,直接更新数据库 await task_status.update({ status: 'cancelled', endTime: new Date(), }, { where: { id: taskId } }); } } catch (error) { console.error('[取消任务] 取消任务失败:', error); // 即使队列操作失败,也尝试更新数据库 await task_status.update({ status: 'cancelled', endTime: new Date(), }, { where: { id: taskId } }); } return ctx.success({ message: '任务取消成功' }); }, /** * @swagger * /admin_api/task/retry: * post: * summary: 重试任务 * description: 重新执行失败的任务 * tags: [后台-任务管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - taskId * properties: * taskId: * type: string * description: 任务ID * responses: * 200: * description: 重试成功 */ 'POST /task/retry': async (ctx) => { const models = Framework.getModels(); const { task_status } = models; const body = ctx.getBody(); // 兼容 taskId 和 id 参数 const task_id = body.taskId || body.id; if (!task_id) { return ctx.fail('任务ID不能为空'); } // 使用 id 字段查询(数据库主键) const task = await task_status.findOne({ where: { id: task_id } }); if (!task) { return ctx.fail('任务不存在'); } if (task.status !== 'failed') { return ctx.fail('只能重试失败的任务'); } await task_status.update({ status: 'pending', progress: 0, errorMessage: '', errorStack: '', startTime: null, endTime: null }, { where: { id: task_id } }); return ctx.success({ message: '任务重试成功' }); }, /** * @swagger * /admin_api/task/export: * post: * summary: 导出任务列表 * description: 导出任务列表为CSV文件 * tags: [后台-任务管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * taskType: * type: string * description: 任务类型(可选) * status: * type: string * description: 任务状态(可选) * responses: * 200: * description: 导出成功 */ 'POST /task/export': async (ctx) => { const models = Framework.getModels(); const { task_status, op } = models; const body = ctx.getBody(); const { taskType, status, taskName, id, sn_code } = body; const where = {}; if (taskType) where.taskType = taskType; if (status) where.status = status; if (taskName) where.taskName = { [op.like]: `%${taskName}%` }; if (id) where.id = id; if (sn_code) where.sn_code = { [op.like]: `%${sn_code}%` }; const tasks = await task_status.findAll({ where, order: [ ['status', 'ASC'], ['startTime', 'DESC'] ] }); // 生成CSV内容 const headers = ['任务ID', '设备SN码', '任务类型', '任务名称', '任务状态', '进度', '开始时间', '结束时间', '创建时间', '错误信息']; const rows = tasks.map(task => [ task.id, task.sn_code, task.taskType, task.taskName || '', task.status, task.progress || 0, task.startTime || '', task.endTime || '', task.errorMessage || '' ]); let csv = headers.join(',') + '\n'; csv += rows.map(row => row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(',')) .join('\n'); // 添加 BOM 以支持 Excel 正确识别 UTF-8 const bom = '\uFEFF'; const content = bom + csv; ctx.set('Content-Type', 'text/csv; charset=utf-8'); ctx.set('Content-Disposition', `attachment; filename="tasks_${Date.now()}.csv"`); return ctx.success(content); } };