532 lines
14 KiB
JavaScript
532 lines
14 KiB
JavaScript
/**
|
|
* 任务状态管理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);
|
|
}
|
|
};
|
|
|