Initial commit: NetMesh smart ops platform with 3D topology, inspection reports, RCA and AI ops assistant
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
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
This commit is contained in:
157
electron/bridges/inspection/reportScheduler.cjs
Normal file
157
electron/bridges/inspection/reportScheduler.cjs
Normal file
@@ -0,0 +1,157 @@
|
||||
"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,
|
||||
};
|
||||
Reference in New Issue
Block a user