Kaynağa Gözat

fix: 脚本 401 自动重登语义(lib httpJson relogin 透传)

lib.mjs httpJson 新增 relogin 参数:401 且环境有 SMARTBADGE_PASSWORD 时自动重新登录重试一次;
fetchAllReports 透传 relogin;fetch-report-text/report-aggregate 调用点改为依赖该语义。
验证:删除 .token 后 --id/单日聚合均自动重登成功。
Huccct 2 hafta önce
ebeveyn
işleme
09b426ca71
3 değiştirilmiş dosya ile 30 ekleme ve 28 silme
  1. 4 11
      scripts/fetch-report-text.mjs
  2. 25 16
      scripts/lib.mjs
  3. 1 1
      scripts/report-aggregate.mjs

+ 4 - 11
scripts/fetch-report-text.mjs

@@ -10,7 +10,7 @@
 import fs from 'fs';
 import path from 'path';
 import { fileURLToPath } from 'url';
-import { baseUrl, fail, loadToken, login, httpJson, htmlToText, fetchAllReports } from './lib.mjs';
+import { baseUrl, fail, loadToken, httpJson, htmlToText, fetchAllReports } from './lib.mjs';
 
 const args = process.argv.slice(2);
 const argVal = (name) => {
@@ -28,17 +28,10 @@ if (!id && !url) fail('用法: node fetch-report-text.mjs --id <reportId> | --ur
 if (url && !/^https?:\/\//i.test(url)) fail(`--url 仅接受 http(s) 协议(拒绝 file:// 等本地协议),收到: ${url}`, 4);
 
 async function getRedirectTarget() {
-  const token = loadToken();
-  let r = await httpJson('GET', `/api/reports/${id}`, undefined, { base, token, raw: true });
+  const r = await httpJson('GET', `/api/reports/${id}`, undefined, { base, token: loadToken(), raw: true, relogin: true });
   if (!r.ok || !r.json?.data) {
-    if (r.status === 401 && process.env.SMARTBADGE_PASSWORD) {
-      await login({ base, password: process.env.SMARTBADGE_PASSWORD });
-      r = await httpJson('GET', `/api/reports/${id}`, undefined, { base, token: loadToken(), raw: true });
-    }
-    if (!r.ok || !r.json?.data) {
-      const recent = await fetchAllReports({ base, token, pageSize: 3 }).then((rs) => rs.map((x) => `${x.id}(${x.type}, ${x.periodStart})`).join('、'));
-      fail(`报告 id 未找到(${id}). 近期报告参考: ${recent || '无'}`, 5);
-    }
+    const recent = await fetchAllReports({ base, token: loadToken(), pageSize: 3, relogin: true }).then((rs) => rs.map((x) => `${x.id}(${x.type}, ${x.periodStart})`).join('、'));
+    fail(`报告 id 未找到(${id}). 近期报告参考: ${recent || '无'}`, 5);
   }
   const d = r.json.data;
   const target = d.ossUrl || d.downloadUrl;

+ 25 - 16
scripts/lib.mjs

@@ -84,20 +84,29 @@ export async function login({ base, username, password }) {
 }
 
 // ---------- httpJson ----------
-export async function httpJson(method, apiPath, body, { base, token, raw = false, json = true } = {}) {
-  const tokenValue = token || loadToken();
-  const headers = { 'X-Requested-With': 'XMLHttpRequest' };
-  if (body !== undefined) headers['Content-Type'] = 'application/json';
-  if (tokenValue) headers['X-Parse-Session-Token'] = tokenValue;
-  let res;
-  try {
-    res = await fetch(baseUrl(base) + apiPath, {
-      method: method.toUpperCase(),
-      headers,
-      body: body !== undefined ? JSON.stringify(body) : undefined,
-    });
-  } catch (err) {
-    fail(`无法连接服务器(${err.message})\n  - 检查 SMARTBADGE_API_BASE / --base(默认线上 https://recording.sh-lami.com;本地 http://localhost:3002)\n  - 以 / 开头的参数在 Git Bash 需先 export MSYS_NO_PATHCONV=1\n  - 服务器是否已启动`, 3);
+// relogin=true 且 401 且环境有 SMARTBADGE_PASSWORD 时自动重新登录重试一次
+export async function httpJson(method, apiPath, body, { base, token, raw = false, json = true, relogin = false } = {}) {
+  const doFetch = async () => {
+    const tokenValue = token || loadToken();
+    const headers = { 'X-Requested-With': 'XMLHttpRequest' };
+    if (body !== undefined) headers['Content-Type'] = 'application/json';
+    if (tokenValue) headers['X-Parse-Session-Token'] = tokenValue;
+    let res;
+    try {
+      res = await fetch(baseUrl(base) + apiPath, {
+        method: method.toUpperCase(),
+        headers,
+        body: body !== undefined ? JSON.stringify(body) : undefined,
+      });
+    } catch (err) {
+      fail(`无法连接服务器(${err.message})\n  - 检查 SMARTBADGE_API_BASE / --base(默认线上 https://recording.sh-lami.com;本地 http://localhost:3002)\n  - 以 / 开头的参数在 Git Bash 需先 export MSYS_NO_PATHCONV=1\n  - 服务器是否已启动`, 3);
+    }
+    return res;
+  };
+  let res = await doFetch();
+  if (res.status === 401 && relogin && process.env.SMARTBADGE_PASSWORD) {
+    try { await login({ base: baseUrl(base), password: process.env.SMARTBADGE_PASSWORD }); } catch { /* 保留原 401 响应 */ }
+    res = await doFetch();
   }
   const text = await res.text();
   if (json) {
@@ -107,7 +116,7 @@ export async function httpJson(method, apiPath, body, { base, token, raw = false
 }
 
 // ---------- 报告分页拉全 ----------
-export async function fetchAllReports({ base, token, type, start, end, deviceNo, pageSize = 100 } = {}) {
+export async function fetchAllReports({ base, token, type, start, end, deviceNo, pageSize = 100, relogin = false } = {}) {
   const all = [];
   let page = 1;
   for (;;) {
@@ -116,7 +125,7 @@ export async function fetchAllReports({ base, token, type, start, end, deviceNo,
     if (start) qs.set('start', start);
     if (end) qs.set('end', end);
     if (deviceNo) qs.set('deviceNo', deviceNo);
-    const r = await httpJson('GET', `/api/reports?${qs}`, undefined, { base, token, raw: true });
+    const r = await httpJson('GET', `/api/reports?${qs}`, undefined, { base, token, raw: true, relogin });
     const data = r.json?.data;
     const items = data?.items ?? [];
     all.push(...items);

+ 1 - 1
scripts/report-aggregate.mjs

@@ -70,7 +70,7 @@ if (!noReconcile) {
     const batch = dayList.slice(i, i + concurrency);
     await Promise.all(batch.map(async (d) => {
       try {
-        const r = await httpJson('GET', `/api/analysis/reconcile?date=${d}`, undefined, auth);
+        const r = await httpJson('GET', `/api/analysis/reconcile?date=${d}`, undefined, { ...auth, relogin: true });
         reconcile.set(d, r.ok ? r.json?.data ?? { reconcileError: 'no-data' } : { reconcileError: `HTTP ${r.status}` });
       } catch (err) {
         reconcile.set(d, { reconcileError: err.message });