| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- /**
- * 行政区划栏目去重:按业务 code(remarks 中 code=)分组,保留最早一条,其余删除/销毁。
- *
- * 用法: node scripts/dedupe-dms-regions.js
- * 可选: node scripts/dedupe-dms-regions.js --dry-run
- */
- const http = require("http");
- const qs = require("querystring");
- const fs = require("fs");
- const path = require("path");
- const HOST = "121.43.55.7";
- const PORT = 2101;
- const DRY = process.argv.includes("--dry-run");
- const COL = { columnId: 1842, modelId: 1965 };
- function req(urlPath, fields, token, method) {
- 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 r = http.request(
- { hostname: HOST, port: PORT, path: urlPath, method: method || "POST", headers },
- (res) => {
- let data = "";
- res.on("data", (c) => (data += c));
- res.on("end", () => {
- try {
- resolve(JSON.parse(data));
- } catch (e) {
- resolve({ raw: data, status: res.statusCode });
- }
- });
- }
- );
- r.on("error", reject);
- r.write(body);
- r.end();
- });
- }
- async function login() {
- const res = await req("/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 && String(res.message).startsWith("eyJ")
- ? res.message
- : res.content;
- }
- function remarkTag(remarks, key) {
- if (!remarks) return null;
- const m = String(remarks).match(new RegExp("(?:^|[|])" + key + "=([^|]+)"));
- return m ? m[1] : null;
- }
- function bizKey(row) {
- const remarks = row.c_remarks || row.content || "";
- const code = remarkTag(remarks, "code");
- if (code) return "code:" + code;
- const level = remarkTag(remarks, "level") || "";
- const parent = row.c_parent_id || "";
- const name = row.c_name || row.title || "";
- return "name:" + level + "|" + parent + "|" + name;
- }
- async function fetchAll(token) {
- const all = [];
- let page = 0;
- const pageSize = 100;
- while (page < 50) {
- const res = await req(
- "/proxy_dms/content/selectContentList",
- {
- columnId: String(COL.columnId),
- modelId: String(COL.modelId),
- states: "0",
- page: String(page),
- pageSize: String(pageSize),
- },
- token
- );
- if (res.code == 202) break;
- if (res.code != 200) {
- throw new Error("select fail: " + JSON.stringify(res).slice(0, 300));
- }
- const content = res.content || {};
- const rows = content.data || content.list || content.records || [];
- if (!rows.length) break;
- all.push(...rows);
- const total = content.count || content.total || 0;
- if (all.length >= total || rows.length < pageSize) break;
- page++;
- }
- return all;
- }
- async function hardDelete(token, id) {
- // DMS 原生删除(部分环境 columnId 解析有 bug,失败则走销毁)
- return req(
- "/proxy_dms/content/delContentById",
- { contentId: id, columnId: String(COL.columnId) },
- token,
- "DELETE"
- );
- }
- async function destroy(token, id) {
- // state=4 销毁;estimateType 对 4 恒为 true
- return req(
- "/proxy_dms/content/updateAudit",
- {
- columnId: String(COL.columnId),
- id,
- state: "4",
- auditorName: "dedupe-script",
- auditorComment: "行政区划去重销毁重复项",
- },
- token
- );
- }
- async function removeOne(token, id) {
- const hard = await hardDelete(token, id);
- if (hard && hard.code == 200) {
- return { ok: true, mode: "delete", res: hard };
- }
- const soft = await destroy(token, id);
- if (soft && soft.code == 200) {
- return { ok: true, mode: "destroy", res: soft, hardFail: hard };
- }
- return { ok: false, mode: "fail", hard, soft };
- }
- (async () => {
- const token = await login();
- console.log("login ok", DRY ? "(dry-run)" : "(live)");
- const rows = await fetchAll(token);
- console.log("loaded regions:", rows.length);
- const groups = new Map();
- for (const row of rows) {
- const key = bizKey(row);
- if (!groups.has(key)) groups.set(key, []);
- groups.get(key).push(row);
- }
- const plan = [];
- for (const [key, list] of groups.entries()) {
- if (list.length <= 1) continue;
- list.sort((a, b) => {
- const ta = Number(a.create_time || a.c_operate_time || 0);
- const tb = Number(b.create_time || b.c_operate_time || 0);
- if (ta !== tb) return ta - tb;
- return String(a.id).localeCompare(String(b.id));
- });
- const keep = list[0];
- const drop = list.slice(1);
- plan.push({
- key,
- keepId: keep.id,
- keepName: keep.c_name || keep.title,
- dropIds: drop.map((d) => d.id),
- dropNames: drop.map((d) => d.c_name || d.title),
- });
- }
- console.log("duplicate groups:", plan.length);
- const result = { dryRun: DRY, total: rows.length, groups: plan.length, kept: [], removed: [], errors: [] };
- for (const g of plan) {
- console.log(
- ` ${g.key} keep=${g.keepId}(${g.keepName}) drop=${g.dropIds.length} [${g.dropNames.join(",")}]`
- );
- result.kept.push({ key: g.key, id: g.keepId, name: g.keepName });
- if (DRY) {
- for (const id of g.dropIds) {
- result.removed.push({ id, key: g.key, mode: "dry-run" });
- }
- continue;
- }
- for (const id of g.dropIds) {
- const r = await removeOne(token, id);
- if (r.ok) {
- console.log(" -", r.mode, id);
- result.removed.push({ id, key: g.key, mode: r.mode });
- } else {
- console.log(" x fail", id, JSON.stringify(r.hard || r.soft).slice(0, 200));
- result.errors.push({ id, key: g.key, hard: r.hard, soft: r.soft });
- }
- }
- }
- const outPath = path.join(__dirname, "dedupe-dms-regions-result.json");
- fs.writeFileSync(outPath, JSON.stringify(result, null, 2));
- console.log("done. removed=", result.removed.length, "errors=", result.errors.length);
- console.log("result ->", outPath);
- })().catch((e) => {
- console.error(e);
- process.exit(1);
- });
|