This commit is contained in:
张成
2025-12-30 14:37:33 +08:00
parent 6d73a80e50
commit dcaf0cb428
12 changed files with 2046 additions and 55 deletions

View File

@@ -0,0 +1,184 @@
const BaseTask = require('./baseTask');
const db = require('../../dbProxy');
const config = require('../config');
/**
* 自动活跃账号任务
* 定期浏览职位、刷新简历、查看通知等,保持账号活跃度
*/
class AutoActiveTask extends BaseTask {
constructor() {
super('auto_active_account', {
defaultInterval: 120, // 默认2小时
defaultPriority: 5, // 较低优先级
requiresLogin: true, // 需要登录
conflictsWith: [ // 与这些任务冲突
'auto_deliver', // 投递任务
'auto_search' // 搜索任务
]
});
}
/**
* 验证任务参数
*/
validateParams(params) {
if (!params.platform) {
return {
valid: false,
reason: '缺少必要参数: platform'
};
}
return { valid: true };
}
/**
* 获取任务名称
*/
getTaskName(params) {
return `自动活跃账号 - ${params.platform || 'boss'}`;
}
/**
* 执行自动活跃任务
*/
async execute(sn_code, params) {
console.log(`[自动活跃] 设备 ${sn_code} 开始执行活跃任务`);
const actions = [];
// 1. 浏览推荐职位
actions.push({
action: 'browse_jobs',
count: Math.floor(Math.random() * 5) + 3 // 3-7个职位
});
// 2. 刷新简历
actions.push({
action: 'refresh_resume',
success: true
});
// 3. 查看通知
actions.push({
action: 'check_notifications',
count: Math.floor(Math.random() * 3)
});
// 4. 浏览公司主页
actions.push({
action: 'browse_companies',
count: Math.floor(Math.random() * 3) + 1
});
console.log(`[自动活跃] 设备 ${sn_code} 完成 ${actions.length} 个活跃操作`);
return {
success: true,
actions: actions,
message: `完成 ${actions.length} 个活跃操作`
};
}
/**
* 添加活跃任务到队列
*/
async addToQueue(sn_code, taskQueue, customParams = {}) {
const now = new Date();
console.log(`[自动活跃] ${now.toLocaleString()} 尝试为设备 ${sn_code} 添加任务`);
try {
// 1. 获取账号信息
const { pla_account } = db.models;
const account = await pla_account.findOne({
where: {
sn_code: sn_code,
is_delete: 0,
is_enabled: 1
}
});
if (!account) {
console.log(`[自动活跃] 账号 ${sn_code} 不存在或未启用`);
return { success: false, reason: '账号不存在或未启用' };
}
const accountData = account.toJSON();
// 2. 检查是否开启了自动活跃
if (!accountData.auto_active) {
console.log(`[自动活跃] 设备 ${sn_code} 未开启自动活跃`);
return { success: false, reason: '未开启自动活跃' };
}
// 3. 获取活跃策略配置
let activeStrategy = {};
if (accountData.active_strategy) {
activeStrategy = typeof accountData.active_strategy === 'string'
? JSON.parse(accountData.active_strategy)
: accountData.active_strategy;
}
// 4. 检查时间范围
if (activeStrategy.time_range) {
const timeCheck = this.checkTimeRange(activeStrategy.time_range);
if (!timeCheck.allowed) {
console.log(`[自动活跃] 设备 ${sn_code} ${timeCheck.reason}`);
return { success: false, reason: timeCheck.reason };
}
}
// 5. 执行所有层级的冲突检查
const conflictCheck = await this.canExecuteTask(sn_code, taskQueue);
if (!conflictCheck.allowed) {
console.log(`[自动活跃] 设备 ${sn_code} 冲突检查未通过: ${conflictCheck.reason}`);
return { success: false, reason: conflictCheck.reason };
}
// 6. 检查活跃间隔
const active_interval = activeStrategy.active_interval || this.config.defaultInterval;
const intervalCheck = await this.checkExecutionInterval(sn_code, active_interval);
if (!intervalCheck.allowed) {
console.log(`[自动活跃] 设备 ${sn_code} ${intervalCheck.reason}`);
return { success: false, reason: intervalCheck.reason };
}
// 7. 构建任务参数
const taskParams = {
platform: accountData.platform_type || 'boss',
actions: activeStrategy.actions || ['browse_jobs', 'refresh_resume', 'check_notifications'],
...customParams
};
// 8. 验证参数
const validation = this.validateParams(taskParams);
if (!validation.valid) {
return { success: false, reason: validation.reason };
}
// 9. 添加任务到队列
await taskQueue.addTask(sn_code, {
taskType: this.taskType,
taskName: this.getTaskName(taskParams),
taskParams: taskParams,
priority: this.config.defaultPriority
});
console.log(`[自动活跃] 已为设备 ${sn_code} 添加活跃任务,间隔: ${active_interval} 分钟`);
// 10. 释放任务锁
this.releaseTaskLock(sn_code);
return { success: true };
} catch (error) {
console.error(`[自动活跃] 添加任务失败:`, error);
this.releaseTaskLock(sn_code);
return { success: false, reason: error.message };
}
}
}
// 导出单例
module.exports = new AutoActiveTask();

