Files
autoAiWorkSys/api/middleware/schedule/command.js
张成 5d7444cd65 1
2025-11-24 13:23:42 +08:00

309 lines
11 KiB
JavaScript
Raw 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.
const logs = require('../logProxy');
const db = require('../dbProxy');
const jobManager = require('../job/index');
const ScheduleUtils = require('./utils');
const ScheduleConfig = require('./config');
/**
* 指令管理器
* 负责管理任务下的多个指令简化MQTT通信流程
*/
class CommandManager {
constructor() {
this.pendingCommands = new Map(); // 等待响应的指令 { commandId: { resolve, reject, timeout } }
}
/**
* 执行指令序列
* @param {Array} commands - 指令数组
* @param {object} mqttClient - MQTT客户端
* @param {object} options - 执行选项
* @returns {Promise<object>} 执行结果
*/
async executeCommands(taskId, commands, mqttClient, options = {}) {
// try {
if (!commands || commands.length === 0) {
throw new Error('没有找到要执行的指令');
}
const {
maxRetries = 1, // 最大重试次数
retryDelay = 1000 // 重试延迟(毫秒)
} = options;
console.log(`[指令管理] 开始执行 ${commands.length} 个指令`);
const results = [];
const errors = [];
// 顺序执行指令,失败时停止
for (let i = 0; i < commands.length; i++) {
const command = commands[i];
let retryCount = 0;
let commandResult = null;
// 重试逻辑
while (retryCount <= maxRetries) {
console.log(`[指令管理] 执行指令 ${i + 1}/${commands.length}: ${command.command_name || command.name} (尝试 ${retryCount + 1}/${maxRetries + 1})`);
commandResult = await this.executeCommand(taskId, command, mqttClient);
results. push(commandResult);
break; // 成功执行,跳出重试循环
}
}
const successCount = results.length;
const errorCount = errors.length;
console.log(`[指令管理] 指令执行完成: 成功 ${successCount}/${commands.length}, 失败 ${errorCount}`);
return {
success: errorCount === 0, // 只有全部成功才算成功
results: results,
errors: errors,
totalCommands: commands.length,
successCount: successCount,
errorCount: errorCount
};
// } catch (error) {
// console.error(`[指令管理] 执行指令序列失败:`, error);
// throw error;
// }
}
/**
* 执行单个指令
* @param {object} command - 指令对象
* @param {object} mqttClient - MQTT客户端
* @returns {Promise<object>} 执行结果
*/
async executeCommand(taskId, command, mqttClient) {
const startTime = new Date();
let commandRecord = null;
const task = await db.getModel('task_status').findByPk(taskId);
// 获取指令信息(支持两种格式)
const commandName = command.command_name;
const commandType = command.command_type;
const commandParams = command.command_params ? JSON.parse(command.command_params) : {};
// 创建指令记录
commandRecord = await db.getModel('task_commands').create({
task_id: taskId,
command_type: commandType,
command_name: commandName,
command_params: JSON.stringify(commandParams),
priority: command.priority || 1,
sequence: command.sequence || 1,
max_retries: command.maxRetries || command.max_retries || 3,
status: 'pending'
});
let commandId = commandRecord.id;
console.log(`[指令管理] 创建指令记录: ${commandName} (ID: ${commandId})`);
// 更新指令状态为运行中
await this.updateCommandStatus(commandId, 'running');
console.log(`[指令管理] 执行指令: ${commandName} (ID: ${commandId})`);
const sn_code = task.sn_code;
// 将驼峰命名转换为下划线命名getOnlineResume -> get_online_resume
const toSnakeCase = (str) => {
// 如果已经是下划线格式,直接返回
if (str.includes('_')) {
return str;
}
// 驼峰转下划线
return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
};
const methodName = toSnakeCase(commandType);
// 获取指令超时时间从配置中获取默认5分钟
const timeout = ScheduleConfig.taskTimeouts[commandType] || ScheduleConfig.taskTimeouts[methodName] || 5 * 60 * 1000;
let result;
try {
// 使用超时机制包装指令执行
const commandPromise = (async () => {
if (commandType && jobManager[methodName]) {
return await jobManager[methodName](sn_code, mqttClient, commandParams);
} else {
// 如果转换后找不到,尝试直接使用原名称
if (jobManager[commandType]) {
return await jobManager[commandType](sn_code, mqttClient, commandParams);
} else {
throw new Error(`未知的指令类型: ${commandType} (尝试的方法名: ${methodName})`);
}
}
})();
// 使用超时机制
result = await ScheduleUtils.withTimeout(
commandPromise,
timeout,
`指令执行超时: ${commandName} (超时时间: ${timeout / 1000}秒)`
);
} catch (error) {
const endTime = new Date();
const duration = endTime - startTime;
// 如果是超时错误,更新指令状态为失败
const errorMessage = error.message || '指令执行失败';
await this.updateCommandStatus(commandId, 'failed', null, errorMessage);
throw error;
}
const endTime = new Date();
const duration = endTime - startTime;
// 更新指令状态为完成
await this.updateCommandStatus(commandId, 'completed', result);
return {
commandId: commandId,
commandName: commandName,
result: result,
duration: duration,
success: true
};
}
/**
* 更新指令状态
* @param {number} commandId - 指令ID
* @param {string} status - 状态
* @param {object} result - 结果
* @param {string} errorMessage - 错误信息
*/
async updateCommandStatus(commandId, status, result = null, errorMessage = null) {
try {
const updateData = {
status: status,
updated_at: new Date()
};
if (status === 'running') {
updateData.start_time = new Date();
} else if (status === 'completed' || status === 'failed') {
updateData.end_time = new Date();
if (result) {
// 将结果转换为JSON字符串并限制长度TEXT类型最大约65KB
let resultStr = JSON.stringify(result);
const maxLength = 60000; // 限制为60KB留一些余量
if (resultStr.length > maxLength) {
// 如果结果太长,尝试压缩或截断
try {
// 如果是对象,尝试只保存关键信息
if (typeof result === 'object' && result !== null) {
const summary = {
success: result.success !== undefined ? result.success : true,
message: result.message || '执行成功',
dataLength: resultStr.length,
truncated: true,
preview: resultStr.substring(0, 1000) // 保存前1000字符作为预览
};
resultStr = JSON.stringify(summary);
} else {
// 直接截断
resultStr = resultStr.substring(0, maxLength) + '...[数据已截断]';
}
} catch (e) {
// 如果处理失败,直接截断
resultStr = resultStr.substring(0, maxLength) + '...[数据已截断]';
}
}
updateData.result = resultStr;
updateData.progress = 100;
}
if (errorMessage) {
// 错误信息也限制长度
const maxErrorLength = 10000; // 错误信息限制10KB
updateData.error_message = errorMessage.length > maxErrorLength
? errorMessage.substring(0, maxErrorLength) + '...[错误信息已截断]'
: errorMessage;
}
// 计算执行时长
const command = await db.getModel('task_commands').findByPk(commandId);
if (command && command.start_time) {
const duration = new Date() - new Date(command.start_time);
updateData.duration = duration;
}
}
await db.getModel('task_commands').update(updateData, {
where: { id: commandId }
});
} catch (error) {
logs.error(`[指令管理] 更新指令状态失败:`, error, {
commandId: commandId,
status: status
});
// 如果是因为数据太长导致的错误,尝试只保存错误信息
if (error.message && error.message.includes('Data too long')) {
try {
await db.getModel('task_commands').update({
status: status,
error_message: '结果数据过长,无法保存完整结果',
end_time: new Date(),
updated_at: new Date()
}, {
where: { id: commandId }
});
} catch (e) {
console.error(`[指令管理] 保存截断结果也失败:`, e);
}
}
}
}
/**
* 清理过期的指令记录
* @param {number} days - 保留天数
*/
async cleanupExpiredCommands(days = 30) {
try {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - days);
const deletedCount = await db.getModel('task_commands').destroy({
where: {
create_time: {
[db.Sequelize.Op.lt]: cutoffDate
}
}
});
console.log(`[指令管理] 清理了 ${deletedCount} 条过期指令记录`);
return deletedCount;
} catch (error) {
logs.error(`[指令管理] 清理过期指令失败:`, error);
return 0;
}
}
}
module.exports = new CommandManager();