"use strict"; /** * 巡检报告定时调度器 * * 和 inspectionScheduler.cjs **物理隔离**: * - 独立 tick 循环(1min) * - 独立配置存储(reportScheduleStore) * - 独立触发路径(调 index.cjs 里 runReportSnapshot 的实现函数) * - 独立日报告聚合(dailyReportStore) * * 防止重蹈「两个调度器竞争同一巡检任务」的覆辙。 */ const reportScheduleStore = require("./reportScheduleStore.cjs"); const reportStore = require("./reportStore.cjs"); const dailyReportStore = require("./dailyReportStore.cjs"); let _tickTimer = null; let _dailyTimer = null; let _lastDailyTriggerKey = null; // 用于 HH:MM 日报告每日只触发一次 let _deviceLastRun = new Map(); // deviceId → timestamp(用于 per-device interval) /** * 启动 reportScheduler * * @param {object} deps * @param {function} deps.readStorage * @param {function} deps.writeStorage * @param {function} deps.runSnapshotFn - 定时快照执行函数(renderer 侧 Hook 实际负责执行,这里传空函数) * @param {function} [deps.onDailyReportReady] - 日报告聚合完成后回调 async (report) => void */ function start({ readStorage, writeStorage, runSnapshotFn, onDailyReportReady, getAllDeviceIdsFn }) { if (_tickTimer) return; // 已启动 const readFn = (k) => readStorage(k); const writeFn = (k, v) => writeStorage(k, v); const _getAllDeviceIds = getAllDeviceIdsFn; const tick = async () => { const cfg = reportScheduleStore.getSchedule(readFn); // 临时验证:忽略 cfg.enabled // if (!cfg.enabled) return; const now = Date.now(); const dueTargets = []; // null/undefined = 全部;空数组 = 不巡检;非空数组 = 指定设备 let deviceIds = cfg.enabledDeviceIds; if (deviceIds === null || deviceIds === undefined) { // 全部设备 —— 通过回调拿(由调用方注入,避免依赖不存在的 deviceStore.listDevices) if (typeof _getAllDeviceIds === "function") { deviceIds = _getAllDeviceIds(); console.error("[reportScheduler] tick: enabledDeviceIds=null, _getAllDeviceIds →", deviceIds?.length || 0, "台"); } else { console.error("[reportScheduler] ❌ 无 getAllDeviceIds 回调,跳过"); return; } } else if (Array.isArray(deviceIds) && deviceIds.length === 0) { console.log("[reportScheduler] tick: enabledDeviceIds=[], 跳过(不巡检任何设备)"); return; } else { console.log("[reportScheduler] tick: enabledDeviceIds 非空数组 →", JSON.stringify(deviceIds)); } for (const deviceId of deviceIds || []) { if (!reportScheduleStore.isDeviceEnabled(readFn, deviceId)) continue; // 临时验证:强制 2 分钟间隔 + 首次立即触发 const intervalMs = 2 * 60 * 1000; const lastRun = _deviceLastRun.get(deviceId) || 0; if (now - lastRun >= intervalMs) { dueTargets.push(deviceId); } } console.log("[reportScheduler] tick: 总设备", deviceIds?.length || 0, "台, 到点", dueTargets.length, "台"); if (dueTargets.length === 0) return; console.log(`[reportScheduler] tick: ${dueTargets.length} 台设备到点,interval=${cfg.defaultIntervalHours}h`); try { await runSnapshotFn(dueTargets, "scheduled"); for (const id of dueTargets) _deviceLastRun.set(id, now); } catch (e) { console.error("[reportScheduler] runSnapshot 失败:", e?.message || String(e)); } }; // 临时验证:启动后立刻执行一次 tick(不等间隔) console.log("[reportScheduler] 启动,立即执行首次 tick"); tick().catch(e => console.error("[reportScheduler] 首次 tick 异常:", e)); // 临时验证:每 5 秒 tick 一次 _tickTimer = setInterval(() => { tick().catch(e => console.error("[reportScheduler] tick 异常:", e)); }, 5 * 1000); _tickTimer.unref?.(); // 每秒检查是否到 dailyReportTime(HH:MM 精确匹配,每日只触发一次) const dailyCheck = () => { const cfg = reportScheduleStore.getSchedule(readFn); if (!cfg.enabled) return; const now = new Date(); const p = (n) => n.toString().padStart(2, "0"); const current = `${p(now.getHours())}:${p(now.getMinutes())}`; const key = `${now.getFullYear()}-${p(now.getMonth() + 1)}-${p(now.getDate())}`; if (current === cfg.dailyReportTime && _lastDailyTriggerKey !== key) { _lastDailyTriggerKey = key; console.log("[reportScheduler] 触发日运行报告:", key); triggerDailyReport(readFn, writeFn, { mode: "finalized", trigger: "scheduled" }) .then(report => { if (onDailyReportReady) { onDailyReportReady(report).catch(e => console.error("[reportScheduler] onDailyReportReady 异常:", e?.message || String(e)) ); } }) .catch(e => console.error("[reportScheduler] 日报告生成异常:", e)); } }; _dailyTimer = setInterval(dailyCheck, 30 * 1000); _dailyTimer.unref?.(); console.log("[reportScheduler] 已启动"); } function stop() { if (_tickTimer) { clearInterval(_tickTimer); _tickTimer = null; } if (_dailyTimer) { clearInterval(_dailyTimer); _dailyTimer = null; } console.log("[reportScheduler] 已停止"); } function isRunning() { return !!_tickTimer; } /** * 手动触发日运行报告(立即执行,不等到 HH:MM) * mode: "provisional" — 取当天 0:00 ~ 此刻 * "finalized" — 取当天 0:00 ~ 23:59 */ async function triggerDailyReport(readFn, writeFn, { mode = "provisional", trigger = "manual" } = {}) { const now = Date.now(); const dayStart = dailyReportStore._dayStart(now); const dayEnd = mode === "finalized" ? dailyReportStore._dayEnd(now) : now; const allSnapshots = reportStore.listSnapshots(readFn, 1000); const report = dailyReportStore.buildDailyReport(allSnapshots, dayStart, dayEnd, { mode, trigger }); dailyReportStore.addDailyReport(readFn, writeFn, report); console.log(`[reportScheduler] 日报告 ${report.id} 已生成 (mode=${mode})`); return report; } module.exports = { start, stop, isRunning, triggerDailyReport, };