1
This commit is contained in:
432
api/middleware/schedule/taskHandlers.js
Normal file
432
api/middleware/schedule/taskHandlers.js
Normal file
@@ -0,0 +1,432 @@
|
||||
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('get_resume', async (task) => {
|
||||
return await this.handleGetResumeTask(task);
|
||||
});
|
||||
|
||||
taskQueue.registerHandler('get_job_list', async (task) => {
|
||||
return await this.handleGetJobListTask(task);
|
||||
});
|
||||
|
||||
taskQueue.registerHandler('send_chat', async (task) => {
|
||||
return await this.handleSendChatTask(task);
|
||||
});
|
||||
|
||||
taskQueue.registerHandler('apply_job', async (task) => {
|
||||
return await this.handleApplyJobTask(task);
|
||||
});
|
||||
|
||||
taskQueue.registerHandler('auto_deliver', async (task) => {
|
||||
return await this.handleAutoDeliverTask(task);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理获取简历任务
|
||||
*/
|
||||
async handleGetResumeTask(task) {
|
||||
const { sn_code } = task;
|
||||
console.log(`[任务处理器] 获取简历任务 - 设备: ${sn_code}`);
|
||||
|
||||
deviceManager.recordTaskStart(sn_code, task);
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const commands = [{
|
||||
command_type: 'getOnlineResume',
|
||||
command_name: '获取在线简历',
|
||||
command_params: JSON.stringify({ sn_code }),
|
||||
priority: config.getTaskPriority('get_resume')
|
||||
}];
|
||||
|
||||
const result = await command.executeCommands(task.id, commands, this.mqttClient);
|
||||
const duration = Date.now() - startTime;
|
||||
deviceManager.recordTaskComplete(sn_code, task, true, duration);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
deviceManager.recordTaskComplete(sn_code, task, false, duration);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理获取岗位列表任务
|
||||
*/
|
||||
async handleGetJobListTask(task) {
|
||||
const { sn_code, taskParams } = task;
|
||||
const { keyword, platform } = taskParams;
|
||||
|
||||
console.log(`[任务处理器] 获取岗位列表任务 - 设备: ${sn_code}`);
|
||||
|
||||
deviceManager.recordTaskStart(sn_code, task);
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const commands = [{
|
||||
command_type: 'getJobList',
|
||||
command_name: '获取岗位列表',
|
||||
command_params: JSON.stringify({ sn_code, keyword, platform }),
|
||||
priority: config.getTaskPriority('search_jobs')
|
||||
}];
|
||||
|
||||
const result = await command.executeCommands(task.id, commands, this.mqttClient);
|
||||
const duration = Date.now() - startTime;
|
||||
deviceManager.recordTaskComplete(sn_code, task, true, duration);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
deviceManager.recordTaskComplete(sn_code, task, false, duration);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理发送聊天任务
|
||||
*/
|
||||
async handleSendChatTask(task) {
|
||||
const { sn_code, taskParams } = task;
|
||||
|
||||
console.log(`[任务处理器] 发送聊天任务 - 设备: ${sn_code}`);
|
||||
|
||||
deviceManager.recordTaskStart(sn_code, task);
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const commands = [{
|
||||
command_type: 'sendChatMessage',
|
||||
command_name: '发送聊天消息',
|
||||
command_params: JSON.stringify(taskParams),
|
||||
priority: config.getTaskPriority('chat')
|
||||
}];
|
||||
|
||||
const result = await command.executeCommands(task.id, commands, this.mqttClient);
|
||||
const duration = Date.now() - startTime;
|
||||
deviceManager.recordTaskComplete(sn_code, task, true, duration);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
deviceManager.recordTaskComplete(sn_code, task, false, duration);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理投递简历任务
|
||||
*/
|
||||
async handleApplyJobTask(task) {
|
||||
const { sn_code, taskParams } = task;
|
||||
|
||||
console.log(`[任务处理器] 投递简历任务 - 设备: ${sn_code}`);
|
||||
|
||||
deviceManager.recordTaskStart(sn_code, task);
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const commands = [{
|
||||
command_type: 'applyJob',
|
||||
command_name: '投递简历',
|
||||
command_params: JSON.stringify(taskParams),
|
||||
priority: config.getTaskPriority('apply')
|
||||
}];
|
||||
|
||||
const result = await command.executeCommands(task.id, commands, this.mqttClient);
|
||||
const duration = Date.now() - startTime;
|
||||
deviceManager.recordTaskComplete(sn_code, task, true, duration);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
deviceManager.recordTaskComplete(sn_code, task, false, duration);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 处理自动投递任务
|
||||
*/
|
||||
|
||||
async handleAutoDeliverTask(task) {
|
||||
const { sn_code, taskParams } = task;
|
||||
const { keyword, platform, pageCount, maxCount } = taskParams;
|
||||
|
||||
console.log(`[任务处理器] 自动投递任务 - 设备: ${sn_code}, 关键词: ${keyword}`);
|
||||
|
||||
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');
|
||||
|
||||
// 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();
|
||||
|
||||
// 获取职位类型配置
|
||||
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: (maxCount || 10) * 3 // 获取更多职位用于筛选
|
||||
});
|
||||
|
||||
if (!pendingJobs || pendingJobs.length === 0) {
|
||||
console.log(`[任务处理器] 没有待投递的职位`);
|
||||
return {
|
||||
success: true,
|
||||
deliveredCount: 0,
|
||||
message: '没有待投递的职位'
|
||||
};
|
||||
}
|
||||
|
||||
// 5. 根据简历信息、职位类型配置和权重配置进行评分和过滤
|
||||
const scoredJobs = [];
|
||||
const excludeKeywords = jobTypeConfig && jobTypeConfig.excludeKeywords
|
||||
? (typeof jobTypeConfig.excludeKeywords === 'string'
|
||||
? JSON.parse(jobTypeConfig.excludeKeywords)
|
||||
: jobTypeConfig.excludeKeywords)
|
||||
: [];
|
||||
|
||||
// 获取一个月内已投递的公司列表(用于过滤)
|
||||
const apply_records = db.getModel('apply_records');
|
||||
const Sequelize = require('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 (Array.isArray(excludeKeywords) && excludeKeywords.length > 0) {
|
||||
const jobText = `${jobData.jobTitle} ${jobData.companyName} ${jobData.jobDescription || ''}`.toLowerCase();
|
||||
const hasExcluded = excludeKeywords.some(kw => jobText.includes(kw.toLowerCase()));
|
||||
if (hasExcluded) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查该公司是否在一个月内已投递过
|
||||
if (jobData.companyName && recentCompanyNames.has(jobData.companyName)) {
|
||||
console.log(`[任务处理器] 跳过一个月内已投递的公司: ${jobData.companyName}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 使用 job_filter_service 计算评分
|
||||
const scoreResult = jobFilterService.calculateJobScoreWithWeights(
|
||||
jobData,
|
||||
resumeInfo,
|
||||
accountConfig,
|
||||
jobTypeConfig,
|
||||
priorityWeights
|
||||
);
|
||||
|
||||
// 只保留总分 >= 60 的职位
|
||||
if (scoreResult.totalScore >= 60) {
|
||||
scoredJobs.push({
|
||||
...jobData,
|
||||
matchScore: scoreResult.totalScore,
|
||||
scoreDetails: scoreResult.scores
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 按总分降序排序
|
||||
scoredJobs.sort((a, b) => b.matchScore - a.matchScore);
|
||||
|
||||
// 取前 maxCount 个职位
|
||||
const jobsToDeliver = scoredJobs.slice(0, maxCount || 10);
|
||||
|
||||
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: 'applyJob',
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TaskHandlers;
|
||||
|
||||
Reference in New Issue
Block a user