check-online.mjs 4.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. #!/usr/bin/env node
  2. // 线上环境一键核查(Node >=18, 零依赖): 登录 → 核心接口链 → 12 项 PASS/FAIL
  3. // 用法:
  4. // node check-online.mjs # 核查线上 https://recording.sh-lami.com
  5. // SMARTBADGE_API_BASE=http://localhost:3002 node check-online.mjs # 核查本地
  6. // 密码: SMARTBADGE_PASSWORD 环境变量或第一个参数(亦可来自 .env,经 lib 回退链自动加载)
  7. import { baseUrl, login, httpJson, loadToken, fail, findEnvFile, loadEnv } from './lib.mjs';
  8. const env = findEnvFile();
  9. if (env.hit) loadEnv(env.hit);
  10. const PASSWORD = process.env.SMARTBADGE_PASSWORD || process.argv[2];
  11. const base = baseUrl();
  12. if (!PASSWORD) fail('用法: node check-online.mjs [密码](或先 SMARTBADGE_PASSWORD=<密码>)');
  13. let pass = 0;
  14. let failCount = 0;
  15. const check = (name, cond, extra = '') => {
  16. const ok = !!cond;
  17. console.log(`[${ok ? 'PASS' : 'FAIL'}] ${name}${extra ? ` — ${extra}` : ''}`);
  18. ok ? pass++ : failCount++;
  19. };
  20. console.log(`== 线上健康核查 (${base}) ==`);
  21. // 1. 登录
  22. let token;
  23. try {
  24. token = await login({ base, password: PASSWORD });
  25. } catch (err) {
  26. console.log(`\n== 结果: 0 PASS / FAIL(登录失败) ==`);
  27. process.exit(1);
  28. }
  29. const auth = { base, token };
  30. // 2. health / 错误面
  31. const h = await httpJson('GET', '/api/analysis/health', undefined, auth);
  32. const warnings = h.json?.data?.warnings ?? [];
  33. const isBenign = (w) => /无待转录文件|属正常/.test(String(w));
  34. const realWarnings = warnings.filter((w) => !isBenign(w));
  35. check('health ok(仅无害告警)', h.ok && (h.json?.data?.ok === true || realWarnings.length === 0), `warnings=${warnings.length}${realWarnings.length ? `(异常: ${realWarnings.join('; ')})` : ''}`);
  36. check('无害告警外无警告', realWarnings.length === 0, realWarnings.join('; ') || `无害=${warnings.length}条`);
  37. check('自动重试中=0', Number(h.json?.data?.retryingCount ?? -1) === 0, `retryingCount=${h.json?.data?.retryingCount}`);
  38. check('OSS 缺失=0', Number(h.json?.data?.ossMissingCount ?? -1) === 0, `ossMissingCount=${h.json?.data?.ossMissingCount}`);
  39. const cat = h.json?.data?.categoryCounts ?? {};
  40. const catTotal = Object.values(cat).reduce((a, b) => a + b, 0);
  41. check('错误分类计数=0', catTotal === 0, JSON.stringify(cat));
  42. // 3. 归档面
  43. const missing = await httpJson('GET', '/api/archive/missing', undefined, auth);
  44. const missingList = missing.json?.data?.items ?? missing.json?.data;
  45. check('归档失败待重试=空', missing.ok && Array.isArray(missingList) && missingList.length === 0, `missing=${Array.isArray(missingList) ? missingList.length : 'err'}`);
  46. // 4. 列表面(ADR-005 本地主路径 + 实时分流)
  47. const rec = await httpJson('POST', '/api/smartbadge/recordings/list', { page: 1, pageSize: 5 }, auth);
  48. check('录音本地列表', rec.ok && (rec.json?.data?.total || 0) > 0, `total=${rec.json?.data?.total}`);
  49. check('录音列表无失败项', rec.ok && Array.isArray(rec.json?.data?.failed) && rec.json.data.failed.length === 0);
  50. const today = new Date().toISOString().slice(0, 10);
  51. const rt = await httpJson('POST', '/api/smartbadge/recordings/list', { endDate: today, page: 1, pageSize: 5 }, auth);
  52. check('实时窗分流', rt.ok && Array.isArray(rt.json?.data?.items), `total=${rt.json?.data?.total}`);
  53. const hb = await httpJson('POST', '/api/smartbadge/logs/heartbeat', { page: 1, pageSize: 5 }, auth);
  54. check('心跳接口', hb.ok && typeof hb.json?.data?.total === 'number', `total=${hb.json?.data?.total}(上游无数据属正常)`);
  55. const devs = await httpJson('GET', '/api/devices?page=1&pageSize=15', undefined, auth);
  56. check('设备列表', devs.ok && Array.isArray(devs.json?.data?.items || devs.json?.data), `count=${(devs.json?.data?.items || devs.json?.data || []).length}`);
  57. // 5. 任务/报告
  58. const jobs = await httpJson('GET', '/api/analysis/jobs?pageSize=5', undefined, auth);
  59. const list = jobs.json?.data?.items ?? jobs.json?.data ?? [];
  60. const running = list.filter((j) => ['pending', 'downloading', 'filtering', 'transcribing', 'reporting'].includes(j.status));
  61. check('无运行中任务(互斥空闲)', running.length === 0, `running=${running.length}`);
  62. console.log(`\n== 结果: ${pass} PASS / ${failCount} FAIL ==`);
  63. process.exit(failCount > 0 ? 1 : 0);