Files
SenMeshNet/electron/bridges/inspection/reportScheduler.cjs
zhaolei 2f9f8da48e
Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
Initial commit: NetMesh smart ops platform with 3D topology, inspection reports, RCA and AI ops assistant
2026-10-15 13:53:04 +08:00

158 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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 }) {
if (_tickTimer) return; // 已启动
const readFn = (k) => readStorage(k);
const writeFn = (k, v) => writeStorage(k, v);
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 拉所有设备 ID
try {
const deviceStore = require("./deviceStore.cjs");
const all = deviceStore.listDevices(readFn);
deviceIds = all.map(d => d.host_id || d.id).filter(Boolean);
console.log("[reportScheduler] tick: enabledDeviceIds=null, deviceStore.listDevices →", all.length, "台 → ids=", JSON.stringify(deviceIds));
} catch (e) {
console.error("[reportScheduler] ❌ 读取全部设备失败:", e?.message, e?.stack?.split("\n").slice(0,3).join("\n"));
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,
};