1
This commit is contained in:
287
api/middleware/schedule/deviceManager.js
Normal file
287
api/middleware/schedule/deviceManager.js
Normal file
@@ -0,0 +1,287 @@
|
||||
const dayjs = require('dayjs');
|
||||
const Sequelize = require('sequelize');
|
||||
const db = require('../dbProxy');
|
||||
const config = require('./config');
|
||||
const utils = require('./utils');
|
||||
|
||||
/**
|
||||
* 设备管理器(简化版)
|
||||
* 合并了 Monitor 和 Strategy 的核心功能
|
||||
*/
|
||||
class DeviceManager {
|
||||
constructor() {
|
||||
// 设备状态 { sn_code: { isOnline, lastHeartbeat, lastSearch, lastApply, lastChat, dailyCounts } }
|
||||
this.devices = new Map();
|
||||
|
||||
// 系统统计
|
||||
this.stats = {
|
||||
totalDevices: 0,
|
||||
onlineDevices: 0,
|
||||
totalTasks: 0,
|
||||
completedTasks: 0,
|
||||
failedTasks: 0,
|
||||
startTime: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
async init() {
|
||||
console.log('[设备管理器] 初始化中...');
|
||||
await this.loadStats();
|
||||
console.log('[设备管理器] 初始化完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载统计数据
|
||||
*/
|
||||
async loadStats() {
|
||||
try {
|
||||
const devices = await db.getModel('pla_account').findAll();
|
||||
this.stats.totalDevices = devices.length;
|
||||
|
||||
const completedCount = await db.getModel('task_status').count({
|
||||
where: { status: 'completed' }
|
||||
});
|
||||
const failedCount = await db.getModel('task_status').count({
|
||||
where: { status: 'failed' }
|
||||
});
|
||||
|
||||
this.stats.completedTasks = completedCount;
|
||||
this.stats.failedTasks = failedCount;
|
||||
this.stats.totalTasks = completedCount + failedCount;
|
||||
} catch (error) {
|
||||
console.error('[设备管理器] 加载统计失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录心跳
|
||||
*/
|
||||
async recordHeartbeat(sn_code, heartbeatData = {}) {
|
||||
const now = Date.now();
|
||||
if (!this.devices.has(sn_code)) {
|
||||
this.devices.set(sn_code, {
|
||||
isOnline: true,
|
||||
lastHeartbeat: now,
|
||||
dailyCounts: { date: utils.getTodayString(), searchCount: 0, applyCount: 0, chatCount: 0 }
|
||||
});
|
||||
}
|
||||
|
||||
const device = this.devices.get(sn_code);
|
||||
device.isOnline = true;
|
||||
device.lastHeartbeat = now;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查设备是否在线
|
||||
*/
|
||||
isDeviceOnline(sn_code) {
|
||||
const device = this.devices.get(sn_code);
|
||||
if (!device) return false;
|
||||
|
||||
const elapsed = Date.now() - device.lastHeartbeat;
|
||||
if (elapsed > config.monitoring.heartbeatTimeout) {
|
||||
device.isOnline = false;
|
||||
return false;
|
||||
}
|
||||
return device.isOnline;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以执行操作
|
||||
*/
|
||||
canExecuteOperation(sn_code, operationType) {
|
||||
// 检查工作时间
|
||||
if (!config.isWorkingHours()) {
|
||||
return { allowed: false, reason: '不在工作时间内' };
|
||||
}
|
||||
|
||||
// 检查频率限制
|
||||
const device = this.devices.get(sn_code);
|
||||
if (device) {
|
||||
const lastTime = device[`last${operationType.charAt(0).toUpperCase() + operationType.slice(1)}`] || 0;
|
||||
const interval = config.getRateLimit(operationType);
|
||||
if (Date.now() - lastTime < interval) {
|
||||
return { allowed: false, reason: '操作过于频繁' };
|
||||
}
|
||||
}
|
||||
|
||||
// 检查日限制
|
||||
if (device && device.dailyCounts) {
|
||||
const today = utils.getTodayString();
|
||||
if (device.dailyCounts.date !== today) {
|
||||
device.dailyCounts = { date: today, searchCount: 0, applyCount: 0, chatCount: 0 };
|
||||
}
|
||||
const countKey = `${operationType}Count`;
|
||||
const current = device.dailyCounts[countKey] || 0;
|
||||
const max = config.getDailyLimit(operationType);
|
||||
if (current >= max) {
|
||||
return { allowed: false, reason: `今日${operationType}操作已达上限` };
|
||||
}
|
||||
}
|
||||
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录操作
|
||||
*/
|
||||
recordOperation(sn_code, operationType) {
|
||||
const device = this.devices.get(sn_code) || {};
|
||||
device[`last${operationType.charAt(0).toUpperCase() + operationType.slice(1)}`] = Date.now();
|
||||
|
||||
if (device.dailyCounts) {
|
||||
const countKey = `${operationType}Count`;
|
||||
device.dailyCounts[countKey] = (device.dailyCounts[countKey] || 0) + 1;
|
||||
}
|
||||
|
||||
this.devices.set(sn_code, device);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录任务开始
|
||||
*/
|
||||
recordTaskStart(sn_code, task) {
|
||||
// 简化实现,只记录日志
|
||||
console.log(`[设备管理器] 设备 ${sn_code} 开始执行任务: ${task.taskName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录任务完成
|
||||
*/
|
||||
recordTaskComplete(sn_code, task, success, duration) {
|
||||
if (success) {
|
||||
this.stats.completedTasks++;
|
||||
} else {
|
||||
this.stats.failedTasks++;
|
||||
}
|
||||
this.stats.totalTasks++;
|
||||
console.log(`[设备管理器] 设备 ${sn_code} 任务${success ? '成功' : '失败'}: ${task.taskName} (${duration}ms)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统统计
|
||||
*/
|
||||
getSystemStats() {
|
||||
const onlineCount = Array.from(this.devices.values()).filter(d => d.isOnline).length;
|
||||
return {
|
||||
...this.stats,
|
||||
onlineDevices: onlineCount,
|
||||
uptime: utils.formatDuration(Date.now() - this.stats.startTime.getTime())
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有设备状态
|
||||
*/
|
||||
getAllDevicesStatus() {
|
||||
const result = {};
|
||||
for (const [sn_code, device] of this.devices.entries()) {
|
||||
result[sn_code] = {
|
||||
isOnline: device.isOnline,
|
||||
lastHeartbeat: device.lastHeartbeat,
|
||||
dailyCounts: device.dailyCounts || {}
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查心跳状态(异步更新数据库)
|
||||
*/
|
||||
async checkHeartbeatStatus() {
|
||||
try {
|
||||
const now = Date.now();
|
||||
const device_status = db.getModel('device_status');
|
||||
const offlineDevices = [];
|
||||
|
||||
for (const [sn_code, device] of this.devices.entries()) {
|
||||
if (now - device.lastHeartbeat > config.monitoring.heartbeatTimeout) {
|
||||
// 如果之前是在线状态,现在检测到离线,需要更新数据库
|
||||
if (device.isOnline) {
|
||||
device.isOnline = false;
|
||||
offlineDevices.push(sn_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量更新数据库中的离线设备状态
|
||||
if (offlineDevices.length > 0) {
|
||||
await device_status.update(
|
||||
{ isOnline: false },
|
||||
{
|
||||
where: {
|
||||
sn_code: {
|
||||
[Sequelize.Op.in]: offlineDevices
|
||||
},
|
||||
isOnline: true // 只更新当前在线的设备,避免重复更新
|
||||
}
|
||||
}
|
||||
);
|
||||
console.log(`[设备管理器] 检测到 ${offlineDevices.length} 个设备心跳超时,已同步到数据库: ${offlineDevices.join(', ')}`);
|
||||
}
|
||||
|
||||
// 同时检查数据库中的设备状态(处理内存中没有但数据库中有心跳超时的情况)
|
||||
const heartbeatTimeout = config.monitoring.heartbeatTimeout;
|
||||
const heartbeatThreshold = new Date(now - heartbeatTimeout);
|
||||
|
||||
const timeoutDevices = await device_status.findAll({
|
||||
where: {
|
||||
isOnline: true,
|
||||
lastHeartbeatTime: {
|
||||
[Sequelize.Op.lt]: heartbeatThreshold
|
||||
}
|
||||
},
|
||||
attributes: ['sn_code', 'lastHeartbeatTime']
|
||||
});
|
||||
|
||||
if (timeoutDevices.length > 0) {
|
||||
const timeoutSnCodes = timeoutDevices.map(dev => dev.sn_code);
|
||||
await device_status.update(
|
||||
{ isOnline: false },
|
||||
{
|
||||
where: {
|
||||
sn_code: {
|
||||
[Sequelize.Op.in]: timeoutSnCodes
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
console.log(`[设备管理器] 从数据库检测到 ${timeoutSnCodes.length} 个心跳超时设备,已更新为离线: ${timeoutSnCodes.join(', ')}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[设备管理器] 检查心跳状态失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置所有日计数器
|
||||
*/
|
||||
resetAllDailyCounters() {
|
||||
const today = utils.getTodayString();
|
||||
for (const device of this.devices.values()) {
|
||||
if (device.dailyCounts && device.dailyCounts.date !== today) {
|
||||
device.dailyCounts = { date: today, searchCount: 0, applyCount: 0, chatCount: 0 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理离线设备
|
||||
*/
|
||||
cleanupOfflineDevices(threshold = 3600000) {
|
||||
const now = Date.now();
|
||||
for (const [sn_code, device] of this.devices.entries()) {
|
||||
if (now - device.lastHeartbeat > threshold) {
|
||||
this.devices.delete(sn_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
const deviceManager = new DeviceManager();
|
||||
module.exports = deviceManager;
|
||||
|
||||
Reference in New Issue
Block a user