This commit is contained in:
张成
2025-11-24 13:23:42 +08:00
commit 5d7444cd65
156 changed files with 50653 additions and 0 deletions

View File

@@ -0,0 +1,308 @@
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();