Files
autoAiWorkSys/api/middleware/schedule/scheduledJobs.js
张成 2617fa2e5e 1
2025-11-24 13:42:17 +08:00

273 lines
9.9 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 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.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;