288 lines
9.3 KiB
JavaScript
288 lines
9.3 KiB
JavaScript
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, operation_type) {
|
|
// 检查工作时间
|
|
if (!config.isWorkingHours()) {
|
|
return { allowed: false, reason: '不在工作时间内' };
|
|
}
|
|
|
|
// 检查频率限制
|
|
const device = this.devices.get(sn_code);
|
|
if (device) {
|
|
const lastTime = device[`last${operation_type.charAt(0).toUpperCase() + operation_type.slice(1)}`] || 0;
|
|
const interval = config.getRateLimit(operation_type);
|
|
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 = `${operation_type}Count`;
|
|
const current = device.dailyCounts[countKey] || 0;
|
|
const max = config.getDailyLimit(operation_type);
|
|
if (current >= max) {
|
|
return { allowed: false, reason: `今日${operation_type}操作已达上限` };
|
|
}
|
|
}
|
|
|
|
return { allowed: true };
|
|
}
|
|
|
|
/**
|
|
* 记录操作
|
|
*/
|
|
recordOperation(sn_code, operation_type) {
|
|
const device = this.devices.get(sn_code) || {};
|
|
device[`last${operation_type.charAt(0).toUpperCase() + operation_type.slice(1)}`] = Date.now();
|
|
|
|
if (device.dailyCounts) {
|
|
const countKey = `${operation_type}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;
|
|
|