From fc134b01087bfdc51ebf723cfab590c9dac548fd Mon Sep 17 00:00:00 2001 From: XuJiacheng Date: Fri, 23 Jan 2026 18:22:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B0=86=E6=89=B9=E9=87=8F=E5=8D=87?= =?UTF-8?q?=E7=BA=A7=E6=94=B9=E4=B8=BA=E9=80=90=E4=B8=AA=E4=B8=BB=E6=9C=BA?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E5=8D=87=E7=BA=A7=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=B6=85=E6=97=B6=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改升级流程,从同时触发所有主机改为逐个主机顺序触发和轮询 - 添加 session_id 字段到 upgrade_log 表以区分不同主机的升级会话 - 引入 upgradePollTimeoutSeconds 配置项控制单个主机轮询超时时间 - 添加数据库迁移脚本以更新表结构和主键约束 - 实现运行组状态跟踪,防止同一组并发执行 - 改进错误处理和日志记录,为每个主机独立记录升级结果 --- scripts/init_db.sql | 1 + scripts/update_db_schema.js | 34 ++++++ src/config.js | 1 + src/loggerService.js | 7 +- src/upgradeController.js | 218 +++++++++++++++++++++--------------- 5 files changed, 167 insertions(+), 94 deletions(-) create mode 100644 scripts/update_db_schema.js diff --git a/scripts/init_db.sql b/scripts/init_db.sql index 6d5866b..e1d39ef 100644 --- a/scripts/init_db.sql +++ b/scripts/init_db.sql @@ -5,6 +5,7 @@ CREATE TABLE IF NOT EXISTS upgrade_log ( uuid UUID NOT NULL, + session_id UUID, start_time TIMESTAMP NOT NULL, roomtype_id INTEGER NOT NULL, host_str TEXT NOT NULL, diff --git a/scripts/update_db_schema.js b/scripts/update_db_schema.js new file mode 100644 index 0000000..32402c4 --- /dev/null +++ b/scripts/update_db_schema.js @@ -0,0 +1,34 @@ +const db = require('../src/db'); + +const updateSchema = async () => { + try { + console.log('Updating database schema...'); + await db.query(` + DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'upgrade_log' AND column_name = 'session_id') THEN + ALTER TABLE upgrade_log ADD COLUMN session_id UUID; + END IF; + END $$; + `); + console.log('Added session_id column.'); + + try { + await db.query(`ALTER TABLE upgrade_log DROP CONSTRAINT IF EXISTS upgrade_log_pkey`); + console.log('Dropped old primary key constraint.'); + } catch (e) { + console.log('Primary key might not exist or different name:', e.message); + } + + await db.query(`ALTER TABLE upgrade_log ADD PRIMARY KEY (uuid)`); + console.log('Set uuid as Primary Key.'); + + console.log('Schema update completed successfully.'); + process.exit(0); + } catch (error) { + console.error('Schema update failed:', error); + process.exit(1); + } +}; + +updateSchema(); diff --git a/src/config.js b/src/config.js index 2090241..7da3082 100644 --- a/src/config.js +++ b/src/config.js @@ -41,5 +41,6 @@ module.exports = { runOnStartup: String(process.env.RUN_ON_STARTUP || 'false').toLowerCase() === 'true', upgradeWaitSeconds: Number(process.env.UPGRADE_WAIT_SECONDS || 45), upgradePollIntervalSeconds: Number(process.env.UPGRADE_POLL_INTERVAL_SECONDS || 45), + upgradePollTimeoutSeconds: Number(process.env.UPGRADE_POLL_TIMEOUT_SECONDS || 300), upgradeConfig: parseUpgradeConfig(process.env.UPGRADE_CONFIG) }; diff --git a/src/loggerService.js b/src/loggerService.js index e0511c0..9424e5e 100644 --- a/src/loggerService.js +++ b/src/loggerService.js @@ -1,13 +1,16 @@ +const { v4: uuidv4 } = require('uuid'); const db = require('./db'); const logHostResult = async (data) => { + const entryId = uuidv4(); const query = ` INSERT INTO upgrade_log ( - uuid, start_time, roomtype_id, host_str, filename, status, + uuid, session_id, start_time, roomtype_id, host_str, filename, status, end_time, file_type, config_version, firmware_version - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) `; const values = [ + entryId, data.uuid, data.start_time, data.roomtype_id, diff --git a/src/upgradeController.js b/src/upgradeController.js index 7ca672c..a306efd 100644 --- a/src/upgradeController.js +++ b/src/upgradeController.js @@ -5,113 +5,147 @@ const config = require('./config'); const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); +const runningGroups = new Map(); + const processGroup = async (group, groupIdx) => { const stateKey = `group_${groupIdx}`; - - let state = await loggerService.getUpgradeState(stateKey); - if (!state) { - state = { current_roomtype_index: 0, execution_count: 0 }; - } - const roomtype = group.roomtypes[state.current_roomtype_index]; - const roomtype_id = roomtype.roomtype_id; - const fileName = roomtype.fileName; - const upgradeCountLimit = roomtype.upgrade_count || 2; - - let nextState = { ...state }; - nextState.execution_count += 1; - if (nextState.execution_count >= upgradeCountLimit) { - nextState.execution_count = 0; - nextState.current_roomtype_index = (state.current_roomtype_index + 1) % group.roomtypes.length; - } - - const sessionUuid = uuidv4(); - const startTime = new Date(); - const hostList = group.hosts; - - console.log(`[${stateKey}] Starting upgrade. Roomtype: ${roomtype_id}. File: ${fileName}. Hosts: ${hostList}`); - - try { - const upgradeRes = await apiClient.triggerUpgrade(roomtype_id, hostList, fileName); - if (!upgradeRes.IsSuccess) { - console.error(`[${stateKey}] Upgrade trigger failed: ${upgradeRes.Message}`); - return; - } - } catch (e) { - console.error(`[${stateKey}] Upgrade trigger error:`, e.message); + if (runningGroups.get(stateKey)) { + console.log(`[${stateKey}] Skipping scheduled run because group is still running.`); return; } - const waitSeconds = config.upgradeWaitSeconds || 45; - const pollIntervalSeconds = config.upgradePollIntervalSeconds || 45; - console.log(`[${stateKey}] Waiting ${waitSeconds}s...`); - await sleep(waitSeconds * 1000); + runningGroups.set(stateKey, true); - const timeout = 5 * 60 * 1000; - const interval = pollIntervalSeconds * 1000; - const pollStartTime = Date.now(); - - const allHosts = new Set(hostList.map(String)); - const lastStatusMap = new Map(); + try { + let state = await loggerService.getUpgradeState(stateKey); + if (!state) { + state = { current_roomtype_index: 0, execution_count: 0 }; + } - while (Date.now() - pollStartTime < timeout) { - try { - const queryRes = await apiClient.queryStatus(hostList); - - if (queryRes && Array.isArray(queryRes.Response)) { - for (const hostStatus of queryRes.Response) { - const hid = String(hostStatus.HostID); - - if (allHosts.has(hid)) { - lastStatusMap.set(hid, { - status: hostStatus.Upgrade_status, - file_type: hostStatus.UpgradeFileType, - config_version: hostStatus.ConfiguraVersion, - firmware_version: hostStatus.Version - }); - } + const roomtype = group.roomtypes[state.current_roomtype_index]; + const roomtype_id = roomtype.roomtype_id; + const fileName = roomtype.fileName; + const upgradeCountLimit = roomtype.upgrade_count || 2; + + let nextState = { ...state }; + nextState.execution_count += 1; + if (nextState.execution_count >= upgradeCountLimit) { + nextState.execution_count = 0; + nextState.current_roomtype_index = (state.current_roomtype_index + 1) % group.roomtypes.length; + } + + const hostQueue = group.hosts.map(String); + console.log(`[${stateKey}] Starting queue. Roomtype: ${roomtype_id}. File: ${fileName}. Hosts: ${hostQueue.join(', ')}`); + + const waitSeconds = config.upgradeWaitSeconds || 45; + const pollIntervalSeconds = config.upgradePollIntervalSeconds || 45; + const pollTimeoutSeconds = config.upgradePollTimeoutSeconds || 300; + + for (const hostId of hostQueue) { + const sessionUuid = uuidv4(); + const startTime = new Date(); + + console.log(`[${stateKey}] Triggering upgrade for host ${hostId}. Roomtype: ${roomtype_id}. File: ${fileName}.`); + + try { + const upgradeRes = await apiClient.triggerUpgrade(roomtype_id, [hostId], fileName); + if (!upgradeRes.IsSuccess) { + console.error(`[${stateKey}] Upgrade trigger failed for host ${hostId}: ${upgradeRes.Message}`); + await loggerService.logHostResult({ + uuid: sessionUuid, + start_time: startTime.toISOString(), + roomtype_id: roomtype_id, + host_str: hostId, + filename: fileName, + status: '触发失败', + end_time: new Date().toISOString(), + file_type: '', + config_version: '', + firmware_version: '' + }); + continue; } + } catch (e) { + console.error(`[${stateKey}] Upgrade trigger error for host ${hostId}:`, e.message); + await loggerService.logHostResult({ + uuid: sessionUuid, + start_time: startTime.toISOString(), + roomtype_id: roomtype_id, + host_str: hostId, + filename: fileName, + status: '触发失败', + end_time: new Date().toISOString(), + file_type: '', + config_version: '', + firmware_version: '' + }); + continue; } - } catch (e) { - console.error(`[${stateKey}] Poll error:`, e.message); - } - if (lastStatusMap.size === allHosts.size) { - const allDone = Array.from(lastStatusMap.values()).every((item) => - item.status === '升级完成' || item.status === '超时失败' - ); - if (allDone) { - console.log(`[${stateKey}] All hosts completed or timeout failed.`); - break; + console.log(`[${stateKey}] Waiting ${waitSeconds}s for host ${hostId}...`); + await sleep(waitSeconds * 1000); + + const pollStartTime = Date.now(); + let completed = false; + + while (Date.now() - pollStartTime < pollTimeoutSeconds * 1000) { + try { + const queryRes = await apiClient.queryStatus([hostId]); + if (queryRes && Array.isArray(queryRes.Response)) { + const hostStatus = queryRes.Response.find((item) => String(item.HostID) === String(hostId)); + if (hostStatus) { + const rawStatus = hostStatus.Upgrade_status || ''; + const status = rawStatus.trim(); + if (['升级完成', '超时失败', '升级失败'].includes(status)) { + await loggerService.logHostResult({ + uuid: sessionUuid, + start_time: startTime.toISOString(), + roomtype_id: roomtype_id, + host_str: hostId, + filename: fileName, + status: status, + end_time: new Date().toISOString(), + file_type: hostStatus.UpgradeFileType, + config_version: hostStatus.ConfiguraVersion, + firmware_version: hostStatus.Version + }); + console.log(`[${stateKey}] Host ${hostId} logged to DB with status: '${status}'.`); + completed = true; + break; + } + } + } else { + console.warn(`[${stateKey}] Invalid query response for host ${hostId}:`, queryRes); + } + } catch (e) { + console.error(`[${stateKey}] Poll error for host ${hostId}:`, e.message); + } + + await sleep(pollIntervalSeconds * 1000); + } + + if (!completed) { + await loggerService.logHostResult({ + uuid: sessionUuid, + start_time: startTime.toISOString(), + roomtype_id: roomtype_id, + host_str: hostId, + filename: fileName, + status: '超时失败', + end_time: new Date().toISOString(), + file_type: 'Unknown', + config_version: '', + firmware_version: '' + }); + console.log(`[${stateKey}] Host ${hostId} timeout failed.`); } } - await sleep(interval); + await loggerService.updateUpgradeState(stateKey, nextState.current_roomtype_index, nextState.execution_count); + } finally { + runningGroups.set(stateKey, false); } - - for (const hid of allHosts) { - const data = lastStatusMap.get(hid) || { - status: '超时失败', - file_type: 'Unknown', - config_version: '', - firmware_version: '' - }; - await loggerService.logHostResult({ - uuid: sessionUuid, - start_time: startTime.toISOString(), - roomtype_id: roomtype_id, - host_str: hid, - filename: fileName, - status: data.status, - end_time: new Date().toISOString(), - file_type: data.file_type, - config_version: data.config_version, - firmware_version: data.firmware_version - }); - console.log(`[${stateKey}] Host ${hid} final status: ${data.status}.`); - } - - await loggerService.updateUpgradeState(stateKey, nextState.current_roomtype_index, nextState.execution_count); }; const run = async () => {