v0.16.0: 后台体验升级——i18n(默认英文)/浅深主题/字号放大/关站维护页/个人设置(头像·语言·主题·密码·MCP Token)/权限管理(菜单+MCP·API)/询盘抽屉翻倍+PHP序列化数据清洗(22.7万行)+去裸JSON

This commit is contained in:
bigcode
2026-11-05 01:46:00 +08:00
parent c3df5d9db1
commit a018019504
47 changed files with 3008 additions and 830 deletions
+66
View File
@@ -0,0 +1,66 @@
/**
* # 脚本 清洗 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();