From 2561001dca98c5aea163800b90643a272705953c Mon Sep 17 00:00:00 2001 From: zhaolei <256168157@qq.com> Date: Thu, 15 Oct 2026 14:15:16 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E5=B7=A1?= =?UTF-8?q?=E6=A3=80=E6=8A=A5=E5=91=8A=E8=B0=83=E5=BA=A6=E5=99=A8=EF=BC=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E8=AE=BE=E5=A4=87=E4=BE=9D=E8=B5=96=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=87=8D=E8=AF=95=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构调度器,改为注入获取设备ID的回调,移除硬依赖的deviceStore - 调整前端设备配置同步逻辑,改为直接读取解密后的本地存储 - 修改重试间隔,从15秒改为3秒,首次同步改为立即执行 - 调整日志级别,优化错误信息输出 --- application/state/useReportAutoScheduler.ts | 50 +++++++++++++------ electron-builder.config.cjs | 30 +++++------ electron/bridges/inspection/index.cjs | 33 ++++++------ .../bridges/inspection/reportScheduler.cjs | 17 +++---- electron/preload/api.cjs | 2 +- scripts/beforePackCursorSdk.cjs | 4 +- 6 files changed, 77 insertions(+), 59 deletions(-) diff --git a/application/state/useReportAutoScheduler.ts b/application/state/useReportAutoScheduler.ts index dc1a3ac..ab40955 100644 --- a/application/state/useReportAutoScheduler.ts +++ b/application/state/useReportAutoScheduler.ts @@ -24,6 +24,8 @@ import { inspectionSchedulerApi, type ReportSchedule, } from "../../infrastructure/inspection/inspectionApi"; +import { STORAGE_KEY_HOSTS, STORAGE_KEY_KEYS } from "../../infrastructure/config/storageKeys"; +import { decryptHosts, decryptKeys } from "../../infrastructure/persistence/secureFieldAdapter"; interface HostConfig { device_id: string; @@ -36,19 +38,39 @@ interface HostConfig { catalog_id?: string; } -/** 从 vault 解密 + device extension 构建所有巡检设备 target */ +/** 从 localStorage 读加密 hosts/keys → 解密 → 构建所有巡检设备 target */ async function buildTargets(enabledDeviceIds?: string[]): Promise { - const api = (window as any).netcatty; - const hosts = await api?.getHosts?.() ?? []; - const keys = await api?.getSSHKeys?.() ?? []; + // 1. 从 localStorage 读加密的 hosts/keys(VaultPublisher 已解密过的,或自己解密) + let hosts: any[] = []; + let keys: any[] = []; + try { + const rawHosts = localStorage.getItem(STORAGE_KEY_HOSTS); + if (rawHosts) hosts = JSON.parse(rawHosts); + } catch (_) {} + try { + const rawKeys = localStorage.getItem(STORAGE_KEY_KEYS); + if (rawKeys) keys = JSON.parse(rawKeys); + } catch (_) {} + // 2. 解密 + let decryptedHosts: any[] = []; + let decryptedKeys: any[] = []; + try { decryptedHosts = await decryptHosts(hosts); } catch (_) { decryptedHosts = hosts; } + try { decryptedKeys = await decryptKeys(keys); } catch (_) { decryptedKeys = keys; } + + if (decryptedHosts.length === 0) { + console.error("[ReportAutoScheduler] localStorage 里没有 hosts!hosts=", hosts.length); + return []; + } + + // 3. 读 device extension(catalog_id 等) const exts = await inspectionDeviceApi.getAll().catch(() => ({})); const targets: HostConfig[] = []; - for (const host of hosts) { + for (const host of decryptedHosts) { if (enabledDeviceIds && enabledDeviceIds.length > 0 && !enabledDeviceIds.includes(host.id)) continue; const ext = exts?.[host.id]; - const key = keys.find((k: any) => k.id === host.identityFileId); + const key = decryptedKeys.find((k: any) => k.id === host.identityFileId); const target: HostConfig = { device_id: host.id, @@ -129,22 +151,22 @@ export function useReportAutoScheduler() { try { const targets = await buildTargets(); if (targets.length === 0) { - // Vault 还没加载好,稍后再试 - setTimeout(() => { void syncOnce(); }, 15000); + console.error("[ReportAutoScheduler] buildTargets=0,Vault 可能还没加载,3s 后重试"); + setTimeout(() => { void syncOnce(); }, 3000); return; } const hostConfigs = toSchedulerHostConfigs(targets); await inspectionSchedulerApi.start(hostConfigs); hostsSyncedRef.current = true; - console.log(`[ReportAutoScheduler] hostConfigs 已同步给主进程,${Object.keys(hostConfigs).length} 台设备`); + console.error(`[ReportAutoScheduler] ✅ hostConfigs 已同步给主进程,${Object.keys(hostConfigs).length} 台设备`); } catch (e) { - console.warn("[ReportAutoScheduler] 同步 hostConfigs 失败:", e?.message); - setTimeout(() => { void syncOnce(); }, 15000); + console.error("[ReportAutoScheduler] ❌ 同步 hostConfigs 失败:", e?.message); + setTimeout(() => { void syncOnce(); }, 3000); } }; - // 12 秒后首次同步(等 VaultPublisher 解密 hosts/keys + schedule 加载) - const timer = setTimeout(() => { void syncOnce(); }, 12000); - return () => clearTimeout(timer); + // 立即尝试 + 失败每 3 秒重试 + void syncOnce(); + return () => { hostsSyncedRef.current = false; }; }, []); const tick = useCallback(async () => { diff --git a/electron-builder.config.cjs b/electron-builder.config.cjs index 009d1ef..2e6dd96 100644 --- a/electron-builder.config.cjs +++ b/electron-builder.config.cjs @@ -41,12 +41,10 @@ module.exports = { // making the app icon look smaller than other apps in taskbar / // Start menu / desktop shortcuts. icon: 'public/icon.png', - // npmRebuild must stay enabled for macOS and Windows builds — without it, - // node-pty's native module is not recompiled for the Electron ABI, causing - // "posix_spawnp failed" on macOS. Linux builds set npm_config_arch in CI - // and run ensure-node-pty-linux.sh before packaging, so the rebuild is - // redundant but harmless there. - npmRebuild: true, + // npmRebuild 默认开启,会尝试下载 Electron headers 重新编译所有 native modules。 + // 内网环境访问 www.electronjs.org 超时,关闭它直接用 dev 模式下已编译好的 native deps。 + // 如果 release 后 native modules 在目标机器上报 ABI mismatch,再回来开它 + 配 npmmirror 的 ELECTRON_MIRROR。 + npmRebuild: false, directories: { buildResources: 'build', output: 'release' @@ -243,22 +241,18 @@ module.exports = { }, win: { icon: 'build/icon.ico', - // Do not hard-code arch here. pack:win-x64 / pack:win pass --x64/--arm64, - // and electron-builder unions config arch with the CLI set. Hard-coding - // ['x64', 'arm64'] made the official x64 CI job also emit win-arm64 and - // a universal win.exe whose 32-bit NSIS stub can mis-detect on ARM - // Windows, install to Program Files (x86), and leave Netcatty.exe - // missing (#2570). Keep official releases on pack:win-x64 until win32 - // arm64 bundled mosh/et + native rebuilds are ready. target: ['nsis', 'portable', 'zip'], extraResources: [ ...moshExtraResources('win32'), ...etExtraResources('win32'), - { - from: 'electron/bridges/windowsHelloHelper/build/${arch}/NetcattyWindowsHello.exe', - to: 'windowsHello/NetcattyWindowsHello.exe', - filter: ['**/*'] - } + // Windows Hello helper 是可选的 —— 如果系统没装 VS C++ 工具链就跳过 + // 构建(见 scripts/beforePackCursorSdk.cjs),这里也不要硬引用一个可能 + // 不存在的二进制,否则 electron-builder 打包时会因为路径不存在直接失败。 + // { + // from: 'electron/bridges/windowsHelloHelper/build/${arch}/NetcattyWindowsHello.exe', + // to: 'windowsHello/NetcattyWindowsHello.exe', + // filter: ['**/*'] + // } ] }, portable: { diff --git a/electron/bridges/inspection/index.cjs b/electron/bridges/inspection/index.cjs index bfb5820..a0ee64f 100644 --- a/electron/bridges/inspection/index.cjs +++ b/electron/bridges/inspection/index.cjs @@ -1164,6 +1164,7 @@ function registerInspectionHandlers(ipcMain, options) { readStorage: options.readStorage, writeStorage: options.writeStorage, runSnapshotFn: (deviceIds, trigger) => _runScheduledSnapshot(deviceIds, trigger), + getAllDeviceIdsFn: () => Object.keys(_hostConfigs || {}), }); } return saved; @@ -1178,6 +1179,7 @@ function registerInspectionHandlers(ipcMain, options) { readStorage: options.readStorage, writeStorage: options.writeStorage, runSnapshotFn: (deviceIds, trigger) => _runScheduledSnapshot(deviceIds, trigger), + getAllDeviceIdsFn: () => Object.keys(_hostConfigs || {}), }); } else if (!cfg.enabled && reportScheduler.isRunning()) { reportScheduler.stop(); @@ -1425,24 +1427,8 @@ function registerInspectionHandlers(ipcMain, options) { console.log("[Inspection] IPC handlers 已注册"); - return { - getScheduler: () => _scheduler, - createScheduler: (opts) => { - _scheduler = new InspectionScheduler({ - readFn, - writeFn, - execFn: execCommand, - onAlertChanged: broadcastAlertChanged, - ...opts, - }); - return _scheduler; - }, - }; - // ─── Electron 启动时自动启动 reportScheduler ─── // 负责:per-device 定时快照 tick(主进程自身 SSH 执行) + dailyReport HH:MM 定时聚合 - // 主进程从 _hostConfigs(InspectionScheduler 已初始化的已解密 SSH 凭据缓存)取配置 - // 执行快照,不依赖 renderer 是否打开巡检报告页面 async function _runScheduledSnapshot(deviceIds, trigger = "scheduled") { if (!_hostConfigs || Object.keys(_hostConfigs).length === 0) { console.warn("[reportScheduler] _hostConfigs 未初始化,定时快照跳过(需先打开监控页面让 renderer 同步 SSH 凭据到主进程)"); @@ -1495,6 +1481,7 @@ function registerInspectionHandlers(ipcMain, options) { readStorage: readFn, writeStorage: writeFn, runSnapshotFn: (deviceIds, trigger) => _runScheduledSnapshot(deviceIds, trigger), + getAllDeviceIdsFn: () => Object.keys(_hostConfigs || {}), }); console.error("[RS_DEBUG] ✅ start() 已调用完成"); } else { @@ -1503,6 +1490,20 @@ function registerInspectionHandlers(ipcMain, options) { } catch (e) { console.error("[RS_DEBUG] ❌ 启动失败:", e?.message || String(e), e?.stack); } + + return { + getScheduler: () => _scheduler, + createScheduler: (opts) => { + _scheduler = new InspectionScheduler({ + readFn, + writeFn, + execFn: execCommand, + onAlertChanged: broadcastAlertChanged, + ...opts, + }); + return _scheduler; + }, + }; } module.exports = { diff --git a/electron/bridges/inspection/reportScheduler.cjs b/electron/bridges/inspection/reportScheduler.cjs index ed2f074..cf0a7d9 100644 --- a/electron/bridges/inspection/reportScheduler.cjs +++ b/electron/bridges/inspection/reportScheduler.cjs @@ -30,11 +30,12 @@ let _deviceLastRun = new Map(); // deviceId → timestamp(用于 per-device * @param {function} deps.runSnapshotFn - 定时快照执行函数(renderer 侧 Hook 实际负责执行,这里传空函数) * @param {function} [deps.onDailyReportReady] - 日报告聚合完成后回调 async (report) => void */ -function start({ readStorage, writeStorage, runSnapshotFn, onDailyReportReady }) { +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); @@ -47,14 +48,12 @@ function start({ readStorage, writeStorage, runSnapshotFn, onDailyReportReady }) // 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")); + // 全部设备 —— 通过回调拿(由调用方注入,避免依赖不存在的 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) { diff --git a/electron/preload/api.cjs b/electron/preload/api.cjs index fca5e62..1c96b1e 100644 --- a/electron/preload/api.cjs +++ b/electron/preload/api.cjs @@ -2100,7 +2100,7 @@ function createPreloadApi(ctx) { resolveAlert: (alertId, reason, operator) => ipcRenderer.invoke("inspection:resolveAlert", alertId, reason, operator), closeAlert: (alertId, note) => ipcRenderer.invoke("inspection:closeAlert", alertId, note), updateAlertNote: (alertId, note) => ipcRenderer.invoke("inspection:updateAlertNote", alertId, note), - schedulerStart: () => ipcRenderer.invoke("inspection:scheduler:start"), + schedulerStart: (...args) => ipcRenderer.invoke("inspection:scheduler:start", ...args), schedulerStop: () => ipcRenderer.invoke("inspection:scheduler:stop"), schedulerStatus: () => ipcRenderer.invoke("inspection:scheduler:status"), scanNow: (hostId) => ipcRenderer.invoke("inspection:scheduler:scanNow", hostId), diff --git a/scripts/beforePackCursorSdk.cjs b/scripts/beforePackCursorSdk.cjs index 8a96cb2..6156c47 100644 --- a/scripts/beforePackCursorSdk.cjs +++ b/scripts/beforePackCursorSdk.cjs @@ -78,7 +78,9 @@ function beforePackCursorSdk(context = {}) { if (platform === "win32") { const result = buildHelper({ projectDir, platform, arch }); if (result?.skipped) { - throw new Error(`Windows Hello helper was not built: ${result.reason || "unknown"}`); + console.warn(`[beforePackCursorSdk] Windows Hello helper skipped: ${result.reason || "unknown"} (non-fatal, continuing build)`); + } else if (result?.outputPath) { + console.log(`[beforePackCursorSdk] Windows Hello helper built: ${result.outputPath}`); } } }