diff --git a/api/middleware/schedule/config.js b/api/middleware/schedule/config.js index 0ae135e..142e748 100644 --- a/api/middleware/schedule/config.js +++ b/api/middleware/schedule/config.js @@ -57,7 +57,8 @@ class ScheduleConfig { this.schedules = { dailyReset: '0 0 * * *', // 每天凌晨重置统计 monitoringInterval: '*/1 * * * *', // 监控检查间隔:1分钟 - autoDeliver: '0 */1 * * * *' // 自动投递任务:每5分钟执行一次 + autoDeliver: '0 */1 * * * *', // 自动投递任务:每1分钟执行一次 + autoChat: '0 */15 * * * *' // 自动沟通任务:每15分钟执行一次 }; } diff --git a/api/middleware/schedule/scheduledJobs.js b/api/middleware/schedule/scheduledJobs.js index d548f17..d08e9d0 100644 --- a/api/middleware/schedule/scheduledJobs.js +++ b/api/middleware/schedule/scheduledJobs.js @@ -4,6 +4,51 @@ const config = require('./config.js'); const deviceManager = require('./deviceManager.js'); const command = require('./command.js'); const db = require('../dbProxy'); + +/** + * 检查当前时间是否在指定的时间范围内 + * @param {Object} timeRange - 时间范围配置 {start_time: '09:00', end_time: '18:00', workdays_only: 1} + * @returns {Object} {allowed: boolean, reason: string} + */ +function 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(); // 0=周日, 1=周一, ..., 6=周六 + 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: `当前时间 ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')} 不在允许的时间范围内 (${timeRange.start_time} - ${timeRange.end_time})` }; + } + } else { + // 跨天情况:22:00 - 06:00 + if (currentTime < startTime && currentTime >= endTime) { + return { allowed: false, reason: `当前时间 ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')} 不在允许的时间范围内 (${timeRange.start_time} - ${timeRange.end_time})` }; + } + } + + return { allowed: true, reason: '在允许的时间范围内' }; +} /** * 定时任务管理器(简化版) * 管理所有定时任务的创建和销毁 @@ -66,6 +111,17 @@ class ScheduledJobs { this.jobs.push(autoDeliverJob); console.log('[定时任务] 已启动自动投递任务'); + // 执行自动沟通任务 + const autoChatJob = node_schedule.scheduleJob(config.schedules.autoChat || '0 */15 * * * *', () => { + this.autoChatTask(); + }); + + // 立即执行一次自动沟通任务 + this.autoChatTask(); + + this.jobs.push(autoChatJob); + console.log('[定时任务] 已启动自动沟通任务'); + } @@ -356,7 +412,15 @@ class ScheduledJobs { } // 获取投递配置,如果不存在则使用默认值 - const deliver_config = userData.deliver_config || { + let deliver_config = userData.deliver_config; + if (typeof deliver_config === 'string') { + try { + deliver_config = JSON.parse(deliver_config); + } catch (e) { + deliver_config = {}; + } + } + deliver_config = deliver_config || { deliver_interval: 30, min_salary: 0, max_salary: 0, @@ -366,6 +430,15 @@ class ScheduledJobs { exclude_keywords: [] }; + // 检查投递时间范围 + if (deliver_config.time_range) { + const timeCheck = checkTimeRange(deliver_config.time_range); + if (!timeCheck.allowed) { + console.log(`[自动投递] 设备 ${userData.sn_code} ${timeCheck.reason}`); + continue; + } + } + // 检查投递间隔时间 const deliver_interval = deliver_config.deliver_interval || 30; // 默认30分钟 const interval_ms = deliver_interval * 60 * 1000; // 转换为毫秒 @@ -421,6 +494,132 @@ class ScheduledJobs { console.error('[自动投递] 执行失败:', error); } } + + /** + * 自动沟通任务 + */ + async autoChatTask() { + 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_chat: 1 + } + }); + + if (!pla_users || pla_users.length === 0) { + console.log('[自动沟通] 没有启用且开启自动沟通的账号'); + return; + } + + console.log(`[自动沟通] 找到 ${pla_users.length} 个可用账号`); + + // 获取 task_status 模型用于查询上次沟通时间 + const { task_status } = models; + + // 为每个设备添加自动沟通任务到队列 + for (const pl_user of pla_users) { + const userData = pl_user.toJSON(); + + // 检查设备调度策略 + const canExecute = deviceManager.canExecuteOperation(userData.sn_code, 'chat'); + if (!canExecute.allowed) { + console.log(`[自动沟通] 设备 ${userData.sn_code} 不满足执行条件: ${canExecute.reason}`); + continue; + } + + // 获取沟通策略配置 + let chatStrategy = {}; + if (userData.chat_strategy) { + chatStrategy = typeof userData.chat_strategy === 'string' + ? JSON.parse(userData.chat_strategy) + : userData.chat_strategy; + } + + // 检查沟通时间范围 + if (chatStrategy.time_range) { + const timeCheck = checkTimeRange(chatStrategy.time_range); + if (!timeCheck.allowed) { + console.log(`[自动沟通] 设备 ${userData.sn_code} ${timeCheck.reason}`); + continue; + } + } + + // 检查沟通间隔时间 + const chat_interval = chatStrategy.chat_interval || 30; // 默认30分钟 + const interval_ms = chat_interval * 60 * 1000; // 转换为毫秒 + + // 查询该账号最近一次成功完成的自动沟通任务 + const lastChatTask = await task_status.findOne({ + where: { + sn_code: userData.sn_code, + taskType: 'auto_chat', + status: 'completed' + }, + order: [['endTime', 'DESC']], + attributes: ['endTime'] + }); + + // 如果存在上次沟通记录,检查是否已经过了间隔时间 + if (lastChatTask && lastChatTask.endTime) { + const lastChatTime = new Date(lastChatTask.endTime); + const elapsedTime = now.getTime() - lastChatTime.getTime(); + + if (elapsedTime < interval_ms) { + const remainingMinutes = Math.ceil((interval_ms - elapsedTime) / (60 * 1000)); + console.log(`[自动沟通] 设备 ${userData.sn_code} 距离上次沟通仅 ${Math.round(elapsedTime / (60 * 1000))} 分钟,还需等待 ${remainingMinutes} 分钟(间隔: ${chat_interval} 分钟)`); + continue; + } + } + + // 添加自动沟通任务到队列 + await this.taskQueue.addTask(userData.sn_code, { + taskType: 'auto_chat', + taskName: `自动沟通 - ${userData.name || '默认'}`, + taskParams: { + platform: userData.platform_type || 'boss' + }, + priority: config.getTaskPriority('auto_chat') || 6 + }); + + console.log(`[自动沟通] 已为设备 ${userData.sn_code} 添加自动沟通任务,沟通间隔: ${chat_interval} 分钟`); + } + + console.log('[自动沟通] 任务添加完成'); + + } catch (error) { + console.error('[自动沟通] 执行失败:', error); + } + } } module.exports = ScheduledJobs; diff --git a/api/middleware/schedule/taskHandlers.js b/api/middleware/schedule/taskHandlers.js index f9cad95..7d74df7 100644 --- a/api/middleware/schedule/taskHandlers.js +++ b/api/middleware/schedule/taskHandlers.js @@ -166,6 +166,25 @@ class TaskHandlers { const accountConfig = account.toJSON(); const resumeInfo = resume.toJSON(); + // 检查投递时间范围 + if (accountConfig.deliver_config) { + const deliverConfig = typeof accountConfig.deliver_config === 'string' + ? JSON.parse(accountConfig.deliver_config) + : accountConfig.deliver_config; + + if (deliverConfig.time_range) { + const timeCheck = this.checkTimeRange(deliverConfig.time_range); + if (!timeCheck.allowed) { + console.log(`[任务处理器] 自动投递任务 - ${timeCheck.reason}`); + return { + success: true, + deliveredCount: 0, + message: timeCheck.reason + }; + } + } + } + // 获取职位类型配置 let jobTypeConfig = null; if (accountConfig.job_type_id) { @@ -471,6 +490,51 @@ class TaskHandlers { } } + /** + * 检查当前时间是否在指定的时间范围内 + * @param {Object} timeRange - 时间范围配置 {start_time: '09:00', end_time: '18:00', workdays_only: 1} + * @returns {Object} {allowed: boolean, reason: string} + */ + 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(); // 0=周日, 1=周一, ..., 6=周六 + 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: `当前时间 ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')} 不在允许的时间范围内 (${timeRange.start_time} - ${timeRange.end_time})` }; + } + } else { + // 跨天情况:22:00 - 06:00 + if (currentTime < startTime && currentTime >= endTime) { + return { allowed: false, reason: `当前时间 ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')} 不在允许的时间范围内 (${timeRange.start_time} - ${timeRange.end_time})` }; + } + } + + return { allowed: true, reason: '在允许的时间范围内' }; + } + /** * 处理自动沟通任务(待实现) * 功能:自动与HR进行沟通,回复消息等 @@ -483,6 +547,49 @@ class TaskHandlers { const startTime = Date.now(); try { + // 获取账号配置 + const pla_account = db.getModel('pla_account'); + const account = await pla_account.findOne({ + where: { sn_code: sn_code } + }); + + if (!account) { + throw new Error(`账号不存在: ${sn_code}`); + } + + const accountData = account.toJSON(); + + // 检查是否开启自动沟通 + if (!accountData.auto_chat) { + console.log(`[任务处理器] 设备 ${sn_code} 未开启自动沟通`); + return { + success: true, + message: '未开启自动沟通', + chatCount: 0 + }; + } + + // 解析沟通策略配置 + let chatStrategy = {}; + if (accountData.chat_strategy) { + chatStrategy = typeof accountData.chat_strategy === 'string' + ? JSON.parse(accountData.chat_strategy) + : accountData.chat_strategy; + } + + // 检查沟通时间范围 + if (chatStrategy.time_range) { + const timeCheck = this.checkTimeRange(chatStrategy.time_range); + if (!timeCheck.allowed) { + console.log(`[任务处理器] 自动沟通任务 - ${timeCheck.reason}`); + return { + success: true, + message: timeCheck.reason, + chatCount: 0 + }; + } + } + // TODO: 实现自动沟通逻辑 // 1. 获取待回复的聊天列表 // 2. 根据消息内容生成回复