249 lines
7.6 KiB
JavaScript
249 lines
7.6 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;
|
||
}
|
||
|
||
/**
|
||
* 检查心跳状态(仅更新内存状态,device_status 表已移除)
|
||
*/
|
||
async checkHeartbeatStatus() {
|
||
try {
|
||
const now = Date.now();
|
||
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) {
|
||
console.log(`[设备管理器] 检测到 ${offlineDevices.length} 个设备心跳超时: ${offlineDevices.join(', ')}`);
|
||
// 注意:device_status 表已移除,设备状态仅在内存中维护
|
||
}
|
||
} 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;
|
||
|