11
This commit is contained in:
118
api/middleware/schedule/infrastructure/ErrorHandler.js
Normal file
118
api/middleware/schedule/infrastructure/ErrorHandler.js
Normal file
@@ -0,0 +1,118 @@
|
||||
const db = require('../dbProxy');
|
||||
|
||||
/**
|
||||
* 统一错误处理模块
|
||||
* 负责错误分类、记录、恢复决策
|
||||
*/
|
||||
class ErrorHandler {
|
||||
/**
|
||||
* 可重试的错误类型
|
||||
*/
|
||||
static RETRYABLE_ERRORS = [
|
||||
'ETIMEDOUT',
|
||||
'ECONNRESET',
|
||||
'ENOTFOUND',
|
||||
'NetworkError',
|
||||
'MQTT客户端未初始化',
|
||||
'设备离线',
|
||||
'超时'
|
||||
];
|
||||
|
||||
/**
|
||||
* 处理错误
|
||||
* @param {Error} error - 错误对象
|
||||
* @param {Object} context - 上下文信息
|
||||
* @returns {Object} 错误处理结果
|
||||
*/
|
||||
static async handleError(error, context = {}) {
|
||||
const errorInfo = {
|
||||
message: error.message || '未知错误',
|
||||
stack: error.stack || '',
|
||||
code: error.code || '',
|
||||
context: {
|
||||
task_id: context.task_id,
|
||||
sn_code: context.sn_code,
|
||||
taskType: context.taskType,
|
||||
...context
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
isRetryable: this.isRetryableError(error)
|
||||
};
|
||||
|
||||
// 记录到日志
|
||||
console.error(`[错误处理] ${errorInfo.message}`, {
|
||||
context: errorInfo.context,
|
||||
isRetryable: errorInfo.isRetryable,
|
||||
stack: errorInfo.stack
|
||||
});
|
||||
|
||||
// 错误信息已通过 console.error 记录到控制台日志
|
||||
|
||||
return errorInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断错误是否可重试
|
||||
* @param {Error} error - 错误对象
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static isRetryableError(error) {
|
||||
if (!error) return false;
|
||||
|
||||
const errorMessage = (error.message || '').toLowerCase();
|
||||
const errorCode = error.code || '';
|
||||
|
||||
// 检查错误代码
|
||||
if (this.RETRYABLE_ERRORS.some(code => errorCode === code)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查错误消息
|
||||
return this.RETRYABLE_ERRORS.some(code =>
|
||||
errorMessage.includes(code.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算重试延迟(指数退避)
|
||||
* @param {number} retryCount - 当前重试次数
|
||||
* @param {number} baseDelay - 基础延迟(毫秒)
|
||||
* @param {number} maxDelay - 最大延迟(毫秒)
|
||||
* @returns {number} 延迟时间(毫秒)
|
||||
*/
|
||||
static calculateRetryDelay(retryCount, baseDelay = 1000, maxDelay = 30000) {
|
||||
const delay = Math.min(baseDelay * Math.pow(2, retryCount - 1), maxDelay);
|
||||
return delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建可重试错误
|
||||
* @param {string} message - 错误消息
|
||||
* @param {Object} context - 上下文
|
||||
* @returns {Error}
|
||||
*/
|
||||
static createRetryableError(message, context = {}) {
|
||||
const error = new Error(message);
|
||||
error.code = 'RETRYABLE';
|
||||
error.context = context;
|
||||
error.isRetryable = true;
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建致命错误
|
||||
* @param {string} message - 错误消息
|
||||
* @param {Object} context - 上下文
|
||||
* @returns {Error}
|
||||
*/
|
||||
static createFatalError(message, context = {}) {
|
||||
const error = new Error(message);
|
||||
error.code = 'FATAL';
|
||||
error.context = context;
|
||||
error.isRetryable = false;
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ErrorHandler;
|
||||
|
||||
215
api/middleware/schedule/infrastructure/PriorityQueue.js
Normal file
215
api/middleware/schedule/infrastructure/PriorityQueue.js
Normal file
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* 优先级队列实现(使用最小堆)
|
||||
* 优先级高的任务(priority值大)会优先出队
|
||||
*/
|
||||
class PriorityQueue {
|
||||
constructor() {
|
||||
this.heap = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取父节点索引
|
||||
*/
|
||||
parent(index) {
|
||||
return Math.floor((index - 1) / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取左子节点索引
|
||||
*/
|
||||
leftChild(index) {
|
||||
return 2 * index + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取右子节点索引
|
||||
*/
|
||||
rightChild(index) {
|
||||
return 2 * index + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 交换两个节点
|
||||
*/
|
||||
swap(i, j) {
|
||||
[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
|
||||
}
|
||||
|
||||
/**
|
||||
* 上浮操作(插入时使用)
|
||||
*/
|
||||
bubbleUp(index) {
|
||||
if (index === 0) return;
|
||||
|
||||
const parentIndex = this.parent(index);
|
||||
const current = this.heap[index];
|
||||
const parent = this.heap[parentIndex];
|
||||
|
||||
// 优先级高的在前(priority值大),如果优先级相同,创建时间早的在前
|
||||
if (
|
||||
current.priority > parent.priority ||
|
||||
(current.priority === parent.priority && current.createdAt < parent.createdAt)
|
||||
) {
|
||||
this.swap(index, parentIndex);
|
||||
this.bubbleUp(parentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下沉操作(删除时使用)
|
||||
*/
|
||||
bubbleDown(index) {
|
||||
const leftIndex = this.leftChild(index);
|
||||
const rightIndex = this.rightChild(index);
|
||||
let largest = index;
|
||||
|
||||
const current = this.heap[index];
|
||||
|
||||
// 比较左子节点
|
||||
if (leftIndex < this.heap.length) {
|
||||
const left = this.heap[leftIndex];
|
||||
if (
|
||||
left.priority > current.priority ||
|
||||
(left.priority === current.priority && left.createdAt < current.createdAt)
|
||||
) {
|
||||
largest = leftIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// 比较右子节点
|
||||
if (rightIndex < this.heap.length) {
|
||||
const right = this.heap[rightIndex];
|
||||
const largestNode = this.heap[largest];
|
||||
if (
|
||||
right.priority > largestNode.priority ||
|
||||
(right.priority === largestNode.priority && right.createdAt < largestNode.createdAt)
|
||||
) {
|
||||
largest = rightIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (largest !== index) {
|
||||
this.swap(index, largest);
|
||||
this.bubbleDown(largest);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
* @param {Object} task - 任务对象,必须包含 priority 和 createdAt 属性
|
||||
*/
|
||||
push(task) {
|
||||
if (!task.hasOwnProperty('priority')) {
|
||||
task.priority = 5; // 默认优先级
|
||||
}
|
||||
if (!task.hasOwnProperty('createdAt')) {
|
||||
task.createdAt = Date.now();
|
||||
}
|
||||
this.heap.push(task);
|
||||
this.bubbleUp(this.heap.length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出优先级最高的任务
|
||||
* @returns {Object|null} 任务对象或null
|
||||
*/
|
||||
pop() {
|
||||
if (this.heap.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.heap.length === 1) {
|
||||
return this.heap.pop();
|
||||
}
|
||||
|
||||
const top = this.heap[0];
|
||||
this.heap[0] = this.heap.pop();
|
||||
this.bubbleDown(0);
|
||||
|
||||
return top;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看优先级最高的任务(不移除)
|
||||
* @returns {Object|null} 任务对象或null
|
||||
*/
|
||||
peek() {
|
||||
return this.heap.length > 0 ? this.heap[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列大小
|
||||
* @returns {number}
|
||||
*/
|
||||
size() {
|
||||
return this.heap.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查队列是否为空
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isEmpty() {
|
||||
return this.heap.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空队列
|
||||
*/
|
||||
clear() {
|
||||
this.heap = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找任务
|
||||
* @param {Function} predicate - 查找条件函数
|
||||
* @returns {Object|null} 任务对象或null
|
||||
*/
|
||||
find(predicate) {
|
||||
return this.heap.find(predicate) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除任务
|
||||
* @param {Function} predicate - 查找条件函数
|
||||
* @returns {boolean} 是否成功移除
|
||||
*/
|
||||
remove(predicate) {
|
||||
const index = this.heap.findIndex(predicate);
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index === this.heap.length - 1) {
|
||||
this.heap.pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 将最后一个元素移到当前位置
|
||||
this.heap[index] = this.heap.pop();
|
||||
|
||||
// 重新调整堆
|
||||
const parentIndex = this.parent(index);
|
||||
if (index > 0 && this.heap[parentIndex] &&
|
||||
(this.heap[index].priority > this.heap[parentIndex].priority ||
|
||||
(this.heap[index].priority === this.heap[parentIndex].priority &&
|
||||
this.heap[index].createdAt < this.heap[parentIndex].createdAt))) {
|
||||
this.bubbleUp(index);
|
||||
} else {
|
||||
this.bubbleDown(index);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为数组(用于调试)
|
||||
* @returns {Array}
|
||||
*/
|
||||
toArray() {
|
||||
return [...this.heap];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PriorityQueue;
|
||||
|
||||
102
api/middleware/schedule/infrastructure/config.js
Normal file
102
api/middleware/schedule/infrastructure/config.js
Normal file
@@ -0,0 +1,102 @@
|
||||
const dayjs = require('dayjs');
|
||||
|
||||
/**
|
||||
* 调度系统配置中心
|
||||
* 统一管理所有配置参数
|
||||
*/
|
||||
class ScheduleConfig {
|
||||
constructor() {
|
||||
|
||||
|
||||
// 单日操作限制
|
||||
this.dailyLimits = {
|
||||
maxSearch: 20, // 每天最多搜索20次
|
||||
maxApply: 50, // 每天最多投递50份简历(默认值)
|
||||
maxChat: 100, // 每天最多发送100条聊天
|
||||
};
|
||||
|
||||
// 平台特定的每日投递限制
|
||||
this.platformDailyLimits = {
|
||||
boss: 150, // Boss直聘:每天最多投递150次
|
||||
liepin: 50 // 猎聘:每天最多投递50次(默认值)
|
||||
};
|
||||
|
||||
// 任务超时配置(毫秒)
|
||||
this.taskTimeouts = {
|
||||
auto_search: 20 * 60 * 1000, // 自动搜索任务:20分钟
|
||||
auto_deliver: 30 * 60 * 1000, // 自动投递任务:30分钟(包含多个子任务)
|
||||
auto_chat: 15 * 60 * 1000, // 自动沟通任务:15分钟
|
||||
auto_active_account: 10 * 60 * 1000 // 自动活跃账号任务:10分钟
|
||||
};
|
||||
|
||||
// 任务优先级配置
|
||||
this.taskPriorities = {
|
||||
auto_search: 8, // 自动搜索任务(最高优先级,先搜索后投递)
|
||||
auto_deliver: 7, // 自动投递任务
|
||||
auto_chat: 6, // 自动沟通任务
|
||||
auto_active_account: 5, // 自动活跃账号任务
|
||||
cleanup: 1
|
||||
};
|
||||
|
||||
// 监控配置
|
||||
this.monitoring = {
|
||||
heartbeatTimeout: 3 * 60 * 1000, // 心跳超时:3分钟
|
||||
offlineThreshold: 24 * 60 * 60 * 1000 // 离线设备清理:24小时
|
||||
};
|
||||
|
||||
// 定时任务配置
|
||||
this.schedules = {
|
||||
dailyReset: '0 0 * * *', // 每天凌晨重置统计
|
||||
monitoringInterval: '*/1 * * * *', // 监控检查间隔:1分钟
|
||||
autoSearch: '0 0 */1 * * *', // 自动搜索任务:每1小时执行一次
|
||||
autoDeliver: '0 */1 * * * *', // 自动投递任务:每1分钟执行一次
|
||||
autoChat: '0 */15 * * * *', // 自动沟通任务:每15分钟执行一次
|
||||
autoActive: '0 0 */2 * * *' // 自动活跃任务:每2小时执行一次
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取任务超时时间
|
||||
* @param {string} taskType - 任务类型
|
||||
* @returns {number} 超时时间(毫秒)
|
||||
*/
|
||||
getTaskTimeout(taskType) {
|
||||
return this.taskTimeouts[taskType] || 30000; // 默认30秒
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务优先级
|
||||
* @param {string} taskType - 任务类型
|
||||
* @param {object} options - 选项
|
||||
* @returns {number} 优先级(1-10)
|
||||
*/
|
||||
getTaskPriority(taskType, options = {}) {
|
||||
let priority = this.taskPriorities[taskType] || 5;
|
||||
|
||||
if (options.urgent) {
|
||||
priority = Math.min(10, priority + 2);
|
||||
}
|
||||
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日限制
|
||||
* @param {string} operation - 操作类型
|
||||
* @param {string} platform - 平台类型(可选,用于平台特定的限制)
|
||||
* @returns {number} 日限制次数
|
||||
*/
|
||||
getDailyLimit(operation, platform = null) {
|
||||
// 如果是投递操作且指定了平台,使用平台特定的限制
|
||||
if (operation === 'apply' && platform && this.platformDailyLimits[platform]) {
|
||||
return this.platformDailyLimits[platform];
|
||||
}
|
||||
// 否则使用通用限制
|
||||
return this.dailyLimits[`max${operation.charAt(0).toUpperCase() + operation.slice(1)}`] || Infinity;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
const scheduleConfig = new ScheduleConfig();
|
||||
module.exports = scheduleConfig;
|
||||
14
api/middleware/schedule/infrastructure/index.js
Normal file
14
api/middleware/schedule/infrastructure/index.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Infrastructure 模块导出
|
||||
* 统一导出基础设施模块
|
||||
*/
|
||||
|
||||
const PriorityQueue = require('./PriorityQueue');
|
||||
const ErrorHandler = require('./ErrorHandler');
|
||||
const config = require('./config');
|
||||
|
||||
module.exports = {
|
||||
PriorityQueue,
|
||||
ErrorHandler,
|
||||
config
|
||||
};
|
||||
Reference in New Issue
Block a user