const db = require('../dbProxy.js'); const config = require('./config.js'); const deviceManager = require('./deviceManager.js'); const command = require('./command.js'); const jobFilterService = require('../job/job_filter_service.js'); /** * 任务处理器(简化版) * 处理各种类型的任务 */ class TaskHandlers { constructor(mqttClient) { this.mqttClient = mqttClient; } /** * 注册任务处理器到任务队列 * @param {object} taskQueue - 任务队列实例 */ register(taskQueue) { // 自动搜索任务 taskQueue.registerHandler('auto_search', async (task) => { return await this.handleAutoSearchTask(task); }); // 自动投递任务 taskQueue.registerHandler('auto_deliver', async (task) => { return await this.handleAutoDeliverTask(task); }); // 搜索职位列表任务(新功能) taskQueue.registerHandler('search_jobs', async (task) => { return await this.handleSearchJobListTask(task); }); // 自动沟通任务 taskQueue.registerHandler('auto_chat', async (task) => { return await this.handleAutoChatTask(task); }); // 自动活跃账号任务 taskQueue.registerHandler('auto_active_account', async (task) => { return await this.handleAutoActiveAccountTask(task); }); } /** * 处理自动搜索任务 */ async handleAutoSearchTask(task) { const { sn_code, taskParams } = task; const { keyword, platform, pageCount } = taskParams; console.log(`[任务处理器] 自动搜索任务 - 设备: ${sn_code}, 关键词: ${keyword}`); // 检查授权状态 const authorizationService = require('../../services/authorization_service'); const authCheck = await authorizationService.checkAuthorization(sn_code, 'sn_code'); if (!authCheck.is_authorized) { console.log(`[任务处理器] 自动搜索任务 - 设备: ${sn_code} 授权检查失败: ${authCheck.message}`); return { success: false, jobsFound: 0, message: authCheck.message }; } deviceManager.recordTaskStart(sn_code, task); const startTime = Date.now(); try { // 构建搜索指令 const searchCommand = { command_type: 'getJobList', command_name: `自动搜索职位 - ${keyword}`, command_params: JSON.stringify({ sn_code: sn_code, keyword: keyword || '', platform: platform || 'boss', pageCount: pageCount || 3 }), priority: config.getTaskPriority('search_jobs') || 8 }; // 执行搜索指令 const result = await command.executeCommands(task.id, [searchCommand], this.mqttClient); const duration = Date.now() - startTime; deviceManager.recordTaskComplete(sn_code, task, true, duration); console.log(`[任务处理器] 自动搜索任务完成 - 设备: ${sn_code}, 耗时: ${duration}ms`); return { success: true, jobsFound: result.jobCount || 0, message: `搜索完成,找到 ${result.jobCount || 0} 个职位` }; } catch (error) { const duration = Date.now() - startTime; deviceManager.recordTaskComplete(sn_code, task, false, duration); console.error(`[任务处理器] 自动搜索任务失败 - 设备: ${sn_code}:`, error); throw error; } } /** * 处理自动投递任务 */ async handleAutoDeliverTask(task) { const { sn_code, taskParams } = task; const { keyword, platform, pageCount, maxCount, filterRules = {} } = taskParams; console.log(`[任务处理器] 自动投递任务 - 设备: ${sn_code}, 关键词: ${keyword}`); // 检查授权状态 const authorizationService = require('../../services/authorization_service'); const authCheck = await authorizationService.checkAuthorization(sn_code, 'sn_code'); if (!authCheck.is_authorized) { console.log(`[任务处理器] 自动投递任务 - 设备: ${sn_code} 授权检查失败: ${authCheck.message}`); return { success: false, deliveredCount: 0, message: authCheck.message }; } deviceManager.recordTaskStart(sn_code, task); const startTime = Date.now(); try { const job_postings = db.getModel('job_postings'); const pla_account = db.getModel('pla_account'); const resume_info = db.getModel('resume_info'); const job_types = db.getModel('job_types'); const apply_records = db.getModel('apply_records'); const Sequelize = require('sequelize'); const { Op } = Sequelize; // 检查今日投递次数限制 const currentPlatform = platform || 'boss'; const dailyLimit = config.getDailyLimit('apply', currentPlatform); // 获取今日开始时间(00:00:00) const today = new Date(); today.setHours(0, 0, 0, 0); // 查询今日已投递次数 const todayApplyCount = await apply_records.count({ where: { sn_code: sn_code, platform: currentPlatform, applyTime: { [Op.gte]: today } } }); console.log(`[任务处理器] 今日已投递 ${todayApplyCount} 次,限制: ${dailyLimit} 次`); // 如果已达到每日投递上限,则跳过 if (todayApplyCount >= dailyLimit) { console.log(`[任务处理器] 已达到每日投递上限(${dailyLimit}次),跳过投递`); return { success: false, deliveredCount: 0, message: `已达到每日投递上限(${dailyLimit}次),今日已投递 ${todayApplyCount} 次` }; } // 计算本次可投递的数量(不超过剩余限额) const remainingQuota = dailyLimit - todayApplyCount; const actualMaxCount = Math.min(maxCount || 10, remainingQuota); if (actualMaxCount < (maxCount || 10)) { console.log(`[任务处理器] 受每日投递上限限制,本次最多投递 ${actualMaxCount} 个职位(剩余限额: ${remainingQuota})`); } // 1. 检查并获取在线简历(如果2小时内没有获取) const twoHoursAgo = new Date(Date.now() - 2 * 60 * 60 * 1000); let resume = await resume_info.findOne({ where: { sn_code, platform: platform || 'boss', isActive: true }, order: [['last_modify_time', 'DESC']] }); const needRefreshResume = !resume || !resume.last_modify_time || new Date(resume.last_modify_time) < twoHoursAgo; if (needRefreshResume) { console.log(`[任务处理器] 简历超过2小时未更新,重新获取在线简历`); try { // 通过 command 系统获取在线简历,而不是直接调用 jobManager const getResumeCommand = { command_type: 'getOnlineResume', command_name: '获取在线简历', command_params: JSON.stringify({ sn_code, platform: platform || 'boss' }), priority: config.getTaskPriority('get_resume') || 5 }; await command.executeCommands(task.id, [getResumeCommand], this.mqttClient); // 重新查询简历 resume = await resume_info.findOne({ where: { sn_code, platform: platform || 'boss', isActive: true }, order: [['last_modify_time', 'DESC']] }); } catch (error) { console.warn(`[任务处理器] 获取在线简历失败,使用已有简历:`, error.message); } } if (!resume) { console.log(`[任务处理器] 未找到简历信息,无法进行自动投递`); return { success: false, deliveredCount: 0, message: '未找到简历信息' }; } // 2. 获取账号配置和职位类型配置 const account = await pla_account.findOne({ where: { sn_code, platform_type: platform || 'boss' } }); if (!account) { console.log(`[任务处理器] 未找到账号配置`); return { success: false, deliveredCount: 0, message: '未找到账号配置' }; } 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) { const jobType = await job_types.findByPk(accountConfig.job_type_id); if (jobType) { jobTypeConfig = jobType.toJSON(); } } // 获取优先级权重配置 let priorityWeights = accountConfig.is_salary_priority; if (!Array.isArray(priorityWeights) || priorityWeights.length === 0) { priorityWeights = [ { key: "distance", weight: 50 }, { key: "salary", weight: 20 }, { key: "work_years", weight: 10 }, { key: "education", weight: 20 } ]; } // 3. 先获取职位列表 const getJobListCommand = { command_type: 'getJobList', command_name: '获取职位列表', command_params: JSON.stringify({ sn_code: sn_code, keyword: keyword || accountConfig.keyword || '', platform: platform || 'boss', pageCount: pageCount || 3 }), priority: config.getTaskPriority('search_jobs') || 5 }; await command.executeCommands(task.id, [getJobListCommand], this.mqttClient); // 4. 从数据库获取待投递的职位 const pendingJobs = await job_postings.findAll({ where: { sn_code: sn_code, platform: platform || 'boss', applyStatus: 'pending' }, order: [['create_time', 'DESC']], limit: actualMaxCount * 3 // 获取更多职位用于筛选(受每日投递上限限制) }); if (!pendingJobs || pendingJobs.length === 0) { console.log(`[任务处理器] 没有待投递的职位`); return { success: true, deliveredCount: 0, message: '没有待投递的职位' }; } // 5. 根据简历信息、职位类型配置和权重配置进行评分和过滤 const scoredJobs = []; // 合并排除关键词:从职位类型配置和任务参数中获取 const jobTypeExcludeKeywords = jobTypeConfig && jobTypeConfig.excludeKeywords ? (typeof jobTypeConfig.excludeKeywords === 'string' ? JSON.parse(jobTypeConfig.excludeKeywords) : jobTypeConfig.excludeKeywords) : []; let taskExcludeKeywords = filterRules.excludeKeywords || []; // 如果 filterRules 中没有,尝试从 accountConfig.deliver_config 获取 if ((!taskExcludeKeywords || taskExcludeKeywords.length === 0) && accountConfig.deliver_config) { const deliverConfig = typeof accountConfig.deliver_config === 'string' ? JSON.parse(accountConfig.deliver_config) : accountConfig.deliver_config; if (deliverConfig.exclude_keywords) { taskExcludeKeywords = Array.isArray(deliverConfig.exclude_keywords) ? deliverConfig.exclude_keywords : (typeof deliverConfig.exclude_keywords === 'string' ? JSON.parse(deliverConfig.exclude_keywords) : []); } } const excludeKeywords = [...jobTypeExcludeKeywords, ...taskExcludeKeywords]; // 获取过滤关键词(用于优先匹配或白名单过滤) let filterKeywords = filterRules.keywords || []; // 如果 filterRules 中没有,尝试从 accountConfig.deliver_config 获取 if ((!filterKeywords || filterKeywords.length === 0) && accountConfig.deliver_config) { const deliverConfig = typeof accountConfig.deliver_config === 'string' ? JSON.parse(accountConfig.deliver_config) : accountConfig.deliver_config; if (deliverConfig.filter_keywords) { filterKeywords = Array.isArray(deliverConfig.filter_keywords) ? deliverConfig.filter_keywords : (typeof deliverConfig.filter_keywords === 'string' ? JSON.parse(deliverConfig.filter_keywords) : []); } } console.log(`[任务处理器] 过滤关键词配置 - 包含关键词: ${JSON.stringify(filterKeywords)}, 排除关键词: ${JSON.stringify(excludeKeywords)}`); // 获取薪资范围过滤(优先从 filterRules,如果没有则从 accountConfig.deliver_config 获取) let minSalary = filterRules.minSalary || 0; let maxSalary = filterRules.maxSalary || 0; // 如果 filterRules 中没有,尝试从 accountConfig.deliver_config 获取 if (minSalary === 0 && maxSalary === 0 && accountConfig.deliver_config) { const deliverConfig = typeof accountConfig.deliver_config === 'string' ? JSON.parse(accountConfig.deliver_config) : accountConfig.deliver_config; minSalary = deliverConfig.min_salary || 0; maxSalary = deliverConfig.max_salary || 0; } console.log(`[任务处理器] 薪资过滤配置 - 最低: ${minSalary}元, 最高: ${maxSalary}元`); // 获取一个月内已投递的公司列表(用于过滤) // 注意:apply_records 和 Sequelize 已在方法开头定义 const oneMonthAgo = new Date(); oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1); const recentApplies = await apply_records.findAll({ where: { sn_code: sn_code, applyTime: { [Sequelize.Op.gte]: oneMonthAgo } }, attributes: ['companyName'], group: ['companyName'] }); const recentCompanyNames = new Set(recentApplies.map(apply => apply.companyName).filter(Boolean)); for (const job of pendingJobs) { const jobData = job.toJSON ? job.toJSON() : job; // 薪资范围过滤 if (minSalary > 0 || maxSalary > 0) { // 解析职位薪资字符串(如 "20-30K") const jobSalaryRange = this.parseSalaryRange(jobData.salary || ''); const jobSalaryMin = jobSalaryRange.min || 0; const jobSalaryMax = jobSalaryRange.max || 0; // 如果职位没有薪资信息,跳过 if (jobSalaryMin === 0 && jobSalaryMax === 0) { console.log(`[任务处理器] 跳过无薪资信息的职位: ${jobData.jobTitle} @ ${jobData.companyName}`); continue; } // 如果职位薪资范围与过滤范围没有交集,则跳过 if (minSalary > 0 && jobSalaryMax > 0 && minSalary > jobSalaryMax) { console.log(`[任务处理器] 跳过薪资过低职位: ${jobData.jobTitle} @ ${jobData.companyName}, 职位薪资: ${jobData.salary}, 要求最低: ${minSalary}`); continue; } if (maxSalary > 0 && jobSalaryMin > 0 && maxSalary < jobSalaryMin) { console.log(`[任务处理器] 跳过薪资过高职位: ${jobData.jobTitle} @ ${jobData.companyName}, 职位薪资: ${jobData.salary}, 要求最高: ${maxSalary}`); continue; } } // 如果配置了简历期望薪资,也要与职位薪资进行比较 if (resumeInfo && resumeInfo.expectedSalary) { const expectedSalaryRange = this.parseExpectedSalary(resumeInfo.expectedSalary); if (expectedSalaryRange) { const jobSalaryRange = this.parseSalaryRange(jobData.salary || ''); const jobSalaryMin = jobSalaryRange.min || 0; const jobSalaryMax = jobSalaryRange.max || 0; // 如果职位薪资明显低于期望薪资范围,跳过 // 期望薪资是 "20-30K",职位薪资应该至少接近或高于期望薪资的最低值 if (jobSalaryMax > 0 && expectedSalaryRange.min > 0 && jobSalaryMax < expectedSalaryRange.min * 0.8) { console.log(`[任务处理器] 跳过薪资低于期望的职位: ${jobData.jobTitle} @ ${jobData.companyName}, 职位薪资: ${jobData.salary}, 期望薪资: ${resumeInfo.expectedSalary}`); continue; } } } // 排除关键词过滤 if (Array.isArray(excludeKeywords) && excludeKeywords.length > 0) { const jobText = `${jobData.jobTitle} ${jobData.companyName} ${jobData.jobDescription || ''}`.toLowerCase(); const matchedExcludeKeywords = excludeKeywords.filter(kw => { const keyword = kw ? kw.toLowerCase().trim() : ''; return keyword && jobText.includes(keyword); }); if (matchedExcludeKeywords.length > 0) { console.log(`[任务处理器] 跳过包含排除关键词的职位: ${jobData.jobTitle} @ ${jobData.companyName}, 匹配: ${matchedExcludeKeywords.join(', ')}`); continue; } } // 过滤关键词(白名单模式):如果设置了过滤关键词,只投递包含这些关键词的职位 if (Array.isArray(filterKeywords) && filterKeywords.length > 0) { const jobText = `${jobData.jobTitle} ${jobData.companyName} ${jobData.jobDescription || ''}`.toLowerCase(); const matchedKeywords = filterKeywords.filter(kw => { const keyword = kw ? kw.toLowerCase().trim() : ''; return keyword && jobText.includes(keyword); }); if (matchedKeywords.length === 0) { // 如果没有匹配到任何过滤关键词,跳过该职位(白名单模式) console.log(`[任务处理器] 跳过未匹配过滤关键词的职位: ${jobData.jobTitle} @ ${jobData.companyName}, 过滤关键词: ${filterKeywords.join(', ')}`); continue; } else { console.log(`[任务处理器] 职位匹配过滤关键词: ${jobData.jobTitle} @ ${jobData.companyName}, 匹配: ${matchedKeywords.join(', ')}`); } } // 检查该公司是否在一个月内已投递过 if (jobData.companyName && recentCompanyNames.has(jobData.companyName)) { console.log(`[任务处理器] 跳过一个月内已投递的公司: ${jobData.companyName}`); continue; } // 使用 job_filter_service 计算评分 const scoreResult = jobFilterService.calculateJobScoreWithWeights( jobData, resumeInfo, accountConfig, jobTypeConfig, priorityWeights ); // 如果配置了过滤关键词,给包含这些关键词的职位加分(额外奖励) let keywordBonus = 0; if (Array.isArray(filterKeywords) && filterKeywords.length > 0) { const jobText = `${jobData.jobTitle} ${jobData.companyName} ${jobData.jobDescription || ''}`.toLowerCase(); const matchedKeywords = filterKeywords.filter(kw => { const keyword = kw ? kw.toLowerCase().trim() : ''; return keyword && jobText.includes(keyword); }); if (matchedKeywords.length > 0) { // 每匹配一个关键词加5分,最多加20分 keywordBonus = Math.min(matchedKeywords.length * 5, 20); } } const finalScore = scoreResult.totalScore + keywordBonus; // 只保留总分 >= 60 的职位 if (finalScore >= 60) { scoredJobs.push({ ...jobData, matchScore: finalScore, scoreDetails: { ...scoreResult.scores, keywordBonus: keywordBonus } }); } } // 按总分降序排序 scoredJobs.sort((a, b) => b.matchScore - a.matchScore); // 取前 actualMaxCount 个职位(受每日投递上限限制) const jobsToDeliver = scoredJobs.slice(0, actualMaxCount); console.log(`[任务处理器] 职位评分完成,共 ${pendingJobs.length} 个职位,评分后 ${scoredJobs.length} 个符合条件,将投递 ${jobsToDeliver.length} 个`); if (jobsToDeliver.length === 0) { return { success: true, deliveredCount: 0, message: '没有符合条件的职位' }; } // 6. 为每个职位创建一条独立的投递指令 const deliverCommands = []; for (const jobData of jobsToDeliver) { console.log(`[任务处理器] 准备投递职位: ${jobData.jobTitle} @ ${jobData.companyName}, 评分: ${jobData.matchScore}`, jobData.scoreDetails); deliverCommands.push({ command_type: 'deliver_resume', // 与MQTT Action保持一致 command_name: `投递简历 - ${jobData.jobTitle} @ ${jobData.companyName} (评分:${jobData.matchScore})`, command_params: JSON.stringify({ sn_code: sn_code, platform: platform || 'boss', jobId: jobData.jobId, encryptBossId: jobData.encryptBossId || '', securityId: jobData.securityId || '', brandName: jobData.companyName, jobTitle: jobData.jobTitle, companyName: jobData.companyName, matchScore: jobData.matchScore, scoreDetails: jobData.scoreDetails }), priority: config.getTaskPriority('apply') || 6 }); } // 7. 执行所有投递指令 const result = await command.executeCommands(task.id, deliverCommands, this.mqttClient); const duration = Date.now() - startTime; deviceManager.recordTaskComplete(sn_code, task, true, duration); console.log(`[任务处理器] 自动投递任务完成 - 设备: ${sn_code}, 创建了 ${deliverCommands.length} 条投递指令, 耗时: ${duration}ms`); return result; } catch (error) { const duration = Date.now() - startTime; deviceManager.recordTaskComplete(sn_code, task, false, duration); console.error(`[任务处理器] 自动投递任务失败 - 设备: ${sn_code}:`, error); throw error; } } /** * 检查当前时间是否在指定的时间范围内 * @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: '在允许的时间范围内' }; } /** * 处理搜索职位列表任务(新功能) * 支持多条件搜索和可选投递 * @param {object} task - 任务对象 * @returns {Promise} 执行结果 */ async handleSearchJobListTask(task) { const { sn_code, taskParams } = task; const { keyword, searchParams = {}, pageCount = 3, autoDeliver = false, filterRules = {}, maxCount = 10 } = taskParams; console.log(`[任务处理器] 搜索职位列表任务 - 设备: ${sn_code}, 关键词: ${keyword}, 自动投递: ${autoDeliver}`); // 检查授权状态 const authorizationService = require('../../services/authorization_service'); const authCheck = await authorizationService.checkAuthorization(sn_code, 'sn_code'); if (!authCheck.is_authorized) { console.log(`[任务处理器] 搜索职位列表任务 - 设备: ${sn_code} 授权检查失败: ${authCheck.message}`); return { success: false, jobCount: 0, deliveredCount: 0, message: authCheck.message }; } deviceManager.recordTaskStart(sn_code, task); const startTime = Date.now(); try { const job_postings = db.getModel('job_postings'); const pla_account = db.getModel('pla_account'); const resume_info = db.getModel('resume_info'); const apply_records = db.getModel('apply_records'); const Sequelize = require('sequelize'); // 1. 获取账号配置 const account = await pla_account.findOne({ where: { sn_code, platform_type: taskParams.platform || 'boss' } }); if (!account) { throw new Error('账号不存在'); } const accountConfig = account.toJSON(); // 2. 从账号配置中读取搜索条件 const searchConfig = accountConfig.search_config ? (typeof accountConfig.search_config === 'string' ? JSON.parse(accountConfig.search_config) : accountConfig.search_config) : {}; // 3. 构建完整的搜索参数(任务参数优先,其次账号配置) const searchCommandParams = { sn_code: sn_code, platform: taskParams.platform || accountConfig.platform_type || 'boss', keyword: keyword || accountConfig.keyword || searchConfig.keyword || '', city: searchParams.city || accountConfig.city || searchConfig.city || '', cityName: searchParams.cityName || accountConfig.cityName || searchConfig.cityName || '', salary: searchParams.salary || searchConfig.defaultSalary || '', experience: searchParams.experience || searchConfig.defaultExperience || '', education: searchParams.education || searchConfig.defaultEducation || '', industry: searchParams.industry || searchConfig.industry || '', companySize: searchParams.companySize || searchConfig.companySize || '', financingStage: searchParams.financingStage || searchConfig.financingStage || '', page: 1, pageSize: 20, pageCount: pageCount }; // 4. 根据是否投递选择不同的指令 let searchCommand; if (autoDeliver) { // 使用搜索并投递指令 searchCommand = { command_type: 'search_and_deliver', command_name: '搜索并投递职位', command_params: JSON.stringify({ keyword: searchCommandParams.keyword, searchParams: { city: searchCommandParams.city, cityName: searchCommandParams.cityName, salary: searchCommandParams.salary, experience: searchCommandParams.experience, education: searchCommandParams.education, industry: searchCommandParams.industry, companySize: searchCommandParams.companySize, financingStage: searchCommandParams.financingStage, page: searchCommandParams.page, pageSize: searchCommandParams.pageSize, pageCount: searchCommandParams.pageCount }, filterRules: filterRules, maxCount: maxCount, platform: searchCommandParams.platform }), priority: config.getTaskPriority('search_and_deliver') || 5, sequence: 1 }; } else { // 使用多条件搜索指令(新的指令类型,使用新的MQTT action) searchCommand = { command_type: 'search_jobs_with_params', // 新的指令类型 command_name: '多条件搜索职位列表', command_params: JSON.stringify(searchCommandParams), // 包含多条件参数 priority: config.getTaskPriority('search_jobs_with_params') || 5, sequence: 1 }; } // 5. 执行指令 const commandResult = await command.executeCommands(task.id, [searchCommand], this.mqttClient); // 6. 处理执行结果 let jobCount = 0; let deliveredCount = 0; if (autoDeliver) { // 如果使用 search_and_deliver 指令,结果中已包含投递信息 if (commandResult && commandResult.results && commandResult.results.length > 0) { const result = commandResult.results[0].result; if (result) { jobCount = result.jobCount || 0; deliveredCount = result.deliveredCount || 0; } } } else { // 如果使用 search_jobs_with_params 指令,等待搜索完成并从数据库获取结果 await new Promise(resolve => setTimeout(resolve, 2000)); const searchedJobs = await job_postings.findAll({ where: { sn_code: sn_code, platform: searchCommandParams.platform, applyStatus: 'pending', keyword: searchCommandParams.keyword }, order: [['create_time', 'DESC']], limit: 1000 }); jobCount = searchedJobs.length; } const duration = Date.now() - startTime; deviceManager.recordTaskComplete(sn_code, task, true, duration); console.log(`[任务处理器] 搜索职位列表任务完成 - 设备: ${sn_code}, 找到 ${jobCount} 个职位, 投递 ${deliveredCount} 个, 耗时: ${duration}ms`); return { success: true, jobCount: jobCount, deliveredCount: deliveredCount, message: autoDeliver ? `搜索完成,找到 ${jobCount} 个职位,成功投递 ${deliveredCount} 个` : `搜索完成,找到 ${jobCount} 个职位` }; } catch (error) { const duration = Date.now() - startTime; deviceManager.recordTaskComplete(sn_code, task, false, duration); console.error(`[任务处理器] 搜索职位列表任务失败 - 设备: ${sn_code}:`, error); throw error; } } /** * 处理自动沟通任务(待实现) * 功能:自动与HR进行沟通,回复消息等 */ async handleAutoChatTask(task) { const { sn_code, taskParams } = task; console.log(`[任务处理器] 自动沟通任务 - 设备: ${sn_code}`); // 检查授权状态 const authorizationService = require('../../services/authorization_service'); const authCheck = await authorizationService.checkAuthorization(sn_code, 'sn_code'); if (!authCheck.is_authorized) { console.log(`[任务处理器] 自动沟通任务 - 设备: ${sn_code} 授权检查失败: ${authCheck.message}`); return { success: false, chatCount: 0, message: authCheck.message }; } deviceManager.recordTaskStart(sn_code, task); 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. 根据消息内容生成回复 // 3. 发送回复消息 // 4. 记录沟通结果 console.log(`[任务处理器] 自动沟通任务 - 逻辑待实现`); const duration = Date.now() - startTime; deviceManager.recordTaskComplete(sn_code, task, true, duration); return { success: true, message: '自动沟通任务框架已就绪,逻辑待实现', chatCount: 0 }; } catch (error) { const duration = Date.now() - startTime; deviceManager.recordTaskComplete(sn_code, task, false, duration); throw error; } } /** * 处理自动活跃账号任务(待实现) * 功能:自动执行一些操作来保持账号活跃度,如浏览职位、搜索等 */ async handleAutoActiveAccountTask(task) { const { sn_code, taskParams } = task; console.log(`[任务处理器] 自动活跃账号任务 - 设备: ${sn_code}`); deviceManager.recordTaskStart(sn_code, task); const startTime = Date.now(); try { // TODO: 实现自动活跃账号逻辑 // 1. 随机搜索一些职位 // 2. 浏览职位详情 // 3. 查看公司信息 // 4. 执行一些模拟用户行为 console.log(`[任务处理器] 自动活跃账号任务 - 逻辑待实现`); const duration = Date.now() - startTime; deviceManager.recordTaskComplete(sn_code, task, true, duration); return { success: true, message: '自动活跃账号任务框架已就绪,逻辑待实现', actionCount: 0 }; } catch (error) { const duration = Date.now() - startTime; deviceManager.recordTaskComplete(sn_code, task, false, duration); throw error; } } /** * 解析职位薪资范围 * @param {string} salaryDesc - 薪资描述(如 "20-30K"、"30-40K·18薪"、"5000-6000元/月") * @returns {object} 薪资范围 { min, max },单位:元 */ parseSalaryRange(salaryDesc) { if (!salaryDesc) return { min: 0, max: 0 }; // 1. 匹配K格式:40-60K, 30-40K·18薪(忽略后面的薪数) const kMatch = salaryDesc.match(/(\d+)[-~](\d+)[kK千]/); if (kMatch) { return { min: parseInt(kMatch[1]) * 1000, max: parseInt(kMatch[2]) * 1000 }; } // 2. 匹配单个K值:25K const singleKMatch = salaryDesc.match(/(\d+)[kK千]/); if (singleKMatch) { const value = parseInt(singleKMatch[1]) * 1000; return { min: value, max: value }; } // 3. 匹配元/月格式:5000-6000元/月 const yuanMatch = salaryDesc.match(/(\d+)[-~](\d+)[元万]/); if (yuanMatch) { const min = parseInt(yuanMatch[1]); const max = parseInt(yuanMatch[2]); // 判断单位(万或元) if (salaryDesc.includes('万')) { return { min: min * 10000, max: max * 10000 }; } else { return { min, max }; } } // 4. 匹配单个元/月值:5000元/月 const singleYuanMatch = salaryDesc.match(/(\d+)[元万]/); if (singleYuanMatch) { const value = parseInt(singleYuanMatch[1]); if (salaryDesc.includes('万')) { return { min: value * 10000, max: value * 10000 }; } else { return { min: value, max: value }; } } // 5. 匹配纯数字格式(如:20000-30000) const numMatch = salaryDesc.match(/(\d+)[-~](\d+)/); if (numMatch) { return { min: parseInt(numMatch[1]), max: parseInt(numMatch[2]) }; } return { min: 0, max: 0 }; } /** * 解析期望薪资范围 * @param {string} expectedSalary - 期望薪资描述(如 "20-30K"、"5000-6000元/月") * @returns {object|null} 期望薪资范围 { min, max },单位:元 */ parseExpectedSalary(expectedSalary) { if (!expectedSalary) return null; // 1. 匹配K格式:20-30K const kMatch = expectedSalary.match(/(\d+)[-~](\d+)[kK千]/); if (kMatch) { return { min: parseInt(kMatch[1]) * 1000, max: parseInt(kMatch[2]) * 1000 }; } // 2. 匹配单个K值:25K const singleKMatch = expectedSalary.match(/(\d+)[kK千]/); if (singleKMatch) { const value = parseInt(singleKMatch[1]) * 1000; return { min: value, max: value }; } // 3. 匹配元/月格式:5000-6000元/月 const yuanMatch = expectedSalary.match(/(\d+)[-~](\d+)[元万]/); if (yuanMatch) { const min = parseInt(yuanMatch[1]); const max = parseInt(yuanMatch[2]); // 判断单位(万或元) if (expectedSalary.includes('万')) { return { min: min * 10000, max: max * 10000 }; } else { return { min, max }; } } // 4. 匹配单个元/月值:5000元/月 const singleYuanMatch = expectedSalary.match(/(\d+)[元万]/); if (singleYuanMatch) { const value = parseInt(singleYuanMatch[1]); if (expectedSalary.includes('万')) { return { min: value * 10000, max: value * 10000 }; } else { return { min: value, max: value }; } } // 5. 匹配纯数字格式(如:20000-30000) const numMatch = expectedSalary.match(/(\d+)[-~](\d+)/); if (numMatch) { return { min: parseInt(numMatch[1]), max: parseInt(numMatch[2]) }; } return null; } } module.exports = TaskHandlers;