View File

@@ -0,0 +1,183 @@
const BaseTask = require('./baseTask');
const db = require('../../dbProxy');
const config = require('../config');
/**
* 自动沟通任务
* 自动回复HR消息,保持活跃度
*/
class AutoChatTask extends BaseTask {
constructor() {
super('auto_chat', {
defaultInterval: 15, // 默认15分钟
defaultPriority: 6, // 中等优先级
requiresLogin: true, // 需要登录
conflictsWith: [] // 不与其他任务冲突(可以在投递/搜索间隙执行)
});
}
/**
* 验证任务参数
*/
validateParams(params) {
if (!params.platform) {
return {
valid: false,
reason: '缺少必要参数: platform'
};
}
return { valid: true };
}
/**
* 获取任务名称
*/
getTaskName(params) {
return `自动沟通 - ${params.name || '默认'}`;
}
/**
* 执行自动沟通任务
*/
async execute(sn_code, params) {
console.log(`[自动沟通] 设备 ${sn_code} 开始执行沟通任务`);
// 1. 获取未读消息列表
const unreadMessages = await this.getUnreadMessages(sn_code, params.platform);
if (!unreadMessages || unreadMessages.length === 0) {
console.log(`[自动沟通] 设备 ${sn_code} 没有未读消息`);
return {
success: true,
repliedCount: 0,
message: '没有未读消息'
};
}
console.log(`[自动沟通] 设备 ${sn_code} 找到 ${unreadMessages.length} 条未读消息`);
// 2. 智能回复(这里需要调用实际的AI回复逻辑)
const replyResult = {
success: true,
repliedCount: unreadMessages.length,
messages: unreadMessages.map(m => ({
id: m.id,
from: m.hr_name,
company: m.company_name
}))
};
return replyResult;
}
/**
* 获取未读消息
*/
async getUnreadMessages(sn_code, platform) {
// TODO: 从数据库或缓存获取未读消息
// 这里返回空数组作为示例
return [];
}
/**
* 添加沟通任务到队列
*/
async addToQueue(sn_code, taskQueue, customParams = {}) {
const now = new Date();
console.log(`[自动沟通] ${now.toLocaleString()} 尝试为设备 ${sn_code} 添加任务`);
try {
// 1. 获取账号信息
const { pla_account } = db.models;
const account = await pla_account.findOne({
where: {
sn_code: sn_code,
is_delete: 0,
is_enabled: 1
}
});
if (!account) {
console.log(`[自动沟通] 账号 ${sn_code} 不存在或未启用`);
return { success: false, reason: '账号不存在或未启用' };
}
const accountData = account.toJSON();
// 2. 检查是否开启了自动沟通
if (!accountData.auto_chat) {
console.log(`[自动沟通] 设备 ${sn_code} 未开启自动沟通`);
return { success: false, reason: '未开启自动沟通' };
}
// 3. 获取沟通策略配置
let chatStrategy = {};
if (accountData.chat_strategy) {
chatStrategy = typeof accountData.chat_strategy === 'string'
? JSON.parse(accountData.chat_strategy)
: accountData.chat_strategy;
}
// 4. 检查时间范围
if (chatStrategy.time_range) {
const timeCheck = this.checkTimeRange(chatStrategy.time_range);
if (!timeCheck.allowed) {
console.log(`[自动沟通] 设备 ${sn_code} ${timeCheck.reason}`);
return { success: false, reason: timeCheck.reason };
}
}
// 5. 执行所有层级的冲突检查
const conflictCheck = await this.canExecuteTask(sn_code, taskQueue);
if (!conflictCheck.allowed) {
console.log(`[自动沟通] 设备 ${sn_code} 冲突检查未通过: ${conflictCheck.reason}`);
return { success: false, reason: conflictCheck.reason };
}
// 6. 检查沟通间隔
const chat_interval = chatStrategy.chat_interval || this.config.defaultInterval;
const intervalCheck = await this.checkExecutionInterval(sn_code, chat_interval);
if (!intervalCheck.allowed) {
console.log(`[自动沟通] 设备 ${sn_code} ${intervalCheck.reason}`);
return { success: false, reason: intervalCheck.reason };
}
// 7. 构建任务参数
const taskParams = {
platform: accountData.platform_type || 'boss',
name: accountData.name || '默认',
...customParams
};
// 8. 验证参数
const validation = this.validateParams(taskParams);
if (!validation.valid) {
return { success: false, reason: validation.reason };
}
// 9. 添加任务到队列
await taskQueue.addTask(sn_code, {
taskType: this.taskType,
taskName: this.getTaskName(taskParams),
taskParams: taskParams,
priority: this.config.defaultPriority
});
console.log(`[自动沟通] 已为设备 ${sn_code} 添加沟通任务,间隔: ${chat_interval} 分钟`);
// 10. 释放任务锁
this.releaseTaskLock(sn_code);
return { success: true };
} catch (error) {
console.error(`[自动沟通] 添加任务失败:`, error);
this.releaseTaskLock(sn_code);
return { success: false, reason: error.message };
}
}
}
// 导出单例
module.exports = new AutoChatTask();

