273 lines
11 KiB
JavaScript
273 lines
11 KiB
JavaScript
/**
|
||
* 设备工作状态推送服务
|
||
* 负责向客户端推送设备当前工作状态(任务、指令等)
|
||
*/
|
||
|
||
const db = require('../dbProxy');
|
||
|
||
class DeviceWorkStatusNotifier {
|
||
constructor() {
|
||
this.mqttClient = null;
|
||
}
|
||
|
||
/**
|
||
* 设置 MQTT 客户端
|
||
* @param {object} mqttClient - MQTT 客户端实例
|
||
*/
|
||
setMqttClient(mqttClient) {
|
||
this.mqttClient = mqttClient;
|
||
}
|
||
|
||
/**
|
||
* 获取 MQTT 客户端
|
||
* @returns {Promise<object>} MQTT 客户端实例
|
||
*/
|
||
async getMqttClient() {
|
||
if (this.mqttClient) {
|
||
return this.mqttClient;
|
||
}
|
||
|
||
// 从调度系统获取 MQTT 客户端(与 taskQueue 保持一致)
|
||
try {
|
||
const scheduleManager = require('./index');
|
||
if (scheduleManager.mqttClient) {
|
||
return scheduleManager.mqttClient;
|
||
}
|
||
} catch (error) {
|
||
// 调度系统未初始化
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 发送设备工作状态(统一方法,包含任务和指令状态)
|
||
* @param {string} sn_code - 设备SN码
|
||
* @param {object} taskStatusSummary - 任务状态摘要(从 taskQueue.getTaskStatusSummary 获取)
|
||
* @param {object} options - 可选参数
|
||
* @param {object} options.currentCommand - 当前执行的指令信息(可选)
|
||
*/
|
||
async sendDeviceWorkStatus(sn_code, taskStatusSummary, options = {}) {
|
||
try {
|
||
const mqttClient = await this.getMqttClient();
|
||
if (!mqttClient) {
|
||
return; // MQTT客户端不可用,静默失败
|
||
}
|
||
|
||
// 构建设备工作状态数据
|
||
const workStatus = {
|
||
sn_code: taskStatusSummary.sn_code || sn_code,
|
||
timestamp: new Date().toISOString(),
|
||
|
||
// 当前活动(任务或指令)
|
||
currentActivity: null,
|
||
|
||
// 待执行队列
|
||
pendingQueue: {
|
||
count: taskStatusSummary.pendingCount || 0,
|
||
totalCount: taskStatusSummary.totalPendingCount || 0,
|
||
nextExecuteTime: taskStatusSummary.nextTaskTime ? new Date(taskStatusSummary.nextTaskTime).toISOString() : null,
|
||
tasks: taskStatusSummary.pendingTasks || []
|
||
},
|
||
|
||
// 设备状态(从 deviceManager 获取)
|
||
deviceStatus: {
|
||
isOnline: true, // 默认在线,实际应从 deviceManager 获取
|
||
workMode: 'auto'
|
||
}
|
||
};
|
||
|
||
// 如果有当前执行的指令,优先显示指令状态
|
||
// 优先从 options 中获取,如果没有则从 taskStatusSummary 中获取
|
||
const currentCommand = options.currentCommand || taskStatusSummary.currentCommand;
|
||
if (currentCommand) {
|
||
const cmd = currentCommand;
|
||
workStatus.currentActivity = {
|
||
type: 'command',
|
||
id: cmd.command_id || cmd.id,
|
||
name: cmd.command_name || cmd.name || '执行指令',
|
||
description: this._formatCommandDescription(cmd),
|
||
status: 'running',
|
||
progress: cmd.progress || 0,
|
||
startTime: cmd.startTime || cmd.start_time || new Date().toISOString(),
|
||
commandType: cmd.command_type || cmd.type || '',
|
||
commandParams: cmd.command_params || cmd.params || {}
|
||
};
|
||
}
|
||
// 如果有当前执行的任务,显示任务状态
|
||
else if (taskStatusSummary.currentTask) {
|
||
const task = taskStatusSummary.currentTask;
|
||
workStatus.currentActivity = {
|
||
type: 'task',
|
||
id: task.taskId,
|
||
name: task.taskName || task.taskType || '未知任务',
|
||
description: this._formatTaskDescription(task),
|
||
status: task.status || 'running',
|
||
progress: task.progress || 0,
|
||
currentStep: task.currentStep || '',
|
||
startTime: task.startTime ? new Date(task.startTime).toISOString() : new Date().toISOString()
|
||
};
|
||
}
|
||
|
||
// 格式化显示文案(服务端统一处理,客户端直接显示)
|
||
workStatus.displayText = this._formatDisplayText(workStatus);
|
||
|
||
// 格式化下次执行时间显示文案
|
||
if (workStatus.pendingQueue.nextExecuteTime) {
|
||
const nextTime = new Date(workStatus.pendingQueue.nextExecuteTime);
|
||
const now = new Date();
|
||
const diff = nextTime - now;
|
||
|
||
if (diff < 0) {
|
||
workStatus.pendingQueue.nextExecuteTimeText = '即将执行';
|
||
} else if (diff < 60000) {
|
||
workStatus.pendingQueue.nextExecuteTimeText = `${Math.floor(diff / 1000)}秒后`;
|
||
} else if (diff < 3600000) {
|
||
workStatus.pendingQueue.nextExecuteTimeText = `${Math.floor(diff / 60000)}分钟后`;
|
||
} else if (diff < 86400000) {
|
||
workStatus.pendingQueue.nextExecuteTimeText = `${Math.floor(diff / 3600000)}小时后`;
|
||
} else {
|
||
workStatus.pendingQueue.nextExecuteTimeText = nextTime.toLocaleString('zh-CN', {
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
}
|
||
} else {
|
||
workStatus.pendingQueue.nextExecuteTimeText = '暂无';
|
||
}
|
||
|
||
// 通过MQTT发布设备工作状态
|
||
// 主题格式: device_work_status_{sn_code}
|
||
const topic = `device_work_status_${sn_code}`;
|
||
const message = JSON.stringify({
|
||
action: 'device_work_status',
|
||
data: workStatus,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
|
||
await mqttClient.publish(topic, message);
|
||
|
||
// 输出详细日志
|
||
if (workStatus.currentActivity) {
|
||
const activity = workStatus.currentActivity;
|
||
const activityInfo = activity.type === 'command'
|
||
? `指令[${activity.name}]`
|
||
: `任务[${activity.name}]`;
|
||
console.log(`[设备工作状态] 已推送到 ${sn_code}: ${activityInfo} - ${workStatus.displayText}`);
|
||
} else {
|
||
console.log(`[设备工作状态] 已推送到 ${sn_code}: ${workStatus.displayText}`);
|
||
}
|
||
} catch (error) {
|
||
// 通知失败不影响任务执行,只记录日志
|
||
console.warn(`[设备工作状态] 推送失败:`, error.message);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 格式化任务描述
|
||
* @private
|
||
*/
|
||
_formatTaskDescription(task) {
|
||
if (task.jobTitle || task.companyName) {
|
||
const jobInfo = [];
|
||
if (task.jobTitle) {
|
||
jobInfo.push(task.jobTitle);
|
||
}
|
||
if (task.companyName) {
|
||
const companyName = task.companyName.length > 20
|
||
? task.companyName.substring(0, 20) + '...'
|
||
: task.companyName;
|
||
jobInfo.push(companyName);
|
||
}
|
||
if (jobInfo.length > 0) {
|
||
return `投递职位: ${jobInfo.join(' @ ')}`;
|
||
}
|
||
}
|
||
return task.taskName || task.taskType || '未知任务';
|
||
}
|
||
|
||
/**
|
||
* 格式化指令描述
|
||
* @private
|
||
*/
|
||
_formatCommandDescription(command) {
|
||
const params = command.command_params || command.params || {};
|
||
let parsedParams = {};
|
||
|
||
if (typeof params === 'string') {
|
||
try {
|
||
parsedParams = JSON.parse(params);
|
||
} catch (e) {
|
||
// 解析失败,忽略
|
||
}
|
||
} else {
|
||
parsedParams = params;
|
||
}
|
||
|
||
// 根据指令类型格式化描述
|
||
const commandType = command.command_type || command.type || '';
|
||
const commandName = command.command_name || command.name || '';
|
||
|
||
if (parsedParams.jobTitle && parsedParams.companyName) {
|
||
const companyName = parsedParams.companyName.length > 20
|
||
? parsedParams.companyName.substring(0, 20) + '...'
|
||
: parsedParams.companyName;
|
||
return `投递职位: ${parsedParams.jobTitle} @ ${companyName}`;
|
||
} else if (parsedParams.jobTitle) {
|
||
return `投递职位: ${parsedParams.jobTitle}`;
|
||
} else if (commandType === 'applyJob' || commandName.includes('投递')) {
|
||
return '投递简历';
|
||
} else if (commandType === 'searchJobs' || commandName.includes('搜索')) {
|
||
return `搜索职位: ${parsedParams.keyword || ''}`;
|
||
} else if (commandType === 'sendChatMessage' || commandName.includes('沟通')) {
|
||
return '发送消息';
|
||
} else if (commandName) {
|
||
return commandName;
|
||
}
|
||
return '执行指令';
|
||
}
|
||
|
||
/**
|
||
* 格式化整体显示文案(服务端统一处理,客户端直接显示)
|
||
* @private
|
||
*/
|
||
_formatDisplayText(workStatus) {
|
||
const parts = [];
|
||
|
||
// 当前活动
|
||
if (workStatus.currentActivity) {
|
||
const activity = workStatus.currentActivity;
|
||
const typeText = activity.type === 'command' ? '指令' : '任务';
|
||
const statusText = activity.status === 'running' ? '执行中' :
|
||
activity.status === 'completed' ? '已完成' :
|
||
activity.status === 'failed' ? '失败' : '未知';
|
||
|
||
// 构建详细描述:包含指令/任务名称和描述
|
||
let activityDesc = activity.description || activity.name;
|
||
if (activity.type === 'command' && activity.currentStep) {
|
||
// 对于指令,如果有当前步骤信息,追加到描述中
|
||
activityDesc = `${activityDesc} - ${activity.currentStep}`;
|
||
}
|
||
|
||
parts.push(`${typeText}: ${activityDesc} (${statusText}${activity.progress > 0 ? `, 进度: ${activity.progress}%` : ''})`);
|
||
} else {
|
||
parts.push('当前活动: 无');
|
||
}
|
||
|
||
// 待执行数量
|
||
parts.push(`待执行: ${workStatus.pendingQueue.totalCount}个`);
|
||
|
||
// 下次执行时间
|
||
parts.push(`下次执行: ${workStatus.pendingQueue.nextExecuteTimeText || '暂无'}`);
|
||
|
||
return parts.join(' | ');
|
||
}
|
||
}
|
||
|
||
// 导出单例
|
||
const deviceWorkStatusNotifier = new DeviceWorkStatusNotifier();
|
||
module.exports = deviceWorkStatusNotifier;
|
||
|