const dayjs = require('dayjs'); const deviceManager = require('../core/deviceManager'); const db = require('../../dbProxy'); /** * 任务基类 * 提供所有任务的通用功能和冲突检测机制 */ class BaseTask { constructor(taskType, config = {}) { this.taskType = taskType; this.config = { // 默认配置 defaultInterval: 30, // 默认间隔30分钟 defaultPriority: 5, requiresLogin: true, // 是否需要登录状态 conflictsWith: [], // 与哪些任务类型冲突 ...config }; // 任务执行锁 { sn_code: timestamp } this.taskLocks = new Map(); // 最后执行时间缓存 { sn_code: timestamp } this.lastExecutionCache = new Map(); } /** * Layer 1: 任务类型互斥锁检查 * 防止同一设备同时添加相同类型的任务 */ acquireTaskLock(sn_code) { const lockKey = `${sn_code}:${this.taskType}`; const now = Date.now(); const existingLock = this.taskLocks.get(lockKey); // 如果存在锁且未超时(5分钟),返回false if (existingLock && (now - existingLock) < 5 * 60 * 1000) { const remainingTime = Math.ceil((5 * 60 * 1000 - (now - existingLock)) / 1000); return { success: false, reason: `任务 ${this.taskType} 正在添加中,请等待 ${remainingTime} 秒` }; } // 获取锁 this.taskLocks.set(lockKey, now); return { success: true }; } /** * 释放任务锁 */ releaseTaskLock(sn_code) { const lockKey = `${sn_code}:${this.taskType}`; this.taskLocks.delete(lockKey); } /** * Layer 2: 设备状态检查 * 检查设备是否在线、是否登录、是否忙碌 */ async checkDeviceStatus(sn_code) { // 1. 检查设备是否在线 const device = deviceManager.devices.get(sn_code); if (!device) { return { allowed: false, reason: `设备 ${sn_code} 离线(从未发送心跳)` }; } // 2. 检查心跳超时 const offlineThreshold = 3 * 60 * 1000; // 3分钟 const now = Date.now(); const lastHeartbeat = device.lastHeartbeat || 0; const isOnline = device.isOnline && (now - lastHeartbeat < offlineThreshold); if (!isOnline) { const offlineMinutes = lastHeartbeat ? Math.round((now - lastHeartbeat) / (60 * 1000)) : '未知'; return { allowed: false, reason: `设备 ${sn_code} 离线(最后心跳: ${offlineMinutes}分钟前)` }; } // 3. 检查登录状态(如果任务需要) if (this.config.requiresLogin && !device.isLoggedIn) { return { allowed: false, reason: `设备 ${sn_code} 未登录平台账号` }; } return { allowed: true }; } /** * Layer 3: 检查任务队列状态 * 防止队列中已有相同任务 */ async checkTaskQueue(sn_code, taskQueue) { // 获取设备队列 const deviceQueue = taskQueue.deviceQueues.get(sn_code); if (!deviceQueue) { return { allowed: true }; } // 检查队列中是否有相同类型的待执行任务 const tasks = deviceQueue.toArray(); const hasSameTypeTask = tasks.some(task => task.taskType === this.taskType && task.status === 'pending' ); if (hasSameTypeTask) { return { allowed: false, reason: `队列中已存在待执行的 ${this.taskType} 任务` }; } return { allowed: true }; } /** * Layer 4: 检查任务去重 * 查询数据库中是否有重复的待执行任务 */ async checkDuplicateTask(sn_code) { try { const { task_status } = db.models; // 查询该设备是否有相同类型的pending/running任务 const existingTask = await task_status.findOne({ where: { sn_code: sn_code, taskType: this.taskType, status: ['pending', 'running'] }, attributes: ['id', 'status', 'taskName'] }); if (existingTask) { return { allowed: false, reason: `已存在 ${existingTask.status} 状态的任务: ${existingTask.taskName}` }; } return { allowed: true }; } catch (error) { console.error(`[${this.taskType}] 检查重复任务失败:`, error); // 出错时允许继续,避免阻塞 return { allowed: true }; } } /** * Layer 5: 操作类型冲突检测 * 某些操作类型不能同时执行 */ async checkOperationConflict(sn_code, taskQueue) { // 如果没有配置冲突类型,直接通过 if (!this.config.conflictsWith || this.config.conflictsWith.length === 0) { return { allowed: true }; } // 检查当前是否有冲突的任务正在执行 const deviceStatus = taskQueue.deviceStatus.get(sn_code); if (deviceStatus && deviceStatus.currentTask) { const currentTaskType = deviceStatus.currentTask.taskType; if (this.config.conflictsWith.includes(currentTaskType)) { return { allowed: false, reason: `与正在执行的任务 ${currentTaskType} 冲突` }; } } return { allowed: true }; } /** * 检查执行间隔 * 从数据库查询上次成功执行时间,判断是否满足间隔要求 */ async checkExecutionInterval(sn_code, intervalMinutes) { try { const { task_status } = db.models; // 先从缓存检查 const cachedLastExecution = this.lastExecutionCache.get(sn_code); const now = Date.now(); if (cachedLastExecution) { const elapsedTime = now - cachedLastExecution; const interval_ms = intervalMinutes * 60 * 1000; if (elapsedTime < interval_ms) { const remainingMinutes = Math.ceil((interval_ms - elapsedTime) / (60 * 1000)); const elapsedMinutes = Math.round(elapsedTime / (60 * 1000)); return { allowed: false, reason: `距离上次执行仅 ${elapsedMinutes} 分钟,还需等待 ${remainingMinutes} 分钟(间隔: ${intervalMinutes} 分钟)`, remainingMinutes, elapsedMinutes }; } } // 从数据库查询最近一次成功完成的任务 const lastTask = await task_status.findOne({ where: { sn_code: sn_code, taskType: this.taskType, status: 'completed' }, order: [['endTime', 'DESC']], attributes: ['endTime'] }); // 如果存在上次执行记录,检查是否已经过了间隔时间 if (lastTask && lastTask.endTime) { const lastExecutionTime = new Date(lastTask.endTime).getTime(); const elapsedTime = now - lastExecutionTime; const interval_ms = intervalMinutes * 60 * 1000; // 更新缓存 this.lastExecutionCache.set(sn_code, lastExecutionTime); if (elapsedTime < interval_ms) { const remainingMinutes = Math.ceil((interval_ms - elapsedTime) / (60 * 1000)); const elapsedMinutes = Math.round(elapsedTime / (60 * 1000)); return { allowed: false, reason: `距离上次执行仅 ${elapsedMinutes} 分钟,还需等待 ${remainingMinutes} 分钟(间隔: ${intervalMinutes} 分钟)`, remainingMinutes, elapsedMinutes, nextExecutionTime: new Date(lastExecutionTime + interval_ms) }; } } return { allowed: true }; } catch (error) { console.error(`[${this.taskType}] 检查执行间隔失败:`, error); // 出错时允许继续,避免阻塞 return { allowed: true }; } } /** * 检查时间范围限制 * @param {Object} timeRange - 时间范围配置 {start_time: '09:00', end_time: '18:00', workdays_only: 1} */ checkTimeRange(timeRange) { if (!timeRange || !timeRange.start_time || !timeRange.end_time) { return { allowed: true, reason: '未配置时间范围' }; } const now = new Date(); const currentHour = now.getHours(); const currentMinute = now.getMinutes(); const currentTime = currentHour * 60 + currentMinute; // 解析开始时间和结束时间 const [startHour, startMinute] = timeRange.start_time.split(':').map(Number); const [endHour, endMinute] = timeRange.end_time.split(':').map(Number); const startTime = startHour * 60 + startMinute; const endTime = endHour * 60 + endMinute; // 检查是否仅工作日 if (timeRange.workdays_only == 1) { const dayOfWeek = now.getDay(); if (dayOfWeek === 0 || dayOfWeek === 6) { return { allowed: false, reason: '当前是周末,不在允许的时间范围内' }; } } // 检查当前时间是否在时间范围内 if (startTime <= endTime) { // 正常情况: 09:00 - 18:00 if (currentTime < startTime || currentTime >= endTime) { return { allowed: false, reason: `当前时间不在允许的时间范围内 (${timeRange.start_time} - ${timeRange.end_time})` }; } } else { // 跨天情况: 22:00 - 06:00 if (currentTime < startTime && currentTime >= endTime) { return { allowed: false, reason: `当前时间不在允许的时间范围内 (${timeRange.start_time} - ${timeRange.end_time})` }; } } return { allowed: true, reason: '在允许的时间范围内' }; } /** * 综合检查 - 执行所有层级的检查 * @param {string} sn_code - 设备SN码 * @param {Object} taskQueue - 任务队列实例 * @param {Object} options - 额外选项 * @returns {Object} { allowed: boolean, reason: string } */ async canExecuteTask(sn_code, taskQueue, options = {}) { const checks = [ { name: 'Layer1-任务锁', fn: () => this.acquireTaskLock(sn_code) }, { name: 'Layer2-设备状态', fn: () => this.checkDeviceStatus(sn_code) }, { name: 'Layer3-队列检查', fn: () => this.checkTaskQueue(sn_code, taskQueue) }, { name: 'Layer4-任务去重', fn: () => this.checkDuplicateTask(sn_code) }, { name: 'Layer5-操作冲突', fn: () => this.checkOperationConflict(sn_code, taskQueue) } ]; // 逐层检查 for (const check of checks) { const result = await check.fn(); if (!result.allowed) { console.log(`[${this.taskType}] ${check.name} 未通过: ${result.reason}`); return result; } } return { allowed: true }; } /** * 清理任务锁(定期清理过期锁) */ cleanupExpiredLocks() { const now = Date.now(); const timeout = 5 * 60 * 1000; // 5分钟超时 for (const [lockKey, timestamp] of this.taskLocks.entries()) { if (now - timestamp > timeout) { this.taskLocks.delete(lockKey); } } } /** * 获取任务名称(子类可覆盖) */ getTaskName(params) { return `${this.taskType} 任务`; } /** * 验证任务参数(子类必须实现) */ validateParams(params) { throw new Error('子类必须实现 validateParams 方法'); } /** * 执行任务的具体逻辑(子类必须实现) */ async execute(sn_code, params) { throw new Error('子类必须实现 execute 方法'); } } module.exports = BaseTask;