From 8506d974c50ebf91845ba2ceab4d6dbf3c8483e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Mon, 15 Dec 2025 22:10:24 +0800 Subject: [PATCH] 1 --- api/controller_admin/dashboard.js | 100 +++++---- api/controller_admin/device_monitor.js | 255 ++++++++++++----------- api/middleware/schedule/deviceManager.js | 51 +---- api/middleware/schedule/taskQueue.js | 14 +- 4 files changed, 211 insertions(+), 209 deletions(-) diff --git a/api/controller_admin/dashboard.js b/api/controller_admin/dashboard.js index 6efe762..0768b41 100644 --- a/api/controller_admin/dashboard.js +++ b/api/controller_admin/dashboard.js @@ -20,21 +20,20 @@ module.exports = { 'GET /dashboard/overview': async (ctx) => { const models = Framework.getModels(); const { - device_status, + pla_account, task_status, job_postings, apply_records, chat_records, op } = models; + const deviceManager = require('../../middleware/schedule/deviceManager'); - -// 设备统计 -const [totalDevices, onlineDevices, runningDevices] = await Promise.all([ - device_status.count(), - device_status.count({ where: { isOnline: true } }), - device_status.count({ where: { isRunning: true } }) -]); + // 设备统计(从 pla_account 和 deviceManager 获取) + const totalDevices = await pla_account.count({ where: { is_delete: 0 } }); + const deviceStatus = deviceManager.getAllDevicesStatus(); + const onlineDevices = Object.values(deviceStatus).filter(d => d.isOnline).length; + const runningDevices = 0; // 不再维护运行状态 // 任务统计 const [totalTasks, runningTasks, completedTasks, failedTasks] = await Promise.all([ @@ -211,45 +210,58 @@ return ctx.success({ */ 'GET /dashboard/device-performance': async (ctx) => { const models = Framework.getModels(); - const { device_status } = models; + const { pla_account, task_status, job_postings, apply_records, chat_records } = models; + const deviceManager = require('../../middleware/schedule/deviceManager'); - -const devices = await device_status.findAll({ - attributes: [ - 'sn_code', - 'deviceName', - 'totalTasksCompleted', - 'totalTasksFailed', - 'totalJobsSearched', - 'totalApplies', - 'totalChats', - 'healthScore', - 'onlineDuration' - ], - order: [['totalTasksCompleted', 'DESC']], - limit: 20 -}); + // 从 pla_account 获取所有账号 + const accounts = await pla_account.findAll({ + where: { is_delete: 0 }, + attributes: ['id', 'sn_code', 'name'], + limit: 20 + }); -const performanceData = devices.map(device => { - const total = device.totalTasksCompleted + device.totalTasksFailed; - const successRate = total > 0 ? ((device.totalTasksCompleted / total) * 100).toFixed(2) : 0; - - return { - sn_code: device.sn_code, - deviceName: device.deviceName, - tasksCompleted: device.totalTasksCompleted, - tasksFailed: device.totalTasksFailed, - jobsSearched: device.totalJobsSearched, - applies: device.totalApplies, - chats: device.totalChats, - successRate, - healthScore: device.healthScore, - onlineDuration: device.onlineDuration - }; -}); + // 获取设备在线状态 + const deviceStatus = deviceManager.getAllDevicesStatus(); -return ctx.success(performanceData); - + // 为每个账号统计任务、岗位、投递、聊天数据 + const performanceData = await Promise.all(accounts.map(async (account) => { + const snCode = account.sn_code; + const status = deviceStatus[snCode] || { isOnline: false }; + + // 统计任务 + const [completedTasks, failedTasks] = await Promise.all([ + task_status.count({ where: { sn_code: snCode, status: 'completed' } }), + task_status.count({ where: { sn_code: snCode, status: 'failed' } }) + ]); + + // 统计岗位、投递、聊天(如果有相关字段) + const [jobsSearched, applies, chats] = await Promise.all([ + job_postings.count({ where: { sn_code: snCode } }).catch(() => 0), + apply_records.count({ where: { sn_code: snCode } }).catch(() => 0), + chat_records.count({ where: { sn_code: snCode } }).catch(() => 0) + ]); + + const total = completedTasks + failedTasks; + const successRate = total > 0 ? ((completedTasks / total) * 100).toFixed(2) : 0; + + return { + sn_code: snCode, + deviceName: account.name || snCode, + tasksCompleted: completedTasks, + tasksFailed: failedTasks, + jobsSearched, + applies, + chats, + successRate, + healthScore: status.isOnline ? 100 : 0, + onlineDuration: 0 // 不再维护在线时长 + }; + })); + + // 按完成任务数排序 + performanceData.sort((a, b) => b.tasksCompleted - a.tasksCompleted); + + return ctx.success(performanceData); }, /** diff --git a/api/controller_admin/device_monitor.js b/api/controller_admin/device_monitor.js index d9f857d..2d9eeda 100644 --- a/api/controller_admin/device_monitor.js +++ b/api/controller_admin/device_monitor.js @@ -61,7 +61,8 @@ module.exports = { */ 'POST /device/detail': async (ctx) => { const models = Framework.getModels(); - const { device_status } = models; + const { pla_account } = models; + const deviceManager = require('../../middleware/schedule/deviceManager'); const body = ctx.getBody(); const { deviceSn } = body; @@ -69,69 +70,97 @@ module.exports = { return ctx.fail('设备SN码不能为空'); } - const device = await device_status.findOne({ + // 从 pla_account 获取账号信息 + const account = await pla_account.findOne({ where: { sn_code: deviceSn } }); - if (!device) { + if (!account) { return ctx.fail('设备不存在'); } - const deviceData = device.toJSON(); + const accountData = account.toJSON(); - // 处理 JSON 字段 - if (deviceData.config) { - try { - deviceData.config = typeof deviceData.config === 'string' - ? JSON.parse(deviceData.config) - : deviceData.config; - } catch (e) { - console.error('解析设备配置失败:', e); - deviceData.config = {}; - } - } + // 从 deviceManager 获取在线状态 + const deviceStatus = deviceManager.getAllDevicesStatus(); + const onlineStatus = deviceStatus[deviceSn] || { isOnline: false }; + + // 组合返回数据 + const deviceData = { + sn_code: accountData.sn_code, + device_id: accountData.device_id, + deviceName: accountData.name || accountData.sn_code, + platform: accountData.platform_type, + isOnline: onlineStatus.isOnline || false, + isRunning: false, // 不再维护运行状态 + lastHeartbeatTime: onlineStatus.lastHeartbeat ? new Date(onlineStatus.lastHeartbeat) : null, + accountName: accountData.name, + platform_type: accountData.platform_type, + is_enabled: accountData.is_enabled + }; return ctx.success(deviceData); }, 'POST /device/list': async (ctx) => { const models = Framework.getModels(); - const { device_status, op } = models; + const { pla_account, op } = models; + const deviceManager = require('../../middleware/schedule/deviceManager'); const body = ctx.getBody(); const { isOnline, healthStatus, platform, searchText} = ctx.getBody(); // 获取分页参数 const { limit, offset } = ctx.getPageSize(); - -const where = {}; -if (isOnline !== undefined) where.isOnline = isOnline; -if (healthStatus) where.healthStatus = healthStatus; -if (platform) where.platform = platform; + // 从 pla_account 查询账号 + const where = { is_delete: 0 }; + if (platform) where.platform_type = platform; -// 支持搜索设备名称或SN码 -if (searchText) { - where[op.or] = [ - { deviceName: { [op.like]: `%${searchText}%` } }, - { sn_code: { [op.like]: `%${searchText}%` } } - ]; -} + // 支持搜索设备名称或SN码 + if (searchText) { + where[op.or] = [ + { name: { [op.like]: `%${searchText}%` } }, + { sn_code: { [op.like]: `%${searchText}%` } } + ]; + } -const result = await device_status.findAndCountAll({ - where, - limit, - offset, - order: [ - ['isOnline', 'DESC'], - ['last_modify_time', 'DESC'] - ] -}); + const result = await pla_account.findAndCountAll({ + where, + limit, + offset, + order: [['id', 'DESC']] + }); -return ctx.success({ - total: result.count, - list: result.rows -}); - + // 获取设备在线状态 + const deviceStatus = deviceManager.getAllDevicesStatus(); + + // 组合数据并过滤在线状态 + let list = result.rows.map(account => { + const accountData = account.toJSON(); + const status = deviceStatus[accountData.sn_code] || { isOnline: false }; + return { + sn_code: accountData.sn_code, + device_id: accountData.device_id, + deviceName: accountData.name || accountData.sn_code, + platform: accountData.platform_type, + isOnline: status.isOnline || false, + isRunning: false, + lastHeartbeatTime: status.lastHeartbeat ? new Date(status.lastHeartbeat) : null, + accountName: accountData.name, + platform_type: accountData.platform_type, + is_enabled: accountData.is_enabled + }; + }); + + // 如果指定了在线状态筛选 + if (isOnline !== undefined) { + list = list.filter(item => item.isOnline === isOnline); + } + + return ctx.success({ + total: list.length, + list: list.slice(0, limit) + }); }, /** @@ -147,56 +176,46 @@ return ctx.success({ */ 'GET /device/overview': async (ctx) => { const models = Framework.getModels(); - const { device_status, op } = models; + const { pla_account } = models; + const deviceManager = require('../../middleware/schedule/deviceManager'); - -const [ - totalDevices, - onlineDevices, - runningDevices, - healthyDevices, - warningDevices, - errorDevices -] = await Promise.all([ - device_status.count(), - device_status.count({ where: { isOnline: true } }), - device_status.count({ where: { isRunning: true } }), - device_status.count({ where: { healthStatus: 'healthy' } }), - device_status.count({ where: { healthStatus: 'warning' } }), - device_status.count({ where: { healthStatus: 'error' } }) -]); + // 从 pla_account 获取账号总数 + const totalDevices = await pla_account.count({ where: { is_delete: 0 } }); -// 计算平均健康分数 -const avgHealthScore = await device_status.findAll({ - attributes: [ - [models.sequelize.fn('AVG', models.sequelize.col('healthScore')), 'avgScore'] - ], - raw: true -}); + // 从 deviceManager 获取在线设备统计 + const deviceStatus = deviceManager.getAllDevicesStatus(); + const onlineDevices = Object.values(deviceStatus).filter(d => d.isOnline).length; + const runningDevices = 0; // 不再维护运行状态 + const healthyDevices = onlineDevices; // 简化处理,在线即健康 + const warningDevices = 0; + const errorDevices = 0; -// 获取最近离线的设备 -const recentOffline = await device_status.findAll({ - where: { isOnline: false }, - limit: 5, - order: [['lastOfflineTime', 'DESC']], - attributes: ['sn_code', 'deviceName', 'lastOfflineTime', 'lastError'] -}); + // 获取最近离线的设备(从内存状态中获取) + const offlineDevicesList = Object.entries(deviceStatus) + .filter(([sn_code, status]) => !status.isOnline) + .map(([sn_code, status]) => ({ + sn_code, + deviceName: sn_code, + lastOfflineTime: status.lastHeartbeat ? new Date(status.lastHeartbeat) : null, + lastError: '' + })) + .sort((a, b) => (b.lastOfflineTime?.getTime() || 0) - (a.lastOfflineTime?.getTime() || 0)) + .slice(0, 5); -return ctx.success({ - totalDevices, - onlineDevices, - offlineDevices: totalDevices - onlineDevices, - runningDevices, - idleDevices: onlineDevices - runningDevices, - healthyDevices, - warningDevices, - errorDevices, - onlineRate: totalDevices > 0 ? ((onlineDevices / totalDevices) * 100).toFixed(2) : 0, - healthyRate: totalDevices > 0 ? ((healthyDevices / totalDevices) * 100).toFixed(2) : 0, - averageHealthScore: parseFloat(avgHealthScore[0]?.avgScore || 0).toFixed(2), - recentOffline -}); - + return ctx.success({ + totalDevices, + onlineDevices, + offlineDevices: totalDevices - onlineDevices, + runningDevices, + idleDevices: onlineDevices - runningDevices, + healthyDevices, + warningDevices, + errorDevices, + onlineRate: totalDevices > 0 ? ((onlineDevices / totalDevices) * 100).toFixed(2) : 0, + healthyRate: totalDevices > 0 ? ((healthyDevices / totalDevices) * 100).toFixed(2) : 0, + averageHealthScore: '100.00', // 简化处理 + recentOffline: offlineDevicesList + }); }, /** @@ -228,7 +247,7 @@ return ctx.success({ */ 'POST /device/update-config': async (ctx) => { const models = Framework.getModels(); - const { device_status } = models; + const { account_config } = models; const body = ctx.getBody(); const { sn_code, config } = body; @@ -236,9 +255,19 @@ return ctx.success({ return ctx.fail('设备SN码和配置数据不能为空'); } - await device_status.update({ - config: JSON.stringify(config) - }, { where: { sn_code } }); + // 从 pla_account 获取账号ID + const { pla_account } = models; + const account = await pla_account.findOne({ where: { sn_code } }); + if (!account) { + return ctx.fail('设备不存在'); + } + + // 更新 account_config 表的配置 + await account_config.upsert({ + account_id: account.id, + platform_type: account.platform_type, + platform_config: config + }); return ctx.success({ message: '设备配置更新成功' }); }, @@ -267,25 +296,9 @@ return ctx.success({ * description: 重置成功 */ 'POST /device/reset-error': async (ctx) => { - const models = Framework.getModels(); - const { device_status } = models; - const body = ctx.getBody(); - const { sn_code } = body; - - if (!sn_code) { - return ctx.fail('设备SN码不能为空'); - } - - -await device_status.update({ - lastError: '', - errorCount: 0, - healthStatus: 'healthy', - healthScore: 100 -}, { where: { sn_code } }); - -return ctx.success({ message: '设备错误已重置' }); - + // device_status 表已移除,此功能暂时禁用 + // 如果需要重置错误,可以在 account_config 表中添加错误信息字段 + return ctx.success({ message: '设备错误重置功能已禁用(device_status 表已移除)' }); }, /** @@ -312,8 +325,10 @@ return ctx.success({ message: '设备错误已重置' }); * description: 删除成功 */ 'POST /device/delete': async (ctx) => { + // device_status 表已移除,删除设备功能改为删除账号 + // 如果需要删除设备,应该删除对应的 pla_account 记录 const models = Framework.getModels(); - const { device_status } = models; + const { pla_account } = models; const body = ctx.getBody(); const { sn_code } = body; @@ -321,15 +336,17 @@ return ctx.success({ message: '设备错误已重置' }); return ctx.fail('设备SN码不能为空'); } - -const result = await device_status.destroy({ where: { sn_code } }); + // 软删除账号(设置 is_delete = 1) + const result = await pla_account.update( + { is_delete: 1 }, + { where: { sn_code } } + ); -if (result === 0) { - return ctx.fail('设备不存在'); -} + if (result[0] === 0) { + return ctx.fail('设备不存在'); + } -return ctx.success({ message: '设备删除成功' }); - + return ctx.success({ message: '设备删除成功' }); } }; diff --git a/api/middleware/schedule/deviceManager.js b/api/middleware/schedule/deviceManager.js index e13a280..3d59f44 100644 --- a/api/middleware/schedule/deviceManager.js +++ b/api/middleware/schedule/deviceManager.js @@ -189,17 +189,17 @@ class DeviceManager { } /** - * 检查心跳状态(异步更新数据库) + * 检查心跳状态(仅更新内存状态,device_status 表已移除) */ 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); @@ -207,49 +207,10 @@ class DeviceManager { } } - // 批量更新数据库中的离线设备状态 + // 记录离线设备(仅日志,不再更新数据库) 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(', ')}`); + console.log(`[设备管理器] 检测到 ${offlineDevices.length} 个设备心跳超时: ${offlineDevices.join(', ')}`); + // 注意:device_status 表已移除,设备状态仅在内存中维护 } } catch (error) { console.error('[设备管理器] 检查心跳状态失败:', error); diff --git a/api/middleware/schedule/taskQueue.js b/api/middleware/schedule/taskQueue.js index 00a92bc..8130de4 100644 --- a/api/middleware/schedule/taskQueue.js +++ b/api/middleware/schedule/taskQueue.js @@ -219,7 +219,18 @@ class TaskQueue { const enabledSnCodes = new Set(enabledAccounts.map(acc => acc.sn_code)); - // 检查设备在线状态(需要同时满足:isOnline = true 且心跳未超时) + // 移除 device_status 依赖,不再检查设备在线状态 + // 如果需要在线状态检查,可以从 deviceManager 获取 + const deviceManager = require('./deviceManager'); + const deviceStatus = deviceManager.getAllDevicesStatus(); + const onlineSnCodes = new Set( + Object.entries(deviceStatus) + .filter(([sn_code, status]) => status.isOnline) + .map(([sn_code]) => sn_code) + ); + + // 原有代码已移除,改为使用 deviceManager + /* 原有代码已注释 const device_status = db.getModel('device_status'); const heartbeatTimeout = require('./config.js').monitoring.heartbeatTimeout; const now = new Date(); @@ -235,6 +246,7 @@ class TaskQueue { attributes: ['sn_code'] }); const onlineSnCodes = new Set(onlineDevices.map(dev => dev.sn_code)); + */ let processedCount = 0; let queuedCount = 0;