图片不再随数据集删除:加「图片挂载恢复」,place_images 摘掉外键

用户报「之前上传的照片找不到了,应该是因为清了应用数据换基线」。查下来与 App 侧无关,
是站点自己两套口径打架:

- place_images.dataset_id 带 ON DELETE CASCADE,而读取按 place_key 坐标键、跨数据集。
- 于是删掉旧数据集时图片**行**被级联删了,**文件却留在 media 目录里**。
- 实测(本机):datasets 只剩 155 号、place_images 0 行、盘上 4 个 .webp 好端端躺着。
  同一批附属数据里备注(17)与结论(3+1)因为早就迁到坐标键,毫发无损——只有图片倒霉。

三件事:

1) 恢复:后台首页「图片挂载恢复 → 扫描并恢复」(POST /admin/images/recover,
   internal/web/image_recovery.go)。只插行、不删文件、不看数据集,按 object_key 判重可反复点。
   认地点两处来源:数据集还在就查它的 places;数据集已删就读它留下的 baselines/<id>.sqlite
   还原「地点号 → 坐标」。对象键两种历史布局都认(<ds>/<YYYYMM>/<srcPK>-<hash>.webp 与
   <ds>/<srcPK>/<hash>.webp);判据先看文件名有没有 `<数字>-` 前缀,再看中间目录——
   地点号也可能刚好 6 位,只看位数会认错。

2) 防复发:CREATE TABLE place_images 去掉外键 + 幂等 DROP CONSTRAINT;删图接口不再按
   dataset_id 过滤(数据集删了也得能删图);「删除数据集」的二次确认文案改成实话。

3) 报告同类陷阱(未改):topics 仍带 CASCADE,专题随数据集一起删且没有恢复来源。

测试踩到的坑:图片「按坐标键跨数据集读」+「按 object_key 判重」都不看数据集,用例之间会
互相污染两处——盘上文件(报「应当 2 张、实际 10 张」)与库里的行(Already:1 让兜底分支
根本没跑到,用例差点假绿)。加 isolateImageState(换空 DataDir + 按 place_key 清历史行),
并给夹具补收尾清理(外键没了必须自己清)。

新增用例 4 条;验收:./scripts/verify.sh 全量 PASS=323 FAIL=0 SKIP=0。
This commit is contained in:
tamakyi
2026-09-30 19:45:51 +08:00
parent 52a82631da
commit 496c992e5e
12 changed files with 900 additions and 7 deletions
+10 -1
View File
@@ -437,9 +437,15 @@ ALTER TABLE places ADD COLUMN IF NOT EXISTS src_lon DOUBLE PRECISION;
-- 地点图片:站长给地点配的图。字节由**浏览器**转成 WebP 后再上传(服务端只负责收下与转存),
-- 1 核机器不用扛解码。storage 记这一张实际放在哪、object_key 是本地相对路径或对象存储 key:
-- 换了存储后端之后旧图仍按各自那一列去取,不会因为改了设置就全变破图。
--
-- ⚠️ dataset_id **故意不带外键**(早期版本带 ON DELETE CASCADE,后果见下)。
-- 图片的「认地点」靠坐标键 place_key,与数据集无关;数据集只是当初上传时的落点。
-- 带 CASCADE 时删除旧数据集会把图片行一并删掉,而**文件还留在磁盘上**——
-- 读取按坐标键、行却按数据集级联,两套口径打架,实测就丢过一批图(2026-09-30)。
-- 备注/结论早就改成坐标键了,图片这里跟上,dataset_id 退化成「出处」这一列。
CREATE TABLE IF NOT EXISTS place_images (
id BIGSERIAL PRIMARY KEY,
dataset_id BIGINT NOT NULL REFERENCES datasets(id) ON DELETE CASCADE,
dataset_id BIGINT NOT NULL,
src_pk INT NOT NULL,
-- place_key 与备注、结论共用同一套坐标键:换数据集后图片还挂得回原地点。
-- 对象键里仍然带 dataset_id(那只是存储路径,与「认不认得出是同一个地点」无关)。
@@ -455,6 +461,9 @@ CREATE TABLE IF NOT EXISTS place_images (
);
CREATE INDEX IF NOT EXISTS idx_place_images_place ON place_images(dataset_id, src_pk, position);
-- 老库摘掉那根外键(新库上面的 CREATE 里就没有)。幂等:约束不在时什么都不做。
ALTER TABLE place_images DROP CONSTRAINT IF EXISTS place_images_dataset_id_fkey;
-- ---------- 附属数据改挂坐标键(一次性迁移)----------
-- 备注 / 结论 / 图片原本挂 (dataset_id, src_pk),换个数据集就全看不见了。
-- 这里把它们搬到坐标键上;只在「还没有 place_key 列」时执行,所以新库直接走上面的