fix(publish): 最终审查修复波——迁移索引口径 / 入库计数 / 仓储证据
C1(关键):2026-09-22-03 的街拍实体键索引改用最终表达式 (city, year, COALESCE(title, '')),与体检及 StreetSnapEntityState 口径一致。 此前索引按 (city, year) 比体检更窄,同城同年多专题的合法数据会通过体检、 再由索引抛原生 23505;04 退化为幂等兜底并在注释说明真正常态由 03 承载。 新增 db/migrations/README.md,写清迁移链执行顺序与各步前提/不可逆点。 I1:processRunway 的 image_count 改为按实际落库主图行数(countMainImages), 不再用 len(p.Looks)(空主图 / sha1 重复会被跳过,导致计数偏大且不再自愈)。 补 service 单测与「入库后 image_count == 存活主图行数」的 DB 断言。 I2:三个迁移文件不再把已删除的一次性搬迁脚本写成硬前置,改为写明取回方式 (git show c4bafc5:scripts/migrate_single_table/main.go,并须在旧代码树上运行)。 I3:新增 SetRecordStatus / SetStreetRecordStatus 仓储集成测试: pending 经仓储通过后在公开视图可见、驳回后不可见、不存在的 id 返回 ErrNotFound。 Minor:修正锁不住口径的走秀 image_count 测试(改为删主图、留细节图); 修正去重注释与事实不符(含 FindNearDuplicateImage 注释); ListRecords / ListStreetRecords 改用 Scope 杜绝 Count 后复用 *gorm.DB; dbtool 视图改 CREATE OR REPLACE 并重跑 dump(仍 4 视图、无草稿表); 删除挂在 Popular 上的「IDs 返回…」注释;README 改为单表 + status 现状; SetRecordStatus 注释写明有意不校验前置状态;规格补两条已知不一致。
This commit is contained in:
@ -101,14 +101,20 @@ const (
|
||||
```
|
||||
|
||||
- **通过**:`UPDATE ... SET status='published', reviewer=?` + 应用表单里的字段编辑。**不再触碰图片行**。
|
||||
- **驳回**:`UPDATE ... SET status='rejected', reviewer=?, reject_reason=?`。行保留。
|
||||
- **驳回**:`UPDATE ... SET status='rejected', reviewer=?, reject_reason=?`。行保留(备查)。
|
||||
- **驳回后重爬**:`rejected → pending`(复用同一行,见 §5 第 1 条第 2 支)。驳回不是永久黑名单。
|
||||
- 每条记录**只有一个实体键行**(runway: `brand_id+season_code+collection_type`;street: `city+year`),不存在"同实体的多条 pending 并存"。
|
||||
|
||||
## 5. 写入链路(ingest 直写正式表)
|
||||
|
||||
`internal/service/ingest_service.go` 的 `processRunway` / `processStreet`:
|
||||
|
||||
1. **实体键查重**:沿用 `RunwayIDByEntity` / `StreetSnapIDByEntity`(`WHERE 实体键 AND is_deleted=0`)。单表之后,该条件会同时匹配 `pending/published/rejected` 的任意行 —— 命中即整任务 `MarkDone` **直接放弃**(用户裁定),与现状对"已发布实体"的行为一致,且顺带把"重复爬取同一实体"也收敛掉。
|
||||
1. **实体键查重**(`RunwayIDByEntity` / `StreetSnapIDByEntity`,`WHERE 实体键 AND is_deleted=0`)。单表之后该条件会命中任意状态的行,因此必须**按命中行的状态分三支**:
|
||||
- 命中 `pending` / `published` → 整任务 `MarkDone`,**直接放弃**(用户裁定)。语义:该实体已收录或正在审核,不重复入库。
|
||||
- 命中 `rejected` → **复用该行**:覆盖内容字段(标题/描述/年份/季节/collection_type/season_code/cover)、软删其旧图片、写入本次抓取的图片、置回 `status='pending'` 并清空 `reviewer`/`reject_reason`。语义:驳回不是永久黑名单,重爬即重新送审。
|
||||
> ⚠️ 这一支是**必需的修正**:查重条件不带 status,若不特判 `rejected`,一条被驳回(甚至误驳)的记录会**永久挡住重爬**;而现状(草稿表)不会——查重只查正式表,驳回的草稿不挡路。
|
||||
- 未命中 → 新建 `pending` 记录。
|
||||
"同一实体只有一行"因此始终成立。
|
||||
2. **建记录**:`CreateRunwayDraft` → `CreateRunway`(写 `brand_runways`,`status='pending'`、`job_id`);`CreateStreetSnapDraft` → `CreateStreetSnap`(写 `street_snaps`)。
|
||||
3. **建图片**:把图直接写 `brand_runway_images` / `street_snap_images`。
|
||||
4. **图片去重**:`FindNearDuplicateImage` 的比对表从"草稿图 + 正式图"两张收敛为**一张正式图表**;`phash` / `is_duplicate` / `dup_of` 的写入位置不变。
|
||||
@ -175,8 +181,16 @@ CREATE VIEW public_street_snap_images AS
|
||||
|
||||
### 第 1 步:DDL + 视图(`db/migrations/2026-09-22-01-single-table-publish.sql`)
|
||||
1. §3.2 加列。
|
||||
2. 存量正式行视为已发布:`UPDATE brand_runways SET status='published' WHERE created_at < :迁移时刻;`(street 同理)。**带时间戳护栏**,避免脚本被重复执行时把新入库的 pending 行误刷成 published。
|
||||
3. 建 §6 的 4 个视图。
|
||||
2. 建 §6 的 4 个视图(视图定义必须幂等:`DROP VIEW IF EXISTS` 后重建)。
|
||||
|
||||
**存量行置已发布拆成独立的一次性文件**(`db/migrations/2026-09-22-01b-publish-existing-rows.sql`):
|
||||
|
||||
```sql
|
||||
UPDATE brand_runways SET status = 'published' WHERE status = 'pending';
|
||||
UPDATE street_snaps SET status = 'published' WHERE status = 'pending';
|
||||
```
|
||||
|
||||
为什么必须拆开:数据搬迁(第 2 步)会把 pending 草稿**连同旧的 `created_at`** 搬进正式表。若这条 UPDATE 留在可重复执行的迁移文件里(集成测试每次都会执行它),这些待审内容会被误刷成 `published` —— 恰好是本设计要防的泄漏。因此:结构 DDL 可重复执行,数据变更只执行一次,且**必须在第 2 步之前**执行。
|
||||
|
||||
> 本步必须在部署新代码**之前**应用(新代码开始写 `status`)。
|
||||
|
||||
@ -240,3 +254,5 @@ DROP TABLE IF EXISTS brand_runway_draft_images, brand_runway_drafts,
|
||||
1. `image_count` 口径:runway 只计主图,street 计全部图(含副图)。
|
||||
2. `favorites` 实建索引是 `uniq_user_target(target_uid)` 单列(代码注释写的是 `(user_id, target_uid)` 组合)——按现有 DDL,同一用户似乎只能收藏一条记录,疑似缺陷。
|
||||
3. `image_duplicates`、`image_embeddings` 两张表零引用;`street_snaps.source/source_url` 死列。可另起一个清理迁移删掉,与本次改造解耦。
|
||||
4. **标题漂移会被判为新实体**:街拍实体键含 `title`(`(city, year, COALESCE(title, ''))`),同一专题若标题只做空白 / 大小写 / 措辞微调,入库查重会当作新实体,从而多出一行(内容重复)。目前无标题归一化。
|
||||
5. **「图片 id 终身不变」不成立**:在「已发布 → 驳回下架 → 重爬复用」这条路径上,复用 `rejected` 行会软删旧图行并插入新行,图片行 id 随之变化。凡把图片 id 当稳定标识(收藏、外链、前端缓存键)的场景都需注意。
|
||||
|
||||
Reference in New Issue
Block a user