67 lines
3.1 KiB
TypeScript
67 lines
3.1 KiB
TypeScript
/**
|
||
* # 脚本 清洗 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();
|