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