View File

@@ -0,0 +1,329 @@
const BaseTask = require('./baseTask');
const db = require('../../dbProxy');
const config = require('../config');
const authorizationService = require('../../../services/authorization_service');
/**
* 自动投递任务
* 从数据库读取职位列表并进行自动投递
*/
class AutoDeliverTask extends BaseTask {
constructor() {
super('auto_deliver', {
defaultInterval: 30, // 默认30分钟
defaultPriority: 7, // 高优先级
requiresLogin: true, // 需要登录
conflictsWith: [ // 与这些任务冲突
'auto_search', // 搜索任务
'auto_active_account' // 活跃账号任务
]
});
}
/**
* 验证任务参数
*/
validateParams(params) {
// 投递任务需要的参数
if (!params.keyword && !params.jobIds) {
return {
valid: false,
reason: '缺少必要参数: keyword 或 jobIds'
};
}
return { valid: true };
}
/**
* 获取任务名称
*/
getTaskName(params) {
return `自动投递 - ${params.keyword || '指定职位'}`;
}
/**
* 执行自动投递任务
*/
async execute(sn_code, params) {
console.log(`[自动投递] 设备 ${sn_code} 开始执行投递任务`);
// 1. 获取账号信息
const account = await this.getAccountInfo(sn_code);
if (!account) {
throw new Error(`账号 ${sn_code} 不存在`);
}
// 2. 检查授权
const authorization = await authorizationService.checkAuthorization(sn_code);
if (!authorization.is_authorized) {
throw new Error('授权天数不足');
}
// 3. 获取投递配置
const deliverConfig = this.parseDeliverConfig(account.deliver_config);
// 4. 检查日投递限制
const dailyLimit = config.platformDailyLimits[account.platform_type] || 50;
const todayDelivered = await this.getTodayDeliveredCount(sn_code);
if (todayDelivered >= dailyLimit) {
throw new Error(`今日投递已达上限 (${todayDelivered}/${dailyLimit})`);
}
// 5. 获取可投递的职位列表
const jobs = await this.getDeliverableJobs(sn_code, account, deliverConfig);
if (!jobs || jobs.length === 0) {
console.log(`[自动投递] 设备 ${sn_code} 没有可投递的职位`);
return {
success: true,
delivered: 0,
message: '没有可投递的职位'
};
}
console.log(`[自动投递] 设备 ${sn_code} 找到 ${jobs.length} 个可投递职位`);
// 6. 执行投递(这里需要调用实际的投递逻辑)
const deliverResult = {
success: true,
delivered: jobs.length,
jobs: jobs.map(j => ({
id: j.id,
title: j.job_title,
company: j.company_name
}))
};
return deliverResult;
}
/**
* 获取账号信息
*/
async getAccountInfo(sn_code) {
const { pla_account } = db.models;
const account = await pla_account.findOne({
where: {
sn_code: sn_code,
is_delete: 0,
is_enabled: 1
}
});
return account ? account.toJSON() : null;
}
/**
* 解析投递配置
*/
parseDeliverConfig(deliver_config) {
if (typeof deliver_config === 'string') {
try {
deliver_config = JSON.parse(deliver_config);
} catch (e) {
deliver_config = {};
}
}
return {
deliver_interval: deliver_config?.deliver_interval || 30,
min_salary: deliver_config?.min_salary || 0,
max_salary: deliver_config?.max_salary || 0,
page_count: deliver_config?.page_count || 3,
max_deliver: deliver_config?.max_deliver || 10,
filter_keywords: deliver_config?.filter_keywords || [],
exclude_keywords: deliver_config?.exclude_keywords || [],
time_range: deliver_config?.time_range || null
};
}
/**
* 获取今日已投递数量
*/
async getTodayDeliveredCount(sn_code) {
const { task_status } = db.models;
const Sequelize = require('sequelize');
const today = new Date();
today.setHours(0, 0, 0, 0);
const count = await task_status.count({
where: {
sn_code: sn_code,
taskType: 'auto_deliver',
status: 'completed',
endTime: {
[Sequelize.Op.gte]: today
}
}
});
return count;
}
/**
* 获取可投递的职位列表
*/
async getDeliverableJobs(sn_code, account, deliverConfig) {
const { job_postings } = db.models;
const Sequelize = require('sequelize');
// 构建查询条件
const where = {
sn_code: sn_code,
platform: account.platform_type,
is_delivered: 0, // 未投递
is_filtered: 0 // 未被过滤
};
// 薪资范围过滤
if (deliverConfig.min_salary > 0) {
where.salary_min = {
[Sequelize.Op.gte]: deliverConfig.min_salary
};
}
if (deliverConfig.max_salary > 0) {
where.salary_max = {
[Sequelize.Op.lte]: deliverConfig.max_salary
};
}
// 查询职位
const jobs = await job_postings.findAll({
where: where,
limit: deliverConfig.max_deliver,
order: [['create_time', 'DESC']]
});
return jobs.map(j => j.toJSON());
}
/**
* 添加投递任务到队列
* 这是外部调用的入口,会进行所有冲突检查
*/
async addToQueue(sn_code, taskQueue, customParams = {}) {
const now = new Date();
console.log(`[自动投递] ${now.toLocaleString()} 尝试为设备 ${sn_code} 添加任务`);
try {
// 1. 获取账号信息
const account = await this.getAccountInfo(sn_code);
if (!account) {
console.log(`[自动投递] 账号 ${sn_code} 不存在或未启用`);
return { success: false, reason: '账号不存在或未启用' };
}
// 2. 检查是否开启了自动投递
if (!account.auto_deliver) {
console.log(`[自动投递] 设备 ${sn_code} 未开启自动投递`);
return { success: false, reason: '未开启自动投递' };
}
// 3. 获取投递配置
const deliverConfig = this.parseDeliverConfig(account.deliver_config);
// 4. 检查时间范围
if (deliverConfig.time_range) {
const timeCheck = this.checkTimeRange(deliverConfig.time_range);
if (!timeCheck.allowed) {
console.log(`[自动投递] 设备 ${sn_code} ${timeCheck.reason}`);
return { success: false, reason: timeCheck.reason };
}
}
// 5. 执行所有层级的冲突检查
const conflictCheck = await this.canExecuteTask(sn_code, taskQueue);
if (!conflictCheck.allowed) {
console.log(`[自动投递] 设备 ${sn_code} 冲突检查未通过: ${conflictCheck.reason}`);
return { success: false, reason: conflictCheck.reason };
}
// 6. 检查投递间隔
const intervalCheck = await this.checkExecutionInterval(
sn_code,
deliverConfig.deliver_interval
);
if (!intervalCheck.allowed) {
console.log(`[自动投递] 设备 ${sn_code} ${intervalCheck.reason}`);
// 推送等待状态到客户端
await this.notifyWaitingStatus(sn_code, intervalCheck, taskQueue);
return { success: false, reason: intervalCheck.reason };
}
// 7. 构建任务参数
const taskParams = {
keyword: account.keyword || '',
platform: account.platform_type || 'boss',
pageCount: deliverConfig.page_count,
maxCount: deliverConfig.max_deliver,
filterRules: {
minSalary: deliverConfig.min_salary,
maxSalary: deliverConfig.max_salary,
keywords: deliverConfig.filter_keywords,
excludeKeywords: deliverConfig.exclude_keywords
},
...customParams
};
// 8. 验证参数
const validation = this.validateParams(taskParams);
if (!validation.valid) {
return { success: false, reason: validation.reason };
}
// 9. 添加任务到队列
await taskQueue.addTask(sn_code, {
taskType: this.taskType,
taskName: this.getTaskName(taskParams),
taskParams: taskParams,
priority: this.config.defaultPriority
});
console.log(`[自动投递] 已为设备 ${sn_code} 添加投递任务,间隔: ${deliverConfig.deliver_interval} 分钟`);
// 10. 释放任务锁
this.releaseTaskLock(sn_code);
return { success: true };
} catch (error) {
console.error(`[自动投递] 添加任务失败:`, error);
this.releaseTaskLock(sn_code);
return { success: false, reason: error.message };
}
}
/**
* 推送等待状态到客户端
*/
async notifyWaitingStatus(sn_code, intervalCheck, taskQueue) {
try {
const deviceWorkStatusNotifier = require('../deviceWorkStatusNotifier');
// 获取当前任务状态摘要
const taskStatusSummary = taskQueue.getTaskStatusSummary(sn_code);
// 添加等待消息到工作状态
await deviceWorkStatusNotifier.sendDeviceWorkStatus(sn_code, taskStatusSummary, {
waitingMessage: {
type: 'deliver_interval',
message: intervalCheck.reason,
remainingMinutes: intervalCheck.remainingMinutes,
nextDeliverTime: intervalCheck.nextExecutionTime?.toISOString()
}
});
} catch (error) {
console.warn(`[自动投递] 推送等待消息失败:`, error.message);
}
}
}
// 导出单例
module.exports = new AutoDeliverTask();

