11
This commit is contained in:
481
api/middleware/schedule/core/command.js
Normal file
481
api/middleware/schedule/core/command.js
Normal file
@@ -0,0 +1,481 @@
|
||||
const logs = require('../../logProxy');
|
||||
const db = require('../../dbProxy');
|
||||
const jobManager = require('../../job/index');
|
||||
const ScheduleUtils = require('../utils/scheduleUtils');
|
||||
const ScheduleConfig = require('../infrastructure/config');
|
||||
const authorizationService = require('../../../services/authorization_service');
|
||||
|
||||
|
||||
/**
|
||||
* 指令管理器
|
||||
* 负责管理任务下的多个指令,简化MQTT通信流程
|
||||
*/
|
||||
class CommandManager {
|
||||
constructor() {
|
||||
this.pendingCommands = new Map(); // 等待响应的指令 { command_id: { resolve, reject, timeout } }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 执行指令序列
|
||||
* @param {number} task_id - 任务ID
|
||||
* @param {Array} commands - 指令数组
|
||||
* @param {object} mqttClient - MQTT客户端
|
||||
* @param {object} options - 执行选项(保留用于扩展)
|
||||
* @returns {Promise<object>} 执行结果
|
||||
*/
|
||||
async executeCommands(task_id, commands, mqttClient, options = {}) {
|
||||
if (!commands || commands.length === 0) {
|
||||
throw new Error('没有找到要执行的指令');
|
||||
}
|
||||
|
||||
console.log(`[指令管理] 开始执行 ${commands.length} 个指令`);
|
||||
|
||||
const results = [];
|
||||
const errors = [];
|
||||
|
||||
// 顺序执行指令,失败时继续执行后续指令
|
||||
for (let i = 0; i < commands.length; i++) {
|
||||
const command = commands[i];
|
||||
|
||||
try {
|
||||
console.log(`[指令管理] 执行指令 ${i + 1}/${commands.length}: ${command.command_name || command.name}`);
|
||||
|
||||
const commandResult = await this.executeCommand(task_id, command, mqttClient);
|
||||
results.push(commandResult);
|
||||
|
||||
} catch (error) {
|
||||
// 指令执行失败,记录错误但继续执行后续指令
|
||||
errors.push({
|
||||
command: command,
|
||||
error: error
|
||||
});
|
||||
console.error(`[指令管理] 指令执行失败: ${command.command_name || command.name}, 错误: ${error.message}`);
|
||||
// 失败时继续执行后续指令
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 执行单个指令(统一封装)
|
||||
* 统一处理成功、失败、超时,统一记录数据库
|
||||
* @param {number} task_id - 任务ID
|
||||
* @param {object} command - 指令对象
|
||||
* @param {object} mqttClient - MQTT客户端
|
||||
* @returns {Promise<object>} 执行结果
|
||||
*/
|
||||
async executeCommand(task_id, command, mqttClient) {
|
||||
const start_time = new Date();
|
||||
let command_id = null;
|
||||
let command_record = null;
|
||||
|
||||
try {
|
||||
// 1. 获取任务信息
|
||||
const task = await db.getModel('task_status').findByPk(task_id);
|
||||
if (!task) {
|
||||
throw new Error(`任务不存在: ${task_id}`);
|
||||
}
|
||||
|
||||
// 1.5 检查账号授权状态
|
||||
const authCheck = await authorizationService.checkAuthorization(task.sn_code, 'sn_code');
|
||||
if (!authCheck.is_authorized) {
|
||||
throw new Error(`授权检查失败: ${authCheck.message}`);
|
||||
}
|
||||
|
||||
// 2. 获取指令信息
|
||||
const command_name = command.command_name || command.name || '未知指令';
|
||||
const command_type = command.command_type || command.type;
|
||||
const command_params = command.command_params ?
|
||||
(typeof command.command_params === 'string' ? JSON.parse(command.command_params) : command.command_params) :
|
||||
{};
|
||||
|
||||
if (!command_type) {
|
||||
throw new Error('指令类型不能为空');
|
||||
}
|
||||
|
||||
// 3. 创建指令记录
|
||||
command_record = await db.getModel('task_commands').create({
|
||||
task_id: task_id,
|
||||
command_type: command_type,
|
||||
command_name: command_name,
|
||||
command_params: JSON.stringify(command_params),
|
||||
priority: command.priority || 1,
|
||||
sequence: command.sequence || 1,
|
||||
max_retries: command.maxRetries || command.max_retries || 3,
|
||||
status: 'pending'
|
||||
});
|
||||
|
||||
command_id = command_record.id;
|
||||
console.log(`[指令管理] 创建指令记录: ${command_name} (ID: ${command_id})`);
|
||||
|
||||
// 4. 更新指令状态为运行中
|
||||
await this._update_command_status(command_id, 'running', null, null, start_time);
|
||||
|
||||
// 4.5 推送指令开始执行状态
|
||||
try {
|
||||
const deviceWorkStatusNotifier = require('../notifiers/deviceWorkStatusNotifier');
|
||||
const taskQueue = require('./taskQueue');
|
||||
const summary = await taskQueue.getTaskStatusSummary(task.sn_code);
|
||||
await deviceWorkStatusNotifier.sendDeviceWorkStatus(task.sn_code, summary, {
|
||||
currentCommand: {
|
||||
command_id: command_id,
|
||||
command_name: command_name,
|
||||
command_type: command_type,
|
||||
command_params: command_params,
|
||||
progress: 0,
|
||||
startTime: start_time.toISOString()
|
||||
}
|
||||
});
|
||||
} catch (pushError) {
|
||||
// 推送失败不影响指令执行
|
||||
console.warn(`[指令管理] 推送设备工作状态失败:`, pushError.message);
|
||||
}
|
||||
|
||||
// 5. 执行指令(统一封装)
|
||||
const result = await this._execute_command_with_timeout(
|
||||
command_id,
|
||||
command_type,
|
||||
command_name,
|
||||
command_params,
|
||||
task.sn_code,
|
||||
mqttClient,
|
||||
start_time
|
||||
);
|
||||
|
||||
// 6. 记录成功结果
|
||||
await this._record_command_result(command_id, 'completed', result, null, start_time);
|
||||
|
||||
// 6.5 推送指令完成状态
|
||||
try {
|
||||
const deviceWorkStatusNotifier = require('../notifiers/deviceWorkStatusNotifier');
|
||||
const taskQueue = require('./taskQueue');
|
||||
const summary = await taskQueue.getTaskStatusSummary(task.sn_code);
|
||||
await deviceWorkStatusNotifier.sendDeviceWorkStatus(task.sn_code, summary);
|
||||
} catch (pushError) {
|
||||
// 推送失败不影响指令执行
|
||||
console.warn(`[指令管理] 推送设备工作状态失败:`, pushError.message);
|
||||
}
|
||||
|
||||
return {
|
||||
command_id: command_id,
|
||||
command_name: command_name,
|
||||
result: result,
|
||||
duration: new Date() - start_time,
|
||||
success: true
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
// 统一处理错误(失败或超时)
|
||||
if (command_id) {
|
||||
await this._record_command_result(
|
||||
command_id,
|
||||
'failed',
|
||||
null,
|
||||
error,
|
||||
start_time
|
||||
);
|
||||
|
||||
// 推送指令失败状态
|
||||
try {
|
||||
const deviceWorkStatusNotifier = require('../notifiers/deviceWorkStatusNotifier');
|
||||
const taskQueue = require('./taskQueue');
|
||||
const summary = await taskQueue.getTaskStatusSummary(task.sn_code);
|
||||
await deviceWorkStatusNotifier.sendDeviceWorkStatus(task.sn_code, summary);
|
||||
} catch (pushError) {
|
||||
// 推送失败不影响错误处理
|
||||
console.warn(`[指令管理] 推送设备工作状态失败:`, pushError.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 重新抛出错误,让调用方知道执行失败
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行指令(带超时保护)
|
||||
* @private
|
||||
*/
|
||||
async _execute_command_with_timeout(command_id, command_type, command_name, command_params, sn_code, mqttClient, start_time) {
|
||||
// 获取指令超时时间(从配置中获取,默认5分钟)
|
||||
const timeout = ScheduleConfig.taskTimeouts[command_type] || 5 * 60 * 1000;
|
||||
|
||||
// 构建指令执行 Promise
|
||||
const command_promise = (async () => {
|
||||
// 直接使用 command_type 调用 jobManager 的方法,不做映射
|
||||
// command_type 和 jobManager 的方法名保持一致
|
||||
if (jobManager[command_type]) {
|
||||
return await jobManager[command_type](sn_code, mqttClient, command_params);
|
||||
} else {
|
||||
throw new Error(`未知的指令类型: ${command_type}, jobManager 中不存在对应方法`);
|
||||
}
|
||||
})();
|
||||
|
||||
// 使用超时机制包装
|
||||
try {
|
||||
const result = await ScheduleUtils.withTimeout(
|
||||
command_promise,
|
||||
timeout,
|
||||
`指令执行超时: ${command_name} (超时时间: ${timeout / 1000}秒)`
|
||||
);
|
||||
return result;
|
||||
} catch (error) {
|
||||
// 判断是否为超时错误
|
||||
if (error.message && error.message.includes('超时')) {
|
||||
error.isTimeout = true;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录指令执行结果(统一封装)
|
||||
* 处理成功、失败、超时等情况,统一记录到数据库
|
||||
* @private
|
||||
*/
|
||||
async _record_command_result(command_id, status, result, error, start_time) {
|
||||
const end_time = new Date();
|
||||
const duration = end_time.getTime() - start_time.getTime();
|
||||
|
||||
let error_message = null;
|
||||
let error_stack = null;
|
||||
let result_data = null;
|
||||
|
||||
// 处理错误信息
|
||||
if (error) {
|
||||
error_message = error.message || '指令执行失败';
|
||||
error_stack = error.stack || '';
|
||||
|
||||
// 如果是超时错误,添加标识
|
||||
if (error.isTimeout) {
|
||||
error_message = `[超时] ${error_message}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理结果数据
|
||||
if (result && status === 'completed') {
|
||||
result_data = result;
|
||||
}
|
||||
|
||||
// 更新数据库
|
||||
await this._update_command_status(
|
||||
command_id,
|
||||
status,
|
||||
result_data,
|
||||
error_message,
|
||||
start_time,
|
||||
end_time,
|
||||
duration,
|
||||
error_stack
|
||||
);
|
||||
|
||||
// 记录日志
|
||||
if (status === 'completed') {
|
||||
console.log(`[指令管理] 指令执行成功: ${command_id} (耗时: ${duration}ms)`);
|
||||
} else if (status === 'failed') {
|
||||
const error_type = error && error.isTimeout ? '超时' : '失败';
|
||||
console.error(`[指令管理] 指令执行${error_type}: ${command_id}, 错误: ${error_message}`, {
|
||||
command_id: command_id,
|
||||
duration: duration,
|
||||
isTimeout: error && error.isTimeout
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新指令状态(统一封装)
|
||||
* @private
|
||||
*/
|
||||
async _update_command_status(command_id, status, result, error_message, start_time, end_time, duration, error_stack) {
|
||||
try {
|
||||
const update_data = {
|
||||
status: status,
|
||||
updated_at: new Date()
|
||||
};
|
||||
|
||||
// 设置开始时间
|
||||
if (status === 'running' && start_time) {
|
||||
update_data.start_time = start_time;
|
||||
}
|
||||
|
||||
// 设置结束时间和执行时长
|
||||
if ((status === 'completed' || status === 'failed') && end_time) {
|
||||
update_data.end_time = end_time;
|
||||
if (duration !== undefined) {
|
||||
update_data.duration = duration;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理执行结果
|
||||
if (result && status === 'completed') {
|
||||
const result_str = this._format_result_for_storage(result);
|
||||
update_data.result = result_str;
|
||||
update_data.progress = 100;
|
||||
}
|
||||
|
||||
// 处理错误信息
|
||||
if (error_message) {
|
||||
update_data.error_message = this._truncate_string(error_message, 10000);
|
||||
}
|
||||
|
||||
if (error_stack) {
|
||||
update_data.error_stack = this._truncate_string(error_stack, 50000);
|
||||
}
|
||||
|
||||
// 更新数据库
|
||||
await db.getModel('task_commands').update(update_data, {
|
||||
where: { id: command_id }
|
||||
});
|
||||
|
||||
} catch (db_error) {
|
||||
logs.error(`[指令管理] 更新指令状态失败:`, db_error, {
|
||||
command_id: command_id,
|
||||
status: status
|
||||
});
|
||||
|
||||
// 如果是因为数据太长导致的错误,尝试只保存错误信息
|
||||
if (db_error.message && db_error.message.includes('Data too long')) {
|
||||
try {
|
||||
await db.getModel('task_commands').update({
|
||||
status: status,
|
||||
error_message: '结果数据过长,无法保存完整结果',
|
||||
end_time: end_time || new Date(),
|
||||
updated_at: new Date()
|
||||
}, {
|
||||
where: { id: command_id }
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`[指令管理] 保存截断结果也失败:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化结果数据用于存储
|
||||
* @private
|
||||
*/
|
||||
_format_result_for_storage(result) {
|
||||
try {
|
||||
let result_str = JSON.stringify(result);
|
||||
const max_length = 60000; // 限制为60KB
|
||||
|
||||
if (result_str.length > max_length) {
|
||||
// 如果结果太长,尝试压缩或截断
|
||||
if (typeof result === 'object' && result !== null) {
|
||||
const summary = {
|
||||
success: result.success !== undefined ? result.success : true,
|
||||
message: result.message || '执行成功',
|
||||
dataLength: result_str.length,
|
||||
truncated: true,
|
||||
preview: result_str.substring(0, 1000) // 保存前1000字符作为预览
|
||||
};
|
||||
result_str = JSON.stringify(summary);
|
||||
} else {
|
||||
result_str = result_str.substring(0, max_length) + '...[数据已截断]';
|
||||
}
|
||||
}
|
||||
|
||||
return result_str;
|
||||
} catch (error) {
|
||||
return JSON.stringify({ error: '结果序列化失败', message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 截断字符串
|
||||
* @private
|
||||
*/
|
||||
_truncate_string(str, max_length) {
|
||||
if (!str || str.length <= max_length) {
|
||||
return str;
|
||||
}
|
||||
return str.substring(0, max_length) + '...[已截断]';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新指令状态(兼容旧接口,内部调用统一方法)
|
||||
* @param {number} command_id - 指令ID
|
||||
* @param {string} status - 状态
|
||||
* @param {object} result - 结果
|
||||
* @param {string} error_message - 错误信息
|
||||
* @deprecated 建议使用 _update_command_status 统一方法
|
||||
*/
|
||||
async updateCommandStatus(command_id, status, result = null, error_message = null) {
|
||||
const start_time = status === 'running' ? new Date() : null;
|
||||
const end_time = (status === 'completed' || status === 'failed') ? new Date() : null;
|
||||
|
||||
// 计算执行时长
|
||||
let duration = null;
|
||||
if (end_time && start_time) {
|
||||
duration = end_time.getTime() - start_time.getTime();
|
||||
} else if (end_time) {
|
||||
// 如果没有开始时间,尝试从数据库获取
|
||||
try {
|
||||
const command = await db.getModel('task_commands').findByPk(command_id);
|
||||
if (command && command.start_time) {
|
||||
duration = end_time.getTime() - new Date(command.start_time).getTime();
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略错误
|
||||
}
|
||||
}
|
||||
|
||||
await this._update_command_status(
|
||||
command_id,
|
||||
status,
|
||||
result,
|
||||
error_message,
|
||||
start_time,
|
||||
end_time,
|
||||
duration,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 清理过期的指令记录
|
||||
* @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();
|
||||
242
api/middleware/schedule/core/deviceManager.js
Normal file
242
api/middleware/schedule/core/deviceManager.js
Normal file
@@ -0,0 +1,242 @@
|
||||
const dayjs = require('dayjs');
|
||||
const Sequelize = require('sequelize');
|
||||
const db = require('../../dbProxy');
|
||||
const config = require('../infrastructure/config');
|
||||
const utils = require('../utils/scheduleUtils');
|
||||
|
||||
/**
|
||||
* 设备管理器(简化版)
|
||||
* 合并了 Monitor 和 Strategy 的核心功能
|
||||
*/
|
||||
class DeviceManager {
|
||||
constructor() {
|
||||
// 设备状态 { sn_code: { isOnline, lastHeartbeat, lastSearch, lastApply, lastChat, dailyCounts } }
|
||||
this.devices = new Map();
|
||||
|
||||
// 系统统计
|
||||
this.stats = {
|
||||
totalDevices: 0,
|
||||
onlineDevices: 0,
|
||||
totalTasks: 0,
|
||||
completedTasks: 0,
|
||||
failedTasks: 0,
|
||||
startTime: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
async init() {
|
||||
console.log('[设备管理器] 初始化中...');
|
||||
await this.loadStats();
|
||||
console.log('[设备管理器] 初始化完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载统计数据
|
||||
*/
|
||||
async loadStats() {
|
||||
try {
|
||||
const devices = await db.getModel('pla_account').findAll();
|
||||
this.stats.totalDevices = devices.length;
|
||||
|
||||
const completedCount = await db.getModel('task_status').count({
|
||||
where: { status: 'completed' }
|
||||
});
|
||||
const failedCount = await db.getModel('task_status').count({
|
||||
where: { status: 'failed' }
|
||||
});
|
||||
|
||||
this.stats.completedTasks = completedCount;
|
||||
this.stats.failedTasks = failedCount;
|
||||
this.stats.totalTasks = completedCount + failedCount;
|
||||
} catch (error) {
|
||||
console.error('[设备管理器] 加载统计失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录心跳
|
||||
*/
|
||||
async recordHeartbeat(sn_code, heartbeatData = {}) {
|
||||
const now = Date.now();
|
||||
if (!this.devices.has(sn_code)) {
|
||||
this.devices.set(sn_code, {
|
||||
isOnline: true,
|
||||
isLoggedIn: heartbeatData.isLoggedIn || false,
|
||||
lastHeartbeat: now,
|
||||
dailyCounts: { date: utils.getTodayString(), searchCount: 0, applyCount: 0, chatCount: 0 }
|
||||
});
|
||||
console.log(`[设备管理器] 新设备 ${sn_code} 初始化 - isLoggedIn: ${heartbeatData.isLoggedIn}`);
|
||||
}
|
||||
|
||||
const device = this.devices.get(sn_code);
|
||||
device.isOnline = true;
|
||||
device.lastHeartbeat = now;
|
||||
// 更新登录状态
|
||||
if (heartbeatData.isLoggedIn !== undefined) {
|
||||
device.isLoggedIn = heartbeatData.isLoggedIn;
|
||||
console.log(`[设备管理器] 设备 ${sn_code} 登录状态更新 - isLoggedIn: ${device.isLoggedIn}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查设备是否在线
|
||||
*/
|
||||
isDeviceOnline(sn_code) {
|
||||
const device = this.devices.get(sn_code);
|
||||
if (!device) return false;
|
||||
|
||||
const elapsed = Date.now() - device.lastHeartbeat;
|
||||
if (elapsed > config.monitoring.heartbeatTimeout) {
|
||||
device.isOnline = false;
|
||||
return false;
|
||||
}
|
||||
return device.isOnline;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以执行操作
|
||||
*/
|
||||
canExecuteOperation(sn_code, operation_type) {
|
||||
// 检查日限制(频率限制已由各任务使用账号配置中的间隔时间,不再使用全局配置)
|
||||
const device = this.devices.get(sn_code);
|
||||
if (device && device.dailyCounts) {
|
||||
const today = utils.getTodayString();
|
||||
if (device.dailyCounts.date !== today) {
|
||||
device.dailyCounts = { date: today, searchCount: 0, applyCount: 0, chatCount: 0 };
|
||||
}
|
||||
const countKey = `${operation_type}Count`;
|
||||
const current = device.dailyCounts[countKey] || 0;
|
||||
const max = config.getDailyLimit(operation_type);
|
||||
if (current >= max) {
|
||||
return { allowed: false, reason: `今日${operation_type}操作已达上限` };
|
||||
}
|
||||
}
|
||||
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录操作
|
||||
*/
|
||||
recordOperation(sn_code, operation_type) {
|
||||
const device = this.devices.get(sn_code) || {};
|
||||
device[`last${operation_type.charAt(0).toUpperCase() + operation_type.slice(1)}`] = Date.now();
|
||||
|
||||
if (device.dailyCounts) {
|
||||
const countKey = `${operation_type}Count`;
|
||||
device.dailyCounts[countKey] = (device.dailyCounts[countKey] || 0) + 1;
|
||||
}
|
||||
|
||||
this.devices.set(sn_code, device);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录任务开始
|
||||
*/
|
||||
recordTaskStart(sn_code, task) {
|
||||
// 简化实现,只记录日志
|
||||
console.log(`[设备管理器] 设备 ${sn_code} 开始执行任务: ${task.taskName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录任务完成
|
||||
*/
|
||||
recordTaskComplete(sn_code, task, success, duration) {
|
||||
if (success) {
|
||||
this.stats.completedTasks++;
|
||||
} else {
|
||||
this.stats.failedTasks++;
|
||||
}
|
||||
this.stats.totalTasks++;
|
||||
console.log(`[设备管理器] 设备 ${sn_code} 任务${success ? '成功' : '失败'}: ${task.taskName} (${duration}ms)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统统计
|
||||
*/
|
||||
getSystemStats() {
|
||||
const onlineCount = Array.from(this.devices.values()).filter(d => d.isOnline).length;
|
||||
return {
|
||||
...this.stats,
|
||||
onlineDevices: onlineCount,
|
||||
uptime: utils.formatDuration(Date.now() - this.stats.startTime.getTime())
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有设备状态
|
||||
*/
|
||||
getAllDevicesStatus() {
|
||||
const result = {};
|
||||
for (const [sn_code, device] of this.devices.entries()) {
|
||||
result[sn_code] = {
|
||||
isOnline: device.isOnline,
|
||||
isLoggedIn: device.isLoggedIn || false,
|
||||
lastHeartbeat: device.lastHeartbeat,
|
||||
dailyCounts: device.dailyCounts || {}
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查心跳状态(仅更新内存状态,device_status 表已移除)
|
||||
*/
|
||||
async checkHeartbeatStatus() {
|
||||
try {
|
||||
const now = Date.now();
|
||||
const offlineDevices = [];
|
||||
|
||||
// 检查内存中的设备状态
|
||||
for (const [sn_code, device] of this.devices.entries()) {
|
||||
if (now - device.lastHeartbeat > config.monitoring.heartbeatTimeout) {
|
||||
// 如果之前是在线状态,现在检测到离线
|
||||
if (device.isOnline) {
|
||||
device.isOnline = false;
|
||||
offlineDevices.push(sn_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 记录离线设备(仅日志,不再更新数据库)
|
||||
if (offlineDevices.length > 0) {
|
||||
console.log(`[设备管理器] 检测到 ${offlineDevices.length} 个设备心跳超时: ${offlineDevices.join(', ')}`);
|
||||
// 注意:device_status 表已移除,设备状态仅在内存中维护
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[设备管理器] 检查心跳状态失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置所有日计数器
|
||||
*/
|
||||
resetAllDailyCounters() {
|
||||
const today = utils.getTodayString();
|
||||
for (const device of this.devices.values()) {
|
||||
if (device.dailyCounts && device.dailyCounts.date !== today) {
|
||||
device.dailyCounts = { date: today, searchCount: 0, applyCount: 0, chatCount: 0 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理离线设备
|
||||
*/
|
||||
cleanupOfflineDevices(threshold = 3600000) {
|
||||
const now = Date.now();
|
||||
for (const [sn_code, device] of this.devices.entries()) {
|
||||
if (now - device.lastHeartbeat > threshold) {
|
||||
this.devices.delete(sn_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
const deviceManager = new DeviceManager();
|
||||
module.exports = deviceManager;
|
||||
|
||||
16
api/middleware/schedule/core/index.js
Normal file
16
api/middleware/schedule/core/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Core 模块导出
|
||||
* 统一导出核心模块,简化引用路径
|
||||
*/
|
||||
|
||||
const deviceManager = require('./deviceManager');
|
||||
const taskQueue = require('./taskQueue');
|
||||
const command = require('./command');
|
||||
const scheduledJobs = require('./scheduledJobs');
|
||||
|
||||
module.exports = {
|
||||
deviceManager,
|
||||
taskQueue,
|
||||
command,
|
||||
scheduledJobs
|
||||
};
|
||||
570
api/middleware/schedule/core/scheduledJobs.js
Normal file
570
api/middleware/schedule/core/scheduledJobs.js
Normal file
@@ -0,0 +1,570 @@
|
||||
const node_schedule = require("node-schedule");
|
||||
const dayjs = require('dayjs');
|
||||
const config = require('../infrastructure/config.js');
|
||||
const deviceManager = require('./deviceManager.js');
|
||||
const command = require('./command.js');
|
||||
const db = require('../../dbProxy');
|
||||
|
||||
// 引入新的任务模块
|
||||
const tasks = require('../tasks');
|
||||
const { autoSearchTask, autoDeliverTask, autoChatTask, autoActiveTask } = tasks;
|
||||
|
||||
const Framework = require("../../../../framework/node-core-framework.js");
|
||||
|
||||
/**
|
||||
* 定时任务管理器(重构版)
|
||||
* 使用独立的任务模块,职责更清晰,易于维护和扩展
|
||||
*/
|
||||
class ScheduledJobs {
|
||||
constructor(components, taskHandlers) {
|
||||
this.taskQueue = components.taskQueue;
|
||||
this.taskHandlers = taskHandlers;
|
||||
this.jobs = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动所有定时任务
|
||||
*/
|
||||
start() {
|
||||
console.log('[定时任务] 开始启动所有定时任务...');
|
||||
|
||||
// ==================== 系统维护任务 ====================
|
||||
|
||||
// 每天凌晨重置统计数据
|
||||
const resetJob = node_schedule.scheduleJob(config.schedules.dailyReset, () => {
|
||||
this.resetDailyStats();
|
||||
});
|
||||
this.jobs.push(resetJob);
|
||||
console.log('[定时任务] ✓ 已启动每日统计重置任务');
|
||||
|
||||
// 启动心跳检查定时任务(每分钟检查一次)
|
||||
const monitoringJob = node_schedule.scheduleJob(config.schedules.monitoringInterval, async () => {
|
||||
await deviceManager.checkHeartbeatStatus().catch(error => {
|
||||
console.error('[定时任务] 检查心跳状态失败:', error);
|
||||
});
|
||||
});
|
||||
this.jobs.push(monitoringJob);
|
||||
console.log('[定时任务] ✓ 已启动心跳检查任务');
|
||||
|
||||
// 启动离线设备任务清理定时任务(每分钟检查一次)
|
||||
const cleanupOfflineTasksJob = node_schedule.scheduleJob(config.schedules.monitoringInterval, async () => {
|
||||
await this.cleanupOfflineDeviceTasks().catch(error => {
|
||||
console.error('[定时任务] 清理离线设备任务失败:', error);
|
||||
});
|
||||
});
|
||||
this.jobs.push(cleanupOfflineTasksJob);
|
||||
console.log('[定时任务] ✓ 已启动离线设备任务清理任务');
|
||||
|
||||
// 启动任务超时检查定时任务(每分钟检查一次)
|
||||
const timeoutCheckJob = node_schedule.scheduleJob(config.schedules.monitoringInterval, async () => {
|
||||
await this.checkTaskTimeouts().catch(error => {
|
||||
console.error('[定时任务] 检查任务超时失败:', error);
|
||||
});
|
||||
});
|
||||
this.jobs.push(timeoutCheckJob);
|
||||
console.log('[定时任务] ✓ 已启动任务超时检查任务');
|
||||
|
||||
// 启动任务状态摘要同步定时任务(每10秒发送一次)
|
||||
const taskSummaryJob = node_schedule.scheduleJob('*/10 * * * * *', async () => {
|
||||
await this.syncTaskStatusSummary().catch(error => {
|
||||
console.error('[定时任务] 同步任务状态摘要失败:', error);
|
||||
});
|
||||
});
|
||||
this.jobs.push(taskSummaryJob);
|
||||
console.log('[定时任务] ✓ 已启动任务状态摘要同步任务');
|
||||
|
||||
// ==================== 业务任务(使用新的任务模块) ====================
|
||||
|
||||
// 1. 自动搜索任务 - 每60分钟执行一次
|
||||
const autoSearchJob = node_schedule.scheduleJob(config.schedules.autoSearch || '0 0 */1 * * *', () => {
|
||||
this.runAutoSearchTask();
|
||||
});
|
||||
this.jobs.push(autoSearchJob);
|
||||
console.log('[定时任务] ✓ 已启动自动搜索任务 (每60分钟)');
|
||||
|
||||
// 2. 自动投递任务 - 每1分钟检查一次
|
||||
const autoDeliverJob = node_schedule.scheduleJob(config.schedules.autoDeliver, () => {
|
||||
this.runAutoDeliverTask();
|
||||
});
|
||||
this.jobs.push(autoDeliverJob);
|
||||
console.log('[定时任务] ✓ 已启动自动投递任务 (每1分钟)');
|
||||
|
||||
// 3. 自动沟通任务 - 每15分钟执行一次
|
||||
const autoChatJob = node_schedule.scheduleJob(config.schedules.autoChat || '0 */15 * * * *', () => {
|
||||
this.runAutoChatTask();
|
||||
});
|
||||
this.jobs.push(autoChatJob);
|
||||
console.log('[定时任务] ✓ 已启动自动沟通任务 (每15分钟)');
|
||||
|
||||
// 4. 自动活跃任务 - 每2小时执行一次
|
||||
const autoActiveJob = node_schedule.scheduleJob(config.schedules.autoActive || '0 0 */2 * * *', () => {
|
||||
this.runAutoActiveTask();
|
||||
});
|
||||
this.jobs.push(autoActiveJob);
|
||||
console.log('[定时任务] ✓ 已启动自动活跃任务 (每2小时)');
|
||||
|
||||
// 立即执行一次业务任务(可选)
|
||||
setTimeout(() => {
|
||||
console.log('[定时任务] 立即执行一次初始化任务...');
|
||||
this.runAutoDeliverTask();
|
||||
this.runAutoChatTask();
|
||||
}, 3000); // 延迟3秒,等待系统初始化完成
|
||||
|
||||
console.log('[定时任务] 所有定时任务启动完成!');
|
||||
}
|
||||
|
||||
// ==================== 业务任务执行方法(使用新的任务模块) ====================
|
||||
|
||||
/**
|
||||
* 运行自动搜索任务
|
||||
* 为所有启用自动搜索的账号添加搜索任务
|
||||
*/
|
||||
async runAutoSearchTask() {
|
||||
try {
|
||||
const accounts = await this.getEnabledAccounts('auto_search');
|
||||
|
||||
if (accounts.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[自动搜索调度] 找到 ${accounts.length} 个启用自动搜索的账号`);
|
||||
|
||||
let successCount = 0;
|
||||
let failedCount = 0;
|
||||
|
||||
for (const account of accounts) {
|
||||
const result = await autoSearchTask.addToQueue(account.sn_code, this.taskQueue);
|
||||
if (result.success) {
|
||||
successCount++;
|
||||
} else {
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (successCount > 0 || failedCount > 0) {
|
||||
console.log(`[自动搜索调度] 完成: 成功 ${successCount} 个, 失败/跳过 ${failedCount} 个`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[自动搜索调度] 执行失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行自动投递任务
|
||||
* 为所有启用自动投递的账号添加投递任务
|
||||
*/
|
||||
async runAutoDeliverTask() {
|
||||
try {
|
||||
const accounts = await this.getEnabledAccounts('auto_deliver');
|
||||
|
||||
if (accounts.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[自动投递调度] 找到 ${accounts.length} 个启用自动投递的账号`);
|
||||
|
||||
let successCount = 0;
|
||||
let failedCount = 0;
|
||||
|
||||
for (const account of accounts) {
|
||||
const result = await autoDeliverTask.addToQueue(account.sn_code, this.taskQueue);
|
||||
if (result.success) {
|
||||
successCount++;
|
||||
} else {
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (successCount > 0 || failedCount > 0) {
|
||||
console.log(`[自动投递调度] 完成: 成功 ${successCount} 个, 失败/跳过 ${failedCount} 个`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[自动投递调度] 执行失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行自动沟通任务
|
||||
* 为所有启用自动沟通的账号添加沟通任务
|
||||
*/
|
||||
async runAutoChatTask() {
|
||||
try {
|
||||
const accounts = await this.getEnabledAccounts('auto_chat');
|
||||
|
||||
if (accounts.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[自动沟通调度] 找到 ${accounts.length} 个启用自动沟通的账号`);
|
||||
|
||||
let successCount = 0;
|
||||
let failedCount = 0;
|
||||
|
||||
for (const account of accounts) {
|
||||
const result = await autoChatTask.addToQueue(account.sn_code, this.taskQueue);
|
||||
if (result.success) {
|
||||
successCount++;
|
||||
} else {
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (successCount > 0 || failedCount > 0) {
|
||||
console.log(`[自动沟通调度] 完成: 成功 ${successCount} 个, 失败/跳过 ${failedCount} 个`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[自动沟通调度] 执行失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行自动活跃任务
|
||||
* 为所有启用自动活跃的账号添加活跃任务
|
||||
*/
|
||||
async runAutoActiveTask() {
|
||||
try {
|
||||
const accounts = await this.getEnabledAccounts('auto_active');
|
||||
|
||||
if (accounts.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[自动活跃调度] 找到 ${accounts.length} 个启用自动活跃的账号`);
|
||||
|
||||
let successCount = 0;
|
||||
let failedCount = 0;
|
||||
|
||||
for (const account of accounts) {
|
||||
const result = await autoActiveTask.addToQueue(account.sn_code, this.taskQueue);
|
||||
if (result.success) {
|
||||
successCount++;
|
||||
} else {
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (successCount > 0 || failedCount > 0) {
|
||||
console.log(`[自动活跃调度] 完成: 成功 ${successCount} 个, 失败/跳过 ${failedCount} 个`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[自动活跃调度] 执行失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启用指定功能的账号列表
|
||||
* @param {string} featureType - 功能类型: auto_search, auto_deliver, auto_chat, auto_active
|
||||
*/
|
||||
async getEnabledAccounts(featureType) {
|
||||
try {
|
||||
const { pla_account } = db.models;
|
||||
|
||||
const accounts = await pla_account.findAll({
|
||||
where: {
|
||||
is_delete: 0,
|
||||
is_enabled: 1,
|
||||
[featureType]: 1
|
||||
},
|
||||
attributes: ['sn_code', 'name', 'keyword', 'platform_type']
|
||||
});
|
||||
|
||||
return accounts.map(acc => acc.toJSON());
|
||||
} catch (error) {
|
||||
console.error(`[获取账号列表] 失败 (${featureType}):`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 系统维护方法(保留原有逻辑) ====================
|
||||
|
||||
/**
|
||||
* 重置每日统计
|
||||
*/
|
||||
resetDailyStats() {
|
||||
console.log('[定时任务] 重置每日统计数据');
|
||||
|
||||
try {
|
||||
deviceManager.resetAllDailyCounters();
|
||||
console.log('[定时任务] 每日统计重置完成');
|
||||
} catch (error) {
|
||||
console.error('[定时任务] 重置统计失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期数据
|
||||
*/
|
||||
cleanupCaches() {
|
||||
console.log('[定时任务] 开始清理过期数据');
|
||||
|
||||
try {
|
||||
deviceManager.cleanupOfflineDevices(config.monitoring.offlineThreshold);
|
||||
command.cleanupExpiredCommands(30);
|
||||
console.log('[定时任务] 数据清理完成');
|
||||
} catch (error) {
|
||||
console.error('[定时任务] 数据清理失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理离线设备任务
|
||||
* 检查离线超过10分钟的设备,取消其所有pending/running状态的任务
|
||||
*/
|
||||
async cleanupOfflineDeviceTasks() {
|
||||
try {
|
||||
// 离线阈值:10分钟
|
||||
const offlineThreshold = 10 * 60 * 1000;
|
||||
const now = Date.now();
|
||||
const thresholdTime = now - offlineThreshold;
|
||||
|
||||
// 获取所有启用的账号
|
||||
const pla_account = db.getModel('pla_account');
|
||||
const accounts = await pla_account.findAll({
|
||||
where: {
|
||||
is_delete: 0,
|
||||
is_enabled: 1
|
||||
},
|
||||
attributes: ['sn_code']
|
||||
});
|
||||
|
||||
if (!accounts || accounts.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 通过 deviceManager 检查哪些设备离线超过10分钟
|
||||
const offlineSnCodes = [];
|
||||
const offlineDevicesInfo = [];
|
||||
|
||||
for (const account of accounts) {
|
||||
const sn_code = account.sn_code;
|
||||
const device = deviceManager.devices.get(sn_code);
|
||||
|
||||
if (!device) {
|
||||
offlineSnCodes.push(sn_code);
|
||||
offlineDevicesInfo.push({
|
||||
sn_code: sn_code,
|
||||
lastHeartbeatTime: null
|
||||
});
|
||||
} else {
|
||||
const lastHeartbeat = device.lastHeartbeat || 0;
|
||||
if (lastHeartbeat < thresholdTime || !device.isOnline) {
|
||||
offlineSnCodes.push(sn_code);
|
||||
offlineDevicesInfo.push({
|
||||
sn_code: sn_code,
|
||||
lastHeartbeatTime: lastHeartbeat ? new Date(lastHeartbeat) : null
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (offlineSnCodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[清理离线任务] 发现 ${offlineSnCodes.length} 个离线超过10分钟的设备`);
|
||||
|
||||
let totalCancelled = 0;
|
||||
const task_status = db.getModel('task_status');
|
||||
|
||||
for (const sn_code of offlineSnCodes) {
|
||||
try {
|
||||
const deviceInfo = offlineDevicesInfo.find(d => d.sn_code === sn_code);
|
||||
|
||||
const updateResult = await task_status.update(
|
||||
{
|
||||
status: 'cancelled',
|
||||
endTime: new Date(),
|
||||
result: JSON.stringify({
|
||||
reason: '设备离线超过10分钟,任务已自动取消',
|
||||
offlineTime: deviceInfo?.lastHeartbeatTime
|
||||
})
|
||||
},
|
||||
{
|
||||
where: {
|
||||
sn_code: sn_code,
|
||||
status: ['pending', 'running']
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const cancelledCount = Array.isArray(updateResult) ? updateResult[0] : updateResult;
|
||||
totalCancelled += cancelledCount;
|
||||
|
||||
if (this.taskQueue && typeof this.taskQueue.cancelDeviceTasks === 'function') {
|
||||
await this.taskQueue.cancelDeviceTasks(sn_code);
|
||||
}
|
||||
|
||||
if (cancelledCount > 0) {
|
||||
console.log(`[清理离线任务] 设备 ${sn_code} 已取消 ${cancelledCount} 个任务`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[清理离线任务] 取消设备 ${sn_code} 的任务失败:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (totalCancelled > 0) {
|
||||
console.log(`[清理离线任务] 共取消 ${totalCancelled} 个离线设备的任务`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[清理离线任务] 执行失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步任务状态摘要到客户端
|
||||
*/
|
||||
async syncTaskStatusSummary() {
|
||||
try {
|
||||
const { pla_account } = await Framework.getModels();
|
||||
|
||||
const accounts = await pla_account.findAll({
|
||||
where: {
|
||||
is_delete: 0,
|
||||
is_enabled: 1
|
||||
},
|
||||
attributes: ['sn_code']
|
||||
});
|
||||
|
||||
if (!accounts || accounts.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offlineThreshold = 3 * 60 * 1000;
|
||||
const now = Date.now();
|
||||
|
||||
for (const account of accounts) {
|
||||
const sn_code = account.sn_code;
|
||||
const device = deviceManager.devices.get(sn_code);
|
||||
|
||||
if (!device) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const lastHeartbeat = device.lastHeartbeat || 0;
|
||||
const isOnline = device.isOnline && (now - lastHeartbeat < offlineThreshold);
|
||||
|
||||
if (!isOnline) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const deviceWorkStatusNotifier = require('../notifiers/deviceWorkStatusNotifier');
|
||||
const summary = await this.taskQueue.getTaskStatusSummary(sn_code);
|
||||
await deviceWorkStatusNotifier.sendDeviceWorkStatus(sn_code, summary, {
|
||||
currentCommand: summary.currentCommand || null
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[设备工作状态同步] 设备 ${sn_code} 同步失败:`, error.message);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[任务状态同步] 执行失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查任务超时并强制标记为失败
|
||||
*/
|
||||
async checkTaskTimeouts() {
|
||||
try {
|
||||
const Sequelize = require('sequelize');
|
||||
const { task_status, op } = db.models;
|
||||
|
||||
const runningTasks = await task_status.findAll({
|
||||
where: {
|
||||
status: 'running'
|
||||
},
|
||||
attributes: ['id', 'sn_code', 'taskType', 'taskName', 'startTime', 'create_time']
|
||||
});
|
||||
|
||||
if (!runningTasks || runningTasks.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
let timeoutCount = 0;
|
||||
|
||||
for (const task of runningTasks) {
|
||||
const taskData = task.toJSON();
|
||||
const startTime = taskData.startTime ? new Date(taskData.startTime) : (taskData.create_time ? new Date(taskData.create_time) : null);
|
||||
|
||||
if (!startTime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const taskTimeout = config.getTaskTimeout(taskData.taskType) || 10 * 60 * 1000;
|
||||
const maxAllowedTime = taskTimeout * 1.2;
|
||||
const elapsedTime = now.getTime() - startTime.getTime();
|
||||
|
||||
if (elapsedTime > maxAllowedTime) {
|
||||
try {
|
||||
await task_status.update(
|
||||
{
|
||||
status: 'failed',
|
||||
endTime: now,
|
||||
duration: elapsedTime,
|
||||
result: JSON.stringify({
|
||||
error: `任务执行超时(运行时间: ${Math.round(elapsedTime / 1000)}秒,超时限制: ${Math.round(maxAllowedTime / 1000)}秒)`,
|
||||
timeout: true,
|
||||
taskType: taskData.taskType,
|
||||
startTime: startTime.toISOString()
|
||||
}),
|
||||
progress: 0
|
||||
},
|
||||
{
|
||||
where: { id: taskData.id }
|
||||
}
|
||||
);
|
||||
|
||||
timeoutCount++;
|
||||
console.warn(`[任务超时检查] 任务 ${taskData.id} (${taskData.taskName}) 运行时间过长,已强制标记为失败`);
|
||||
|
||||
if (this.taskQueue && typeof this.taskQueue.deviceStatus !== 'undefined') {
|
||||
const deviceStatus = this.taskQueue.deviceStatus.get(taskData.sn_code);
|
||||
if (deviceStatus && deviceStatus.currentTask && deviceStatus.currentTask.id === taskData.id) {
|
||||
deviceStatus.isRunning = false;
|
||||
deviceStatus.currentTask = null;
|
||||
deviceStatus.runningCount = Math.max(0, deviceStatus.runningCount - 1);
|
||||
this.taskQueue.globalRunningCount = Math.max(0, this.taskQueue.globalRunningCount - 1);
|
||||
|
||||
console.log(`[任务超时检查] 已重置设备 ${taskData.sn_code} 的状态`);
|
||||
|
||||
setTimeout(() => {
|
||||
this.taskQueue.processQueue(taskData.sn_code).catch(error => {
|
||||
console.error(`[任务超时检查] 继续处理队列失败:`, error);
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[任务超时检查] 更新超时任务状态失败:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (timeoutCount > 0) {
|
||||
console.log(`[任务超时检查] 共检测到 ${timeoutCount} 个超时任务`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[任务超时检查] 执行失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止所有定时任务
|
||||
*/
|
||||
stop() {
|
||||
console.log('[定时任务] 停止所有定时任务...');
|
||||
|
||||
for (const job of this.jobs) {
|
||||
if (job) {
|
||||
job.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
this.jobs = [];
|
||||
console.log('[定时任务] 所有定时任务已停止');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ScheduledJobs;
|
||||
1328
api/middleware/schedule/core/taskQueue.js
Normal file
1328
api/middleware/schedule/core/taskQueue.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user