fix: 修复迁移被原地改写导致老库缺表 (#20)

#16 原地改写了 migration v1 而不是新增一条。Migrate 只在
user_version < 版本数时才跑,老库版本号已越过 v1,改写后的语句
永远不会重跑——程序拿着对不上的库静默启动,点到 PDD 商品页才 500。

修法:v1 逐字恢复成 7ad82b7 的原样,#16 的结构改动全部挪进 v3。
全新库也走 v1→v2→v3,与老库升级跑的是同一份 v3 代码,
不需要维护两条路径。

v3 必须认两种 user_version=2:#16 的原地改写让这个版本号对应
两种不同结构(原始 v1 建的没有 pdd_products,改写后的 v1 建的已经有)。
所以 v3 每一步先查 PRAGMA table_info / sqlite_master 看实际结构
再决定做不做,只有版本号推进是无条件的;已是最终结构的库
只推版本号,日志也照实说,不谎称"新增 pdd_products"。

旧 sku_mappings 数据丢弃并打日志:新主键需要 pdd_option_key,
那是 Go 的 OptionKey() 用 json.Marshal 算的,SQL 复现不了。
硬凑一个键出来,轻则映射静默失效,重则撞上别的规格静默买错东西——
后者正是 #16 存在的全部意义。

collecting 映射成 pending:原样保留会让 MarkCollecting 永远不成功,
那个商品再也建不了采集任务,界面上表现为按钮永远置灰且无法解开。

表重建按 SQLite 官方 12 步顺序:先建 _new 再 RENAME。
实测 ALTER TABLE RENAME TO 会自动重写别的表里指向它的外键子句,
先 RENAME 让位会把 shopee_skus 的外键改成指向一张马上被删的表。

另加两道闸:启动时 CheckSchema 缺表即拒绝启动(不是警告后继续);
admin/AGENTS.md 写死"migrations 只追加、不得修改已发布条目"。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
chengma
2026-08-07 12:23:24 +08:00
co-authored by Claude Opus 5
parent 03f91c2488
commit d2de7331bb
5 changed files with 1433 additions and 45 deletions
+29
View File
@@ -86,6 +86,35 @@ SQLite 同一时刻只允许一个写事务,连接放太开会互相抢锁、
`[必须]` 加新版本时**只能往末尾追加** `migrations`,不许改动已有元素——
已经发布出去的库是按旧语句建的,改了会导致新旧库结构不一致。
### 2.3 迁移版本历史
| 版本 | 做了什么 |
|---|---|
| v1 | 初始表结构:`shopee_products`(当时还带着 `pdd_data`/`collect_status` 等采集字段)、`shopee_skus`、`syb_orders`、`sku_mappings`(当时主键只有 `shopee_sku_id`)、`tasks`、`clients`、`idempotency_keys`。 |
| v2 | 新增 `task_claims`(领取历史,见 §8)。 |
| v3 | 把 PDD 采集数据从 `shopee_products` 拆到独立的 `pdd_products`(本文档 §4 描述的最终结构);重建 `shopee_products`,去掉已经搬走的四个字段;重建 `sku_mappings`,主键改成 `(shopee_sku_id, pdd_goods_id)`(§6.1 的理由)。 |
**v3 为什么丢弃旧 `sku_mappings` 数据(见 #20):** 新主键需要 `pdd_option_key`,
这是 Go 的 `service.OptionKey()` 用 `json.Marshal` 算出来的规范化键,SQL 语句
复现不了。硬凑一个键出来有两种后果:算错了会让映射静默失效,需要人工重新匹配一遍;
算的时候恰好和别的规格撞了键,会**静默买错东西,且事后查不出来**——这正是引入
`pdd_goods_id` 做主键要防的问题,不能在迁移里重新引入。当时匹配界面还没做,
所以迁移时不可能存在真实映射数据,丢弃的代价很小。丢弃的行数会打进启动日志
(`迁移 v3:丢弃了 N 条旧规格映射...`),不会静默丢。
`[必须]` v1 曾经在 #16 里被原地改写(直接把 `pdd_products` 等结构塞进 v1,
没有新增版本),导致已经建过库、`user_version` 已经越过 v1 的老机器永远不会
重跑改写后的语句,程序拿着一个和代码对不上的库静默启动,界面点到 PDD 商品页
才报 500。#20 把 v1 恢复成原样、改动挪进新增的 v3,并加了启动时 schema 自检
(缺表直接拒绝启动,见下)作为兜底。
### 2.4 启动时 schema 自检
`[必须]` `Migrate` 成功后,`repository.CheckSchema` 会检查代码依赖的表是否都在,
缺了就返回错误、**拒绝启动**,不是打个警告继续跑——静默启动正是 #20 的教训:
错误要等操作员点到那个页面才暴露,如果那是个写操作页面,暴露出来的就不是报错
而是写坏数据。自检只查表名,不逐列校验:够抓住"迁移没跑到"这一类问题,代价也低。
## 3. 蝦皮数据
蝦皮报表**一个文件里混了两层数据**,所以拆成两张表。