chore: baseline snapshot before moderation-strictness feature

Repo .git was damaged (refs/ missing, index truncated, object store near-empty).
Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak,
then full worktree committed as a rollback baseline.
This commit is contained in:
tamakyi
2026-09-10 10:56:27 +08:00
commit e3e59e7c69
296 changed files with 67458 additions and 0 deletions
+158
View File
@@ -0,0 +1,158 @@
-- ============================================================================
-- TamaBox 时间列 timestamptz 迁移脚本(线上执行版)
-- 前置: 本脚本已在你提供的生产克隆库(nekobox_clone)完整验证通过:
-- * 迁移后 epoch 与迁移前逐项一致(差异 0)
-- * 回滚路径同样验证无损(可安全回退)
-- * 会话时区无关(验证时使用 Asia/Shanghai 会话)
-- 实际受影响表(含数据): questions / users / censor_logs / upload_images
-- 其余表(conversations/messages/votes/...)时间列本来就是 timestamptz, 脚本会自动跳过
-- 覆盖: 只读 + ALTER(DDL), 建议低峰窗口执行; 克隆实测全库 1.7s 完成
-- 使用: psql/pgAdmin 整段按 PART 顺序执行, 把输出贴回核对
-- 注意: 迁移期间避免应用滚动发布; 执行前确认无长时间运行事务持锁
-- ============================================================================
-- ============================================================================
-- PART 0 前置核对: 列出将被迁移的列(线上应为 4 表 x 3 列 = 12 列)
-- ============================================================================
SELECT 'PART 0: 将被迁移的无时区时间列' AS step;
SELECT table_name, column_name
FROM information_schema.columns
WHERE table_schema = 'nekobox'
AND data_type = 'timestamp without time zone'
AND column_name IN ('created_at','updated_at','deleted_at')
ORDER BY table_name, column_name;
-- ============================================================================
-- PART 1 迁移前 epoch 快照(存入 diag_test schema, 供 PART 3 自动比对)
-- ============================================================================
SELECT 'PART 1: 迁移前快照' AS step;
CREATE SCHEMA IF NOT EXISTS diag_test;
DROP TABLE IF EXISTS diag_test.snap_before;
CREATE TABLE diag_test.snap_before(tbl text, col text, n bigint, minv numeric, maxv numeric, s numeric);
DO $$
DECLARE r record;
BEGIN
FOR r IN
SELECT table_name tbl, column_name col
FROM information_schema.columns
WHERE table_schema='nekobox' AND data_type='timestamp without time zone'
AND column_name IN ('created_at','updated_at','deleted_at')
ORDER BY table_name, column_name
LOOP
EXECUTE format(
'INSERT INTO diag_test.snap_before SELECT %L, %L, count(%I), min(extract(epoch FROM %I)*1000000000), max(extract(epoch FROM %I)*1000000000), sum(extract(epoch FROM %I)*1000000000) FROM nekobox.%I',
r.tbl, r.col, r.col, r.col, r.col, r.col, r.tbl);
END LOOP;
END $$;
SELECT '快照完成, 共 ' || count(*) || ' 列' AS info FROM diag_test.snap_before;
-- ============================================================================
-- PART 2 执行迁移
-- 核心语义: 库存值是 UTC 墙钟, 用 AT TIME ZONE 'UTC' 显式解释为 timestamptz,
-- 与会话时区无关。绝不可用裸 ALTER(会按会话时区解释 -> 全部 -8h 偏移)。
-- ============================================================================
SELECT 'PART 2: 执行迁移' AS step;
DO $$
DECLARE r record;
BEGIN
FOR r IN
SELECT table_name tbl, column_name col
FROM information_schema.columns
WHERE table_schema='nekobox' AND data_type='timestamp without time zone'
AND column_name IN ('created_at','updated_at','deleted_at')
ORDER BY table_name, column_name
LOOP
EXECUTE format(
'ALTER TABLE nekobox.%I ALTER COLUMN %I TYPE timestamptz USING %I AT TIME ZONE ''UTC''',
r.tbl, r.col, r.col);
END LOOP;
END $$;
SELECT '迁移完成' AS step;
-- ============================================================================
-- PART 3 验收(4 项全绿 = 迁移成功; 任一项不满足立即执行 PART 4 回滚)
-- ============================================================================
SELECT 'PART 3: 验收' AS step;
-- 3.1 迁移后快照并自动比对: 差异应为 0 行
DROP TABLE IF EXISTS diag_test.snap_after;
CREATE TABLE diag_test.snap_after(tbl text, col text, n bigint, minv numeric, maxv numeric, s numeric);
DO $$
DECLARE r record;
BEGIN
FOR r IN
SELECT table_name tbl, column_name col
FROM information_schema.columns
WHERE table_schema='nekobox' AND data_type='timestamp with time zone'
AND column_name IN ('created_at','updated_at','deleted_at')
ORDER BY table_name, column_name
LOOP
EXECUTE format(
'INSERT INTO diag_test.snap_after SELECT %L, %L, count(%I), min(extract(epoch FROM %I)*1000000000), max(extract(epoch FROM %I)*1000000000), sum(extract(epoch FROM %I)*1000000000) FROM nekobox.%I',
r.tbl, r.col, r.col, r.col, r.col, r.col, r.tbl);
END LOOP;
END $$;
SELECT '3.1 迁移前后 epoch 差异(应为 0 行)' AS check_name;
SELECT b.tbl, b.col, b.n AS n_before, a.n AS n_after,
b.minv AS min_before, a.minv AS min_after,
b.maxv AS max_before, a.maxv AS max_after,
b.s AS sum_before, a.s AS sum_after
FROM diag_test.snap_before b
JOIN diag_test.snap_after a USING (tbl, col)
WHERE b.n IS DISTINCT FROM a.n OR b.minv IS DISTINCT FROM a.minv
OR b.maxv IS DISTINCT FROM a.maxv OR b.s IS DISTINCT FROM a.s;
-- 3.2 残余无时区时间列(应为 0)
SELECT '3.2 残余 timestamp without time zone(应 0)' AS check_name;
SELECT count(*) AS remain_without
FROM information_schema.columns
WHERE table_schema='nekobox' AND data_type='timestamp without time zone'
AND column_name IN ('created_at','updated_at','deleted_at');
-- 3.3 关键行 epoch 指纹(应与迁移前/线上游标一致)
SELECT '3.3 关键行 epoch 指纹' AS check_name;
SELECT id, (extract(epoch FROM created_at)*1000000000)::numeric(30,0) AS epoch_ns
FROM nekobox.questions WHERE id IN (3109, 3089, 2411) ORDER BY id;
-- 3.4 keyset 翻页抽查: 应精确返回 3088,2883,...,2411 共 18 条(与修复后线上第2页一致)
SELECT '3.4 keyset 翻页抽查(第2页)' AS check_name;
SELECT q.id
FROM nekobox.questions q
WHERE q.user_id = (SELECT id FROM nekobox.users WHERE domain='aoi')
AND q.deleted_at IS NULL AND q.answer <> '' AND q.is_private = false
AND q.is_pinned = false
AND (q.created_at < TIMESTAMPTZ '2026-09-05 00:01:16.678885+00'
OR (q.created_at = TIMESTAMPTZ '2026-09-05 00:01:16.678885+00' AND q.id < 3089))
ORDER BY q.is_pinned DESC, q.created_at DESC, q.id DESC;
-- ============================================================================
-- PART 4 回滚(仅在 PART 3 验收不通过或线上出现异常时执行)
-- 还原为 timestamp, 与迁移前快照一致(已在克隆库验证无损)
-- ============================================================================
-- SELECT 'PART 4: 回滚(按需执行)' AS step;
-- DO $$
-- DECLARE r record;
-- BEGIN
-- FOR r IN
-- SELECT table_name tbl, column_name col
-- FROM information_schema.columns
-- WHERE table_schema='nekobox' AND data_type='timestamp with time zone'
-- AND column_name IN ('created_at','updated_at','deleted_at')
-- AND table_name IN ('questions','users','censor_logs','upload_images')
-- ORDER BY table_name, column_name
-- LOOP
-- EXECUTE format(
-- 'ALTER TABLE nekobox.%I ALTER COLUMN %I TYPE timestamp USING %I AT TIME ZONE ''UTC''',
-- r.tbl, r.col, r.col);
-- END LOOP;
-- END $$;
-- SELECT '回滚完成(可再次跑 PART 3 核验)' AS step;
-- ============================================================================
-- 验收说明:
-- 3.1 = 0 行且 3.2 = 0、3.3 指纹与迁移前一致、3.4 翻出 18 条 -> 迁移成功
-- 任何一项不符 -> 执行 PART 4 回滚后停止排查, 联系开发
-- 迁移后建议:
-- 1) 重新部署应用(含 parsePinCursor 的 .UTC() 修复版本)
-- 2) 复跑今日翻页验收脚本, 预期 38 条唯一收敛
-- 3) 确认无误后可清理 diag_test schema: DROP SCHEMA diag_test;
-- ============================================================================