/** * # 脚本 清洗 inquiries 表中 WordPress 遗留的 PHP 序列化字符串 * 范围:product_interest(数组→逗号连接)、message(数组→换行连接)、 * fields / utm(jsonb 内递归还原)。 * 幂等:已清洗的行不再匹配序列化前缀,重复执行零更新。 * 运行:bun scripts/cleanup-php-serialize.ts [--dry] */ import { SQL } from "bun"; import { phpToPlain, deepConvertPhp, tryPhpUnserialize } from "../src/lib/php-serialize"; const dry = process.argv.includes("--dry"); const sql = new SQL(process.env.DATABASE_URL || "postgres://dusuniot:dusuniot@127.0.0.1:25432/dusuniot", { max: 2 }); /** # 成员变量 疑似序列化前缀(SQL LIKE 预筛用,覆盖 a:/s:/i:/b:/d:/N;) **/ const LIKE = ["a:%", "s:%", "i:%", "b:%", "d:%", "N;%"]; /** # 函数 OR 拼接 LIKE 条件(jsonb::text 对全文预筛) */ function orLike(col: string): string { return `(${LIKE.map((p) => `${col} like '${p}'`).join(" or ")})`; } let scanned = 0; let updated = 0; // ---- 1) 标量列:product_interest / message ---- for (const col of ["product_interest", "message"] as const) { const rows = await sql.unsafe( `select id, ${col} v from inquiries where ${orLike(col)} order by id`, ); for (const r of rows as { id: number; v: string }[]) { scanned++; const u = tryPhpUnserialize(r.v); if (u === undefined) continue; // message 为多行文本:数组用换行连接;product_interest 逗号连接 const cleaned = col === "message" && Array.isArray(u) ? u.filter((x) => x !== null && x !== "").map(String).join("\n") : phpToPlain(r.v); updated++; if (dry) { if (updated <= 5) console.log(`[${col}] #${r.id}: ${r.v.slice(0, 80)} -> ${cleaned.slice(0, 80)}`); continue; } await sql`update inquiries set ${sql(col)} = ${cleaned} where id = ${r.id}`; } console.log(`${col}: scanned ${rows.length} suspicious rows`); } // ---- 2) jsonb 列:fields / utm(递归还原) ---- for (const col of ["fields", "utm"] as const) { // jsonb::text 以 { 开头,序列化串出现在字符串值内部 → 包含匹配预筛(宽进严出, // deepConvertPhp 只转换能完整解析的串,LIKE 误中会被安全跳过) const contains = `(${LIKE.map((p) => `${col}::text like '%${p}' or ${col}::text like '%${p.slice(0, -1)}%'`).join(" or ")})`; const rows = await sql.unsafe( `select id, ${col} v from inquiries where ${contains} order by id`, ); for (const r of rows as { id: number; v: unknown }[]) { scanned++; const conv = deepConvertPhp(r.v); if (JSON.stringify(conv) === JSON.stringify(r.v)) continue; updated++; if (dry) { if (updated <= 5) console.log(`[${col}] #${r.id}: ${JSON.stringify(r.v).slice(0, 100)} -> ${JSON.stringify(conv).slice(0, 100)}`); continue; } await sql`update inquiries set ${sql(col)} = ${JSON.stringify(conv)}::jsonb where id = ${r.id}`; updated++; } console.log(`${col}: scanned ${rows.length} suspicious rows`); } console.log(dry ? `DRY RUN — scanned ${scanned}, would update ${updated}` : `done: scanned ${scanned}, updated ${updated} rows`); sql.end();