/** * 清理发放年月脏数据(如 249241 → 界面显示 2492-41,或 202699 非法月份)。 * * 判定:c_pay_month / payMonth 不是 2020–2030 年合法 yyyyMM。 * 处理:调用线上 POST /api/batches/{id}/purge(明细软摘离 + 批次标记 DELETED)。 * * 用法:node scripts/cleanup-invalid-paymonth.js * 可选:BASE=http://localhost:8088/sjnmtybt node scripts/cleanup-invalid-paymonth.js */ const http = require("http"); const qs = require("querystring"); const BASE = process.env.BASE || "http://121.43.55.7:11091/sjnmtybt"; const DMS_HOST = "121.43.55.7"; const DMS_PORT = 2101; const STAT_COL = { columnId: 1846, modelId: 1969 }; function isValidPayMonth(v) { const s = String(v || "").replace(/-/g, ""); if (!/^[0-9]{6}$/.test(s)) return false; const y = +s.slice(0, 4); const m = +s.slice(4, 6); return y >= 2020 && y <= 2030 && m >= 1 && m <= 12; } function api(method, path, body) { return new Promise((resolve, reject) => { const u = new URL(BASE + path); 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 req = http.request( { hostname: u.hostname, port: u.port, path: u.pathname + u.search, method, headers, }, (res) => { let raw = ""; res.on("data", (c) => (raw += c)); res.on("end", () => { try { resolve(JSON.parse(raw)); } catch (e) { resolve({ code: res.statusCode, raw: String(raw).slice(0, 300) }); } }); } ); req.on("error", reject); if (data) req.write(data); req.end(); }); } function dms(path, fields, token) { return new Promise((resolve, reject) => { const body = qs.stringify(fields || {}); const headers = { "Content-Type": "application/x-www-form-urlencoded", "Content-Length": Buffer.byteLength(body), }; if (token) { headers.Token = token; headers.token = token; } const req = http.request( { hostname: DMS_HOST, port: DMS_PORT, path, method: "POST", headers }, (res) => { let raw = ""; res.on("data", (c) => (raw += c)); res.on("end", () => { try { resolve(JSON.parse(raw)); } catch (e) { resolve({ raw: String(raw).slice(0, 200) }); } }); } ); req.on("error", reject); req.write(body); req.end(); }); } async function login() { const res = await dms("/proxy_oauth/user/login", { userName: "user_liu", password: "WE176852439@lmx", clientId: "1", }); if (res.code != 200) throw new Error("login fail: " + JSON.stringify(res)); return res.message; } async function listBatches() { const all = []; for (let pageNum = 1; pageNum <= 20; pageNum++) { const res = await api("POST", "/api/batches/page", { pageNum, pageSize: 100, }); const list = (res.data && res.data.list) || []; all.push.apply(all, list); if (list.length < 100) break; } return all; } async function neutralizeStat(token, row) { return dms( "/proxy_dms/content/updateContent", { columnId: String(STAT_COL.columnId), modelId: String(STAT_COL.modelId), content: JSON.stringify({ id: row.id, state: -1, c_batch_id: "REMOVED|" + row.id, c_pay_month: "000000", c_remarks: "INVALID_PAYMONTH_CLEANED|" + Date.now(), }), }, token ); } async function cleanStats(token, dirtyBatchIds) { const idSet = new Set(dirtyBatchIds); let cleaned = 0; for (let page = 0; page < 40; page++) { const res = await dms( "/proxy_dms/content/selectContentList", { columnId: String(STAT_COL.columnId), modelId: String(STAT_COL.modelId), page: String(page), pageSize: "100", states: "0,1,2,3", }, token ); const data = (res.content && res.content.data) || []; for (const row of data) { const bid = String(row.c_batch_id || ""); const pm = row.c_pay_month; const hit = idSet.has(bid) || !isValidPayMonth(pm); if (!hit) continue; const r = await neutralizeStat(token, row); if (r && r.code == 200) { cleaned++; console.log("stat OK", row.id, "batch=" + bid, "pm=" + pm); } else { console.log("stat FAIL", row.id, JSON.stringify(r).slice(0, 160)); } } if (data.length < 100) break; } return cleaned; } (async () => { console.log("BASE=" + BASE); const batches = await listBatches(); const dirty = batches.filter((b) => !isValidPayMonth(b.payMonth)); const months = [...new Set(dirty.map((b) => b.payMonth))].sort(); console.log( "列表批次=" + batches.length + " 脏批次=" + dirty.length + " 非法月份=" + months.join(",") ); let ok = 0; let fail = 0; const purgedIds = []; for (const b of dirty) { const id = b.id || b.batchNo; const res = await api("POST", "/api/batches/" + encodeURIComponent(id) + "/purge", null); if (res && res.code == 200 && res.data && res.data.ok !== false) { ok++; purgedIds.push(id); console.log( "purge OK", id, "pm=" + b.payMonth, "detached=" + (res.data.detachedPayments || 0) ); } else { fail++; console.log("purge FAIL", id, "pm=" + b.payMonth, JSON.stringify(res).slice(0, 220)); } } const token = await login(); const stats = await cleanStats(token, purgedIds); const after = await listBatches(); const stillDirty = after.filter((b) => !isValidPayMonth(b.payMonth)); console.log( "完成 purgeOk=" + ok + " purgeFail=" + fail + " statsCleaned=" + stats + " 剩余列表=" + after.length + " 仍脏=" + stillDirty.length ); if (stillDirty.length) { console.log( "仍脏样例", stillDirty.slice(0, 10).map((b) => ({ id: b.id, pm: b.payMonth, no: b.batchNo })) ); } })().catch((e) => { console.error(e); process.exit(1); });