| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- /**
- * 前端各页面只读接口耗时巡检(跳过初始化/清空/写操作)
- * 用法: node scripts/frontend-audit-apis.js
- */
- const http = require("http");
- const fs = require("fs");
- const path = require("path");
- const BASE = { hostname: "localhost", port: 8088, prefix: "/sjnmtybt" };
- const PAY_MONTH = "202608";
- const TOWN = "T_YEXIE";
- const VILLAGE = "V_YX_001";
- const TIMEOUT_WARN = 3000;
- const TIMEOUT_FAIL = 15000;
- function req(method, apiPath, body, timeoutMs) {
- const t0 = Date.now();
- return new Promise((resolve) => {
- const data = body == null ? null : JSON.stringify(body);
- const headers = { Accept: "application/json" };
- if (data) {
- headers["Content-Type"] = "application/json";
- headers["Content-Length"] = Buffer.byteLength(data);
- }
- const timer = setTimeout(() => {
- resolve({ ms: Date.now() - t0, code: 0, error: "TIMEOUT", data: null });
- r.destroy();
- }, timeoutMs || TIMEOUT_FAIL);
- const r = http.request(
- {
- hostname: BASE.hostname,
- port: BASE.port,
- path: BASE.prefix + apiPath,
- method,
- headers,
- },
- (res) => {
- let raw = "";
- res.on("data", (c) => (raw += c));
- res.on("end", () => {
- clearTimeout(timer);
- const ms = Date.now() - t0;
- let json = null;
- try {
- json = JSON.parse(raw);
- } catch (_) {
- json = { parseError: true };
- }
- resolve({
- ms,
- http: res.statusCode,
- code: json && json.code != null ? json.code : res.statusCode,
- data: json && json.data,
- message: json && (json.message || json.msg),
- error: null,
- });
- });
- }
- );
- r.on("error", (e) => {
- clearTimeout(timer);
- resolve({ ms: Date.now() - t0, code: 0, error: e.message, data: null });
- });
- if (data) r.write(data);
- r.end();
- });
- }
- /** 页面 → 只读接口用例 */
- const PAGE_CASES = [
- {
- page: "工作台 /dashboard",
- role: "all",
- cases: [
- ["GET", `/api/dashboard/workbench?payMonth=${PAY_MONTH}&townId=${TOWN}&months=6`, null, 90000],
- ["GET", `/api/dashboard/summary?payMonth=${PAY_MONTH}&townId=${TOWN}`, null],
- ["GET", `/api/dashboard/region-stats?payMonth=${PAY_MONTH}&townId=${TOWN}&months=6`, null],
- ["GET", "/api/workflow/roles", null],
- ["GET", "/api/workflow/stages", null],
- ],
- },
- {
- page: "人员录入 /base",
- role: "village,admin",
- cases: [
- ["GET", "/api/regions?level=TOWN&parentId=D_SJ", null],
- ["GET", `/api/regions?level=VILLAGE&parentId=${TOWN}`, null],
- [
- "POST",
- "/api/personnel/page",
- { pageNum: 1, pageSize: 20, townId: TOWN, villageId: VILLAGE },
- ],
- ["GET", `/api/amount-standards/current?townId=${TOWN}&standardType=MONTHLY`, null],
- ],
- },
- {
- page: "居保匹配 /insurance",
- role: "social,admin",
- cases: [
- [
- "POST",
- "/api/batches/page",
- {
- pageNum: 1,
- pageSize: 10,
- batchLevel: "TOWN",
- townId: TOWN,
- statusIn: "PENDING_INSURANCE_MATCH,PENDING_STATUS_MAINTAIN",
- lite: true,
- },
- ],
- [
- "POST",
- "/api/batches/page",
- {
- pageNum: 1,
- pageSize: 10,
- batchLevel: "DISTRICT",
- statusIn: "PENDING_INSURANCE_MATCH,PENDING_STATUS_MAINTAIN",
- lite: true,
- },
- ],
- ],
- },
- {
- page: "审批管理 /approvals",
- role: "town,leader,admin",
- cases: [
- [
- "POST",
- "/api/batches/page",
- { pageNum: 1, pageSize: 20, townId: TOWN, batchLevel: "TOWN", lite: true },
- ],
- [
- "POST",
- "/api/personnel/page",
- { pageNum: 1, pageSize: 20, townId: TOWN, bizStatus: "PENDING_TOWN_APPROVE" },
- ],
- ],
- },
- {
- page: "补助标准 /status",
- role: "social,admin",
- cases: [
- ["GET", `/api/amount-standards?townId=${TOWN}¤t=true`, null],
- ["GET", `/api/amount-standards/current?townId=${TOWN}&standardType=FUNERAL`, null],
- ],
- },
- {
- page: "特殊业务 /special",
- role: "social,admin",
- cases: [
- ["GET", "/api/special-biz/page?bizType=FUNERAL&pageNum=1&pageSize=20", null],
- ["GET", "/api/special-biz/page?bizType=ADJUST&pageNum=1&pageSize=20", null],
- [`GET`, `/api/special-biz/refund-stats?townId=${TOWN}&payMonth=${PAY_MONTH}`, null],
- ],
- },
- {
- page: "月补贴清单 /monthly",
- role: "social,admin",
- cases: [
- [
- "POST",
- "/api/batches/page",
- {
- pageNum: 1,
- pageSize: 20,
- townId: TOWN,
- batchLevel: "TOWN",
- payMonth: PAY_MONTH,
- lite: true,
- withTownTotals: true,
- },
- ],
- [
- "POST",
- "/api/batches/page",
- {
- pageNum: 1,
- pageSize: 20,
- batchLevel: "TOWN",
- statusIn:
- "PENDING_STATUS_MAINTAIN,PENDING_LEADER_APPROVE,READY_EXPORT,EXPORTED,RETURNED,ARCHIVED",
- lite: true,
- withTownTotals: false,
- },
- ],
- [
- "POST",
- "/api/batches/unresolved-insurance-counts",
- { batchIds: ["TB_T_YEXIE_202608"] },
- ],
- [
- "POST",
- "/api/payments/details/page",
- { pageNum: 1, pageSize: 20, townId: TOWN, payMonth: PAY_MONTH },
- ],
- ],
- },
- {
- page: "回盘确认 /disk",
- role: "social,admin",
- cases: [
- [
- "POST",
- "/api/batches/page",
- {
- pageNum: 1,
- pageSize: 20,
- townId: TOWN,
- statusIn: "EXPORTED,RETURNED",
- lite: true,
- },
- ],
- [
- "POST",
- "/api/return-disk/details/page",
- { pageNum: 1, pageSize: 20, townId: TOWN },
- ],
- ],
- },
- {
- page: "监控告警 /alerts",
- role: "all",
- cases: [
- ["POST", "/api/alerts/page", { pageNum: 1, pageSize: 20, townId: TOWN }],
- ["POST", "/api/alerts/page", { pageNum: 1, pageSize: 20, level: "HIGH" }],
- ],
- },
- {
- page: "数据报表 /reports",
- role: "all",
- cases: [
- ["GET", `/api/reports/yoy?payMonth=${PAY_MONTH}`, null],
- ["GET", `/api/reports/topics/personnel?payMonth=${PAY_MONTH}&townId=${TOWN}`, null],
- ["GET", `/api/reports/topics/payment?payMonth=${PAY_MONTH}&townId=${TOWN}`, null],
- [
- "POST",
- "/api/batch-stats/page",
- { pageNum: 1, pageSize: 20, townId: TOWN, payMonth: PAY_MONTH },
- ],
- ],
- },
- {
- page: "区划管理 /regions",
- role: "admin",
- cases: [
- ["GET", "/api/regions?level=TOWN&parentId=D_SJ", null],
- ["GET", "/api/regions?level=VILLAGE", null],
- ],
- },
- {
- page: "人员底数 /personnel-admin",
- role: "admin",
- cases: [
- ["POST", "/api/personnel/page", { pageNum: 1, pageSize: 20, townId: TOWN }],
- ["POST", "/api/personnel/page", { pageNum: 1, pageSize: 20 }],
- ],
- },
- {
- page: "批次管理 /admin-batches",
- role: "admin",
- cases: [
- [
- "POST",
- "/api/batches/page",
- { pageNum: 1, pageSize: 20, payMonth: PAY_MONTH, lite: true, withTownTotals: false },
- ],
- [
- "POST",
- "/api/batches/page",
- { pageNum: 1, pageSize: 20, payMonth: PAY_MONTH, lite: true, withTownTotals: true },
- ],
- ["GET", `/api/workflow/batches/DB${PAY_MONTH}/overview`, null],
- ],
- },
- {
- page: "系统管理 /system",
- role: "admin",
- cases: [
- ["GET", "/api/workflow/roles", null],
- ["GET", "/api/workflow/lanes", null],
- ["GET", "/api/workflow/special-biz-nodes", null],
- ["GET", "/api/admin/validate-rules", null],
- ],
- },
- ];
- function hintOf(data) {
- if (data == null) return "null";
- if (Array.isArray(data)) return "arr=" + data.length;
- if (data.list) return "total=" + data.total + ",list=" + (data.list.length || 0);
- if (typeof data === "object") {
- const keys = Object.keys(data).slice(0, 8);
- const extra = [];
- if (data.monthAmount != null) extra.push("monthAmount=" + data.monthAmount);
- if (data.personnelCount != null) extra.push("personnel=" + data.personnelCount);
- if (data.currentMonth) {
- extra.push(
- "curAmt=" + (data.currentMonth.totalAmount || 0) +
- ",curPeople=" + (data.currentMonth.peopleCount || 0)
- );
- }
- return extra.length ? extra.join(";") : "keys=" + keys.join(",");
- }
- return String(data).slice(0, 60);
- }
- function statusOf(r) {
- if (r.error === "TIMEOUT") return "TIMEOUT";
- if (r.error) return "ERR";
- if (r.code !== 200) return "BIZ_FAIL";
- if (r.ms > TIMEOUT_FAIL) return "TIMEOUT";
- if (r.ms > TIMEOUT_WARN) return "SLOW";
- return "OK";
- }
- (async () => {
- console.log("=== Frontend API Audit (read-only) ===\n");
- const results = [];
- const seen = new Set();
- for (const group of PAGE_CASES) {
- for (const row of group.cases) {
- const method = row[0];
- const apiPath = row[1];
- const body = row[2];
- const timeoutMs = row[3];
- const key = method + " " + apiPath + JSON.stringify(body || {});
- if (seen.has(key)) continue;
- seen.add(key);
- const r = await req(method, apiPath, body, timeoutMs);
- const st = statusOf(r);
- results.push({
- page: group.page,
- role: group.role,
- method,
- path: apiPath.split("?")[0],
- ms: r.ms,
- code: r.code,
- status: st,
- hint: hintOf(r.data),
- message: r.message || r.error || "",
- });
- console.log(
- (st === "OK" ? "OK " : st.padEnd(5)) +
- " | " +
- String(r.ms).padStart(5) +
- "ms | " +
- group.page.split(" ")[0] +
- " | " +
- method +
- " " +
- apiPath.split("?")[0]
- );
- }
- }
- // 数据一致性抽检
- const checks = [];
- checks.push({
- name: "叶榭工作台本月金额=村图表合计",
- pass: false,
- detail: "pending",
- });
- const wbYexie = await req(
- "GET",
- `/api/dashboard/workbench?payMonth=${PAY_MONTH}&townId=${TOWN}&months=6`,
- null,
- 120000
- );
- const yx = wbYexie.data || {};
- const yxAmt = Number(yx.monthAmount || 0);
- const yxVillageSum = (yx.villageCharts || []).reduce(
- (s, v) => s + Number(v.amount || 0),
- 0
- );
- const yxAmtOk = yxAmt > 0 && Math.abs(yxAmt - yxVillageSum) < 1;
- checks[checks.length - 1] = {
- name: "叶榭工作台本月金额=村图表合计",
- pass: yxAmtOk,
- detail: `monthAmount=${yxAmt}, villageSum=${yxVillageSum}, ms=${wbYexie.ms}`,
- };
- checks.push({
- name: "叶榭本月新增不应≈总人数",
- pass: Number(yx.monthIncreaseCount || 0) < Number(yx.monthPeopleCount || 0) * 0.5,
- detail: `monthIncrease=${yx.monthIncreaseCount}, monthPeople=${yx.monthPeopleCount}`,
- });
- const wb = await req(
- "GET",
- `/api/dashboard/workbench?payMonth=${PAY_MONTH}&townId=T_MAOGANG&months=6`,
- null
- );
- const ma = wb.data || {};
- const monthAmt = Number(ma.monthAmount || 0);
- const villageSum = (ma.villageCharts || []).reduce(
- (s, v) => s + Number(v.amount || 0),
- 0
- );
- const amtOk = monthAmt > 0 && Math.abs(monthAmt - villageSum) < 1;
- checks.push({
- name: "泖港工作台本月金额=村图表合计",
- pass: amtOk,
- detail: `monthAmount=${monthAmt}, villageSum=${villageSum}`,
- });
- const ins = await req("POST", "/api/batches/page", {
- pageNum: 1,
- pageSize: 10,
- batchLevel: "TOWN",
- townId: TOWN,
- statusIn: "PENDING_INSURANCE_MATCH,PENDING_STATUS_MAINTAIN",
- lite: true,
- });
- checks.push({
- name: "居保列表 lite 查询 < 3s",
- pass: ins.ms < TIMEOUT_WARN && ins.code === 200,
- detail: `ms=${ins.ms}, total=${ins.data && ins.data.total}`,
- });
- const insHist = await req("POST", "/api/batches/page", {
- pageNum: 1,
- pageSize: 10,
- batchLevel: "TOWN",
- townId: TOWN,
- statusIn: "PENDING_LEADER_APPROVE,READY_EXPORT,EXPORTED,RETURNED,ARCHIVED",
- lite: true,
- withTownTotals: true,
- });
- const histBatch =
- insHist.data && insHist.data.list && insHist.data.list.length
- ? insHist.data.list[0]
- : null;
- const histPeople = Number((histBatch && histBatch.peopleCount) || 0);
- const histAmt = Number((histBatch && histBatch.totalAmount) || 0);
- checks.push({
- name: "叶榭居保历史批人数/金额与明细一致",
- pass: histPeople > 1000 && histAmt > 1_000_000,
- detail: `peopleCount=${histPeople}, totalAmount=${histAmt}, ms=${insHist.ms}`,
- });
- const outDir = path.join(__dirname, "audit-output");
- if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
- const outFile = path.join(outDir, "api-audit-result.json");
- fs.writeFileSync(
- outFile,
- JSON.stringify({ at: new Date().toISOString(), results, checks }, null, 2),
- "utf8"
- );
- const slow = results.filter((r) => r.status === "SLOW" || r.status === "TIMEOUT");
- const fail = results.filter((r) => r.status === "BIZ_FAIL" || r.status === "ERR");
- console.log("\n=== Summary ===");
- console.log("total=", results.length, "ok=", results.filter((r) => r.status === "OK").length);
- console.log("slow=", slow.length, "fail=", fail.length);
- checks.forEach((c) =>
- console.log((c.pass ? "PASS" : "FAIL") + " | " + c.name + " | " + c.detail)
- );
- console.log("\nWrote", outFile);
- process.exit(fail.length + checks.filter((c) => !c.pass).length > 0 ? 1 : 0);
- })();
|