diff --git a/electron/bridges/inspection/index.cjs b/electron/bridges/inspection/index.cjs index c0a117b..ac6fb4e 100644 --- a/electron/bridges/inspection/index.cjs +++ b/electron/bridges/inspection/index.cjs @@ -1462,6 +1462,11 @@ function registerInspectionHandlers(ipcMain, options) { ipcMain.handle("inspection:runReportSnapshot", async (_e, payload) => { const { deviceTargets = [], trigger = "manual" } = payload || {}; + // 防重入:快照正在执行中就拒绝(用户连点按钮或定时 tick 并发) + if (_isSnapshotRunning) { + console.warn("[Inspection:runReportSnapshot] 快照正在执行中,拒绝并发请求"); + return { ok: false, error: "快照正在执行中,请稍候" }; + } _isSnapshotRunning = true; // 广播 running 状态(页面切换后回来的 renderer 也能收到) try { @@ -2075,6 +2080,11 @@ function registerInspectionHandlers(ipcMain, options) { // ─── Electron 启动时自动启动 reportScheduler ─── // 负责:per-device 定时快照 tick(主进程自身 SSH 执行) + dailyReport HH:MM 定时聚合 async function _runScheduledSnapshot(deviceIds, trigger = "scheduled") { + // 与 IPC 手动触发路径互斥:防止定时 tick 和用户手动按钮并发跑快照 + if (_isSnapshotRunning) { + console.warn("[reportScheduler] _isSnapshotRunning=true(可能是手动触发在执行),跳过本次定时快照"); + return; + } if (!_hostConfigs || Object.keys(_hostConfigs).length === 0) { console.warn("[reportScheduler] _hostConfigs 未初始化,定时快照跳过(需先打开监控页面让 renderer 同步 SSH 凭据到主进程)"); return; @@ -2109,10 +2119,13 @@ function registerInspectionHandlers(ipcMain, options) { return; } console.log(`[reportScheduler] 主进程执行定时快照,目标 ${targets.length} 台`); + _isSnapshotRunning = true; try { await _executeSnapshot(targets, trigger); } catch (e) { console.error("[reportScheduler] 定时快照执行失败:", e?.message || String(e)); + } finally { + _isSnapshotRunning = false; } } diff --git a/electron/bridges/inspection/reportScheduler.cjs b/electron/bridges/inspection/reportScheduler.cjs index 7d509a7..8e8f758 100644 --- a/electron/bridges/inspection/reportScheduler.cjs +++ b/electron/bridges/inspection/reportScheduler.cjs @@ -19,6 +19,7 @@ const dailyReportStore = require("./dailyReportStore.cjs"); let _tickTimer = null; let _dailyTimer = null; let _lastDailyTriggerKey = null; // 用于 HH:MM 日报告每日只触发一次 +let _inProgress = false; // 快照执行中锁 —— 防止执行时间 >60s 时被下一个 tick 重入 /** * 启动 reportScheduler @@ -40,6 +41,12 @@ function start({ readStorage, writeStorage, runSnapshotFn, onDailyReportReady, g const cfg = reportScheduleStore.getSchedule(readFn); if (!cfg.enabled) return; + // 防重入:快照正在执行中就跳过(执行时间 >60s 时下一个 tick 会撞到) + if (_inProgress) { + console.log("[reportScheduler] tick: 上一个快照还在执行,跳过"); + return; + } + const now = Date.now(); const intervalMs = (cfg.defaultIntervalHours || 1) * 3600 * 1000; const lastRun = cfg.lastRunAt || 0; @@ -78,6 +85,7 @@ function start({ readStorage, writeStorage, runSnapshotFn, onDailyReportReady, g } console.log(`[reportScheduler] tick: 到点,${enabledDeviceIds.length} 台设备,interval=${cfg.defaultIntervalHours}h,距上次 ${(sinceLast/60000).toFixed(1)} 分钟`); + _inProgress = true; try { await runSnapshotFn(enabledDeviceIds, "scheduled"); // 持久化 lastRunAt —— 重启 Electron 也知道上次什么时候跑的 @@ -86,6 +94,8 @@ function start({ readStorage, writeStorage, runSnapshotFn, onDailyReportReady, g console.log("[reportScheduler] ✅ 快照完成,lastRunAt 已持久化"); } catch (e) { console.error("[reportScheduler] runSnapshot 失败:", e?.message || String(e)); + } finally { + _inProgress = false; } };