Files
dusuniot/scripts/cleanup-php-serialize.ts

67 lines
3.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* # 脚本 清洗 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();