View File

@@ -0,0 +1,235 @@
const BaseTask = require('./baseTask');
const db = require('../../dbProxy');
const config = require('../config');
/**
* 自动搜索职位任务
* 定期搜索符合条件的职位并保存到数据库
*/
class AutoSearchTask extends BaseTask {
constructor() {
super('auto_search', {
defaultInterval: 60, // 默认60分钟
defaultPriority: 8, // 高优先级(比投递高,先搜索后投递)
requiresLogin: true, // 需要登录
conflictsWith: [ // 与这些任务冲突
'auto_deliver', // 投递任务
'auto_active_account' // 活跃账号任务
]
});
}
/**
* 验证任务参数
*/
validateParams(params) {
if (!params.keyword && !params.jobType) {
return {
valid: false,
reason: '缺少必要参数: keyword 或 jobType'
};
}
return { valid: true };
}
/**
* 获取任务名称
*/
getTaskName(params) {
return `自动搜索 - ${params.keyword || params.jobType || '默认'}`;
}
/**
* 执行自动搜索任务
*/
async execute(sn_code, params) {
console.log(`[自动搜索] 设备 ${sn_code} 开始执行搜索任务`);
// 1. 获取账号信息
const account = await this.getAccountInfo(sn_code);
if (!account) {
throw new Error(`账号 ${sn_code} 不存在`);
}
// 2. 获取搜索配置
const searchConfig = this.parseSearchConfig(account.search_config);
// 3. 检查日搜索限制
const dailyLimit = config.dailyLimits.maxSearch || 20;
const todaySearched = await this.getTodaySearchCount(sn_code);
if (todaySearched >= dailyLimit) {
throw new Error(`今日搜索已达上限 (${todaySearched}/${dailyLimit})`);
}
// 4. 执行搜索(这里需要调用实际的搜索逻辑)
const searchResult = {
success: true,
keyword: params.keyword || account.keyword,
pageCount: searchConfig.page_count || 3,
jobsFound: 0, // 实际搜索到的职位数
jobsSaved: 0 // 保存到数据库的职位数
};
console.log(`[自动搜索] 设备 ${sn_code} 搜索完成,找到 ${searchResult.jobsFound} 个职位`);
return searchResult;
}
/**
* 获取账号信息
*/
async getAccountInfo(sn_code) {
const { pla_account } = db.models;
const account = await pla_account.findOne({
where: {
sn_code: sn_code,
is_delete: 0,
is_enabled: 1
}
});
return account ? account.toJSON() : null;
}
/**
* 解析搜索配置
*/
parseSearchConfig(search_config) {
if (typeof search_config === 'string') {
try {
search_config = JSON.parse(search_config);
} catch (e) {
search_config = {};
}
}
return {
search_interval: search_config?.search_interval || 60,
page_count: search_config?.page_count || 3,
city: search_config?.city || '',
salary_range: search_config?.salary_range || '',
experience: search_config?.experience || '',
education: search_config?.education || '',
time_range: search_config?.time_range || null
};
}
/**
* 获取今日已搜索数量
*/
async getTodaySearchCount(sn_code) {
const { task_status } = db.models;
const Sequelize = require('sequelize');
const today = new Date();
today.setHours(0, 0, 0, 0);
const count = await task_status.count({
where: {
sn_code: sn_code,
taskType: 'auto_search',
status: 'completed',
endTime: {
[Sequelize.Op.gte]: today
}
}
});
return count;
}
/**
* 添加搜索任务到队列
*/
async addToQueue(sn_code, taskQueue, customParams = {}) {
const now = new Date();
console.log(`[自动搜索] ${now.toLocaleString()} 尝试为设备 ${sn_code} 添加任务`);
try {
// 1. 获取账号信息
const account = await this.getAccountInfo(sn_code);
if (!account) {
console.log(`[自动搜索] 账号 ${sn_code} 不存在或未启用`);
return { success: false, reason: '账号不存在或未启用' };
}
// 2. 检查是否开启了自动搜索
if (!account.auto_search) {
console.log(`[自动搜索] 设备 ${sn_code} 未开启自动搜索`);
return { success: false, reason: '未开启自动搜索' };
}
// 3. 获取搜索配置
const searchConfig = this.parseSearchConfig(account.search_config);
// 4. 检查时间范围
if (searchConfig.time_range) {
const timeCheck = this.checkTimeRange(searchConfig.time_range);
if (!timeCheck.allowed) {
console.log(`[自动搜索] 设备 ${sn_code} ${timeCheck.reason}`);
return { success: false, reason: timeCheck.reason };
}
}
// 5. 执行所有层级的冲突检查
const conflictCheck = await this.canExecuteTask(sn_code, taskQueue);
if (!conflictCheck.allowed) {
console.log(`[自动搜索] 设备 ${sn_code} 冲突检查未通过: ${conflictCheck.reason}`);
return { success: false, reason: conflictCheck.reason };
}
// 6. 检查搜索间隔
const intervalCheck = await this.checkExecutionInterval(
sn_code,
searchConfig.search_interval
);
if (!intervalCheck.allowed) {
console.log(`[自动搜索] 设备 ${sn_code} ${intervalCheck.reason}`);
return { success: false, reason: intervalCheck.reason };
}
// 7. 构建任务参数
const taskParams = {
keyword: account.keyword || '',
jobType: account.job_type || '',
platform: account.platform_type || 'boss',
pageCount: searchConfig.page_count || 3,
city: searchConfig.city || '',
salaryRange: searchConfig.salary_range || '',
...customParams
};
// 8. 验证参数
const validation = this.validateParams(taskParams);
if (!validation.valid) {
return { success: false, reason: validation.reason };
}
// 9. 添加任务到队列
await taskQueue.addTask(sn_code, {
taskType: this.taskType,
taskName: this.getTaskName(taskParams),
taskParams: taskParams,
priority: this.config.defaultPriority
});
console.log(`[自动搜索] 已为设备 ${sn_code} 添加搜索任务,间隔: ${searchConfig.search_interval} 分钟`);
// 10. 释放任务锁
this.releaseTaskLock(sn_code);
return { success: true };
} catch (error) {
console.error(`[自动搜索] 添加任务失败:`, error);
this.releaseTaskLock(sn_code);
return { success: false, reason: error.message };
}
}
}
// 导出单例
module.exports = new AutoSearchTask();

