const node_schedule = require("node-schedule"); const config = require('./config.js'); const deviceManager = require('./deviceManager.js'); const command = require('./command.js'); const db = require('../dbProxy'); /** * 定时任务管理器(简化版) * 管理所有定时任务的创建和销毁 */ class ScheduledJobs { constructor(components, taskHandlers) { this.taskQueue = components.taskQueue; this.taskHandlers = taskHandlers; this.jobs = []; } /** * 启动所有定时任务 */ start() { // 每天凌晨重置统计数据 const resetJob = node_schedule.scheduleJob(config.schedules.dailyReset, () => { this.resetDailyStats(); }); this.jobs.push(resetJob); // 启动心跳检查定时任务(每分钟检查一次) const monitoringJob = node_schedule.scheduleJob(config.schedules.monitoringInterval, async () => { await deviceManager.checkHeartbeatStatus().catch(error => { console.error('[定时任务] 检查心跳状态失败:', error); }); }); this.jobs.push(monitoringJob); // 启动离线设备任务清理定时任务(每分钟检查一次) const cleanupOfflineTasksJob = node_schedule.scheduleJob(config.schedules.monitoringInterval, async () => { await this.cleanupOfflineDeviceTasks().catch(error => { console.error('[定时任务] 清理离线设备任务失败:', error); }); }); this.jobs.push(cleanupOfflineTasksJob); console.log('[定时任务] 已启动离线设备任务清理任务'); // 执行自动投递任务 const autoDeliverJob = node_schedule.scheduleJob(config.schedules.autoDeliver, () => { this.autoDeliverTask(); }); this.jobs.push(autoDeliverJob); console.log('[定时任务] 已启动自动投递任务'); } /** * 重置每日统计 */ 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 { const Sequelize = require('sequelize'); const { device_status, task_status, op } = db.models; // 离线阈值:10分钟 const offlineThreshold = 10 * 60 * 1000; // 10分钟 const now = new Date(); const thresholdTime = new Date(now.getTime() - offlineThreshold); // 查询离线超过10分钟的设备 const offlineDevices = await device_status.findAll({ where: { isOnline: false, lastHeartbeatTime: { [op.lt]: thresholdTime } }, attributes: ['sn_code', 'lastHeartbeatTime'] }); if (!offlineDevices || offlineDevices.length === 0) { return; } const offlineSnCodes = offlineDevices.map(dev => dev.sn_code); console.log(`[清理离线任务] 发现 ${offlineSnCodes.length} 个离线超过10分钟的设备: ${offlineSnCodes.join(', ')}`); let totalCancelled = 0; // 为每个离线设备取消任务 for (const sn_code of offlineSnCodes) { try { // 查询该设备的所有pending/running任务 const pendingTasks = await task_status.findAll({ where: { sn_code: sn_code, status: ['pending', 'running'] }, attributes: ['id'] }); if (pendingTasks.length === 0) { continue; } // 更新任务状态为cancelled const updateResult = await task_status.update( { status: 'cancelled', endTime: new Date(), result: JSON.stringify({ reason: '设备离线超过10分钟,任务已自动取消', offlineTime: offlineDevices.find(d => d.sn_code === sn_code)?.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); } console.log(`[清理离线任务] 设备 ${sn_code} 已取消 ${cancelledCount} 个任务`); } catch (error) { console.error(`[清理离线任务] 取消设备 ${sn_code} 的任务失败:`, error); } } if (totalCancelled > 0) { console.log(`[清理离线任务] 共取消 ${totalCancelled} 个离线设备的任务`); } } catch (error) { console.error('[清理离线任务] 执行失败:', error); } } /** * 自动投递任务 */ async autoDeliverTask() { const now = new Date(); console.log(`[自动投递] ${now.toLocaleString()} 开始执行自动投递任务`); // 检查是否在工作时间 if (!config.isWorkingHours()) { console.log(`[自动投递] 非工作时间,跳过执行`); return; } try { // 从 device_status 查询所有在线且已登录的设备 const models = db.models; const { device_status, pla_account, op } = models; const onlineDevices = await device_status.findAll({ where: { isOnline: true, isLoggedIn: true }, attributes: ['sn_code', 'accountName', 'platform'] }); if (!onlineDevices || onlineDevices.length === 0) { console.log('[自动投递] 没有在线且已登录的设备'); return; } // 获取这些在线设备对应的账号配置(只获取启用且开启自动投递的账号) const snCodes = onlineDevices.map(device => device.sn_code); const pla_users = await pla_account.findAll({ where: { sn_code: { [op.in]: snCodes }, is_delete: 0, is_enabled: 1, // 只获取启用的账号 auto_deliver: 1 } }); if (!pla_users || pla_users.length === 0) { console.log('[自动投递] 没有启用且开启自动投递的账号'); return; } console.log(`[自动投递] 找到 ${pla_users.length} 个可用账号`); // 为每个设备添加自动投递任务到队列 for (const pl_user of pla_users) { const userData = pl_user.toJSON(); // 检查设备调度策略 const canExecute = deviceManager.canExecuteOperation(userData.sn_code, 'deliver'); if (!canExecute.allowed) { console.log(`[自动投递] 设备 ${userData.sn_code} 不满足执行条件: ${canExecute.reason}`); continue; } // 添加自动投递任务到队列 await this.taskQueue.addTask(userData.sn_code, { taskType: 'auto_deliver', taskName: `自动投递 - ${userData.keyword || '默认关键词'}`, taskParams: { keyword: userData.keyword || '', platform: userData.platform_type || 'boss', pageCount: 3, // 默认值 maxCount: 10, // 默认值 filterRules: { minSalary: userData.min_salary || 0, maxSalary: userData.max_salary || 0, keywords: [], excludeKeywords: [] } }, priority: config.getTaskPriority('auto_deliver') || 6 }); console.log(`[自动投递] 已为设备 ${userData.sn_code} 添加自动投递任务,关键词: ${userData.keyword || '默认'}`); } console.log('[自动投递] 任务添加完成'); } catch (error) { console.error('[自动投递] 执行失败:', error); } } } module.exports = ScheduledJobs;