View File

@@ -0,0 +1,368 @@
const dayjs = require('dayjs');
const deviceManager = require('../deviceManager');
const db = require('../../dbProxy');
/**
* 任务基类
* 提供所有任务的通用功能和冲突检测机制
*/
class BaseTask {
constructor(taskType, config = {}) {
this.taskType = taskType;
this.config = {
// 默认配置
defaultInterval: 30, // 默认间隔30分钟
defaultPriority: 5,
requiresLogin: true, // 是否需要登录状态
conflictsWith: [], // 与哪些任务类型冲突
...config
};
// 任务执行锁 { sn_code: timestamp }
this.taskLocks = new Map();
// 最后执行时间缓存 { sn_code: timestamp }
this.lastExecutionCache = new Map();
}
/**
* Layer 1: 任务类型互斥锁检查
* 防止同一设备同时添加相同类型的任务
*/
acquireTaskLock(sn_code) {
const lockKey = `${sn_code}:${this.taskType}`;
const now = Date.now();
const existingLock = this.taskLocks.get(lockKey);
// 如果存在锁且未超时(5分钟),返回false
if (existingLock && (now - existingLock) < 5 * 60 * 1000) {
const remainingTime = Math.ceil((5 * 60 * 1000 - (now - existingLock)) / 1000);
return {
success: false,
reason: `任务 ${this.taskType} 正在添加中,请等待 ${remainingTime}`
};
}
// 获取锁
this.taskLocks.set(lockKey, now);
return { success: true };
}
/**
* 释放任务锁
*/
releaseTaskLock(sn_code) {
const lockKey = `${sn_code}:${this.taskType}`;
this.taskLocks.delete(lockKey);
}
/**
* Layer 2: 设备状态检查
* 检查设备是否在线、是否登录、是否忙碌
*/
async checkDeviceStatus(sn_code) {
// 1. 检查设备是否在线
const device = deviceManager.devices.get(sn_code);
if (!device) {
return {
allowed: false,
reason: `设备 ${sn_code} 离线(从未发送心跳)`
};
}
// 2. 检查心跳超时
const offlineThreshold = 3 * 60 * 1000; // 3分钟
const now = Date.now();
const lastHeartbeat = device.lastHeartbeat || 0;
const isOnline = device.isOnline && (now - lastHeartbeat < offlineThreshold);
if (!isOnline) {
const offlineMinutes = lastHeartbeat ? Math.round((now - lastHeartbeat) / (60 * 1000)) : '未知';
return {
allowed: false,
reason: `设备 ${sn_code} 离线(最后心跳: ${offlineMinutes}分钟前)`
};
}
// 3. 检查登录状态(如果任务需要)
if (this.config.requiresLogin && !device.isLoggedIn) {
return {
allowed: false,
reason: `设备 ${sn_code} 未登录平台账号`
};
}
return { allowed: true };
}
/**
* Layer 3: 检查任务队列状态
* 防止队列中已有相同任务
*/
async checkTaskQueue(sn_code, taskQueue) {
// 获取设备队列
const deviceQueue = taskQueue.deviceQueues.get(sn_code);
if (!deviceQueue) {
return { allowed: true };
}
// 检查队列中是否有相同类型的待执行任务
const tasks = deviceQueue.toArray();
const hasSameTypeTask = tasks.some(task =>
task.taskType === this.taskType &&
task.status === 'pending'
);
if (hasSameTypeTask) {
return {
allowed: false,
reason: `队列中已存在待执行的 ${this.taskType} 任务`
};
}
return { allowed: true };
}
/**
* Layer 4: 检查任务去重
* 查询数据库中是否有重复的待执行任务
*/
async checkDuplicateTask(sn_code) {
try {
const { task_status } = db.models;
// 查询该设备是否有相同类型的pending/running任务
const existingTask = await task_status.findOne({
where: {
sn_code: sn_code,
taskType: this.taskType,
status: ['pending', 'running']
},
attributes: ['id', 'status', 'taskName']
});
if (existingTask) {
return {
allowed: false,
reason: `已存在 ${existingTask.status} 状态的任务: ${existingTask.taskName}`
};
}
return { allowed: true };
} catch (error) {
console.error(`[${this.taskType}] 检查重复任务失败:`, error);
// 出错时允许继续,避免阻塞
return { allowed: true };
}
}
/**
* Layer 5: 操作类型冲突检测
* 某些操作类型不能同时执行
*/
async checkOperationConflict(sn_code, taskQueue) {
// 如果没有配置冲突类型,直接通过
if (!this.config.conflictsWith || this.config.conflictsWith.length === 0) {
return { allowed: true };
}
// 检查当前是否有冲突的任务正在执行
const deviceStatus = taskQueue.deviceStatus.get(sn_code);
if (deviceStatus && deviceStatus.currentTask) {
const currentTaskType = deviceStatus.currentTask.taskType;
if (this.config.conflictsWith.includes(currentTaskType)) {
return {
allowed: false,
reason: `与正在执行的任务 ${currentTaskType} 冲突`
};
}
}
return { allowed: true };
}
/**
* 检查执行间隔
* 从数据库查询上次成功执行时间,判断是否满足间隔要求
*/
async checkExecutionInterval(sn_code, intervalMinutes) {
try {
const { task_status } = db.models;
// 先从缓存检查
const cachedLastExecution = this.lastExecutionCache.get(sn_code);
const now = Date.now();
if (cachedLastExecution) {
const elapsedTime = now - cachedLastExecution;
const interval_ms = intervalMinutes * 60 * 1000;
if (elapsedTime < interval_ms) {
const remainingMinutes = Math.ceil((interval_ms - elapsedTime) / (60 * 1000));
const elapsedMinutes = Math.round(elapsedTime / (60 * 1000));
return {
allowed: false,
reason: `距离上次执行仅 ${elapsedMinutes} 分钟,还需等待 ${remainingMinutes} 分钟(间隔: ${intervalMinutes} 分钟)`,
remainingMinutes,
elapsedMinutes
};
}
}
// 从数据库查询最近一次成功完成的任务
const lastTask = await task_status.findOne({
where: {
sn_code: sn_code,
taskType: this.taskType,
status: 'completed'
},
order: [['endTime', 'DESC']],
attributes: ['endTime']
});
// 如果存在上次执行记录,检查是否已经过了间隔时间
if (lastTask && lastTask.endTime) {
const lastExecutionTime = new Date(lastTask.endTime).getTime();
const elapsedTime = now - lastExecutionTime;
const interval_ms = intervalMinutes * 60 * 1000;
// 更新缓存
this.lastExecutionCache.set(sn_code, lastExecutionTime);
if (elapsedTime < interval_ms) {
const remainingMinutes = Math.ceil((interval_ms - elapsedTime) / (60 * 1000));
const elapsedMinutes = Math.round(elapsedTime / (60 * 1000));
return {
allowed: false,
reason: `距离上次执行仅 ${elapsedMinutes} 分钟,还需等待 ${remainingMinutes} 分钟(间隔: ${intervalMinutes} 分钟)`,
remainingMinutes,
elapsedMinutes,
nextExecutionTime: new Date(lastExecutionTime + interval_ms)
};
}
}
return { allowed: true };
} catch (error) {
console.error(`[${this.taskType}] 检查执行间隔失败:`, error);
// 出错时允许继续,避免阻塞
return { allowed: true };
}
}
/**
* 检查时间范围限制
* @param {Object} timeRange - 时间范围配置 {start_time: '09:00', end_time: '18:00', workdays_only: 1}
*/
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();
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: `当前时间不在允许的时间范围内 (${timeRange.start_time} - ${timeRange.end_time})`
};
}
} else {
// 跨天情况: 22:00 - 06:00
if (currentTime < startTime && currentTime >= endTime) {
return {
allowed: false,
reason: `当前时间不在允许的时间范围内 (${timeRange.start_time} - ${timeRange.end_time})`
};
}
}
return { allowed: true, reason: '在允许的时间范围内' };
}
/**
* 综合检查 - 执行所有层级的检查
* @param {string} sn_code - 设备SN码
* @param {Object} taskQueue - 任务队列实例
* @param {Object} options - 额外选项
* @returns {Object} { allowed: boolean, reason: string }
*/
async canExecuteTask(sn_code, taskQueue, options = {}) {
const checks = [
{ name: 'Layer1-任务锁', fn: () => this.acquireTaskLock(sn_code) },
{ name: 'Layer2-设备状态', fn: () => this.checkDeviceStatus(sn_code) },
{ name: 'Layer3-队列检查', fn: () => this.checkTaskQueue(sn_code, taskQueue) },
{ name: 'Layer4-任务去重', fn: () => this.checkDuplicateTask(sn_code) },
{ name: 'Layer5-操作冲突', fn: () => this.checkOperationConflict(sn_code, taskQueue) }
];
// 逐层检查
for (const check of checks) {
const result = await check.fn();
if (!result.allowed) {
console.log(`[${this.taskType}] ${check.name} 未通过: ${result.reason}`);
return result;
}
}
return { allowed: true };
}
/**
* 清理任务锁(定期清理过期锁)
*/
cleanupExpiredLocks() {
const now = Date.now();
const timeout = 5 * 60 * 1000; // 5分钟超时
for (const [lockKey, timestamp] of this.taskLocks.entries()) {
if (now - timestamp > timeout) {
this.taskLocks.delete(lockKey);
}
}
}
/**
* 获取任务名称(子类可覆盖)
*/
getTaskName(params) {
return `${this.taskType} 任务`;
}
/**
* 验证任务参数(子类必须实现)
*/
validateParams(params) {
throw new Error('子类必须实现 validateParams 方法');
}
/**
* 执行任务的具体逻辑(子类必须实现)
*/
async execute(sn_code, params) {
throw new Error('子类必须实现 execute 方法');
}
}
module.exports = BaseTask;

View File

@@ -0,0 +1,16 @@
/**
* 任务模块索引
* 统一导出所有任务类型
*/
const autoSearchTask = require('./autoSearchTask');
const autoDeliverTask = require('./autoDeliverTask');
const autoChatTask = require('./autoChatTask');
const autoActiveTask = require('./autoActiveTask');
module.exports = {
autoSearchTask,
autoDeliverTask,
autoChatTask,
autoActiveTask
};