Admin:采集结果增加店铺名与价格采样粒度 #31

Closed
opened 2026-08-07 16:51:47 +08:00 by ila · 3 comments
Owner

基本信息

  • 类型:需求(含接口契约与数据库结构变更)
  • 父级大工单:#14
  • 所属 MVP / 版本:#15 / MVP
  • 关联:Client 采集执行工单(本单是它的前置)
  • 前置:#20(迁移只追加的规则,本单必须遵守)

要解决什么

Client 采集回来的数据里要加两样现在契约没有的东西:

  1. 店铺名 —— 同一个商品标题在不同店铺都有,价格和靠谱程度不一样。
    操作员要能核对「是不是我要的那家店」,光看标题分不出来。
  2. 价格采样粒度标记 —— 见下。

为什么需要粒度标记

PDD 的规格面板只显示当前选中组合的价格(实测样本
client/image_xml/737116531267_size.xml:8 个颜色选项旁边没有各自价格,
整屏只有一处 折后¥4.7 / ¥19.9)。

要拿每个颜色的价格必须逐个点击。已定案:按颜色采样(点 8 次),
而不是按颜色×尺码全遍历(点约 48 次)。

这意味着「黑色/2XL」的价格是从「黑色/某个尺码」推出来的,不是实测的。
如果该商品大码加价,记录的价格会偏低,下单时的价格保护会误判。

[必须] 所以必须把这件事标进数据里,不能让下游以为每个价格都是实测的。

做什么 / 不做什么

做:

  1. pdd_products 增加 shop_name 列(新增 migration v4)
  2. submit.go 从采集结果里取出 shop_name 落库
  3. 列表页和详情弹窗显示店铺名
  4. 详情弹窗在价格按颜色采样时给出提示
  5. 更新两侧契约文档

不做:

  • 不改 Client 任何代码
  • 不做按店铺搜索/筛选
  • 不改 skus[] 里已有的字段含义

怎么做

migration 只能追加,不许改 v1/v2/v3

[必须] 新增 v4,ALTER TABLE pdd_products ADD COLUMN shop_name TEXT。

理由见 #20:#16 曾经原地改写 v1,导致已经建过库的机器版本号越过它、
永远不重跑,程序拿着对不上的库静默启动,点到页面才 500。
admin/AGENTS.md 已写死「migrations 只追加」这条 [必须]。

[必须] #20 建立的三起点收敛测试要把 v4 覆盖进去——
全新库 / v2 老结构库 / v2 新结构库,迁到最新后 schema 必须一致。

[必须] shop_name 可空。老数据没有它;Client 采不到时也留空。
不要加 NOT NULL DEFAULT ''——空字符串和「没采到」会分不清。

契约新增两个字段

Client 提交的 pdd_data 增加:

{
  "goods_id": "737116531267",
  "title": "【店铺热销】冰丝吊带裙女夏季薄款内搭连衣裙宽松a字裙中长款打底裙衬裙纯色",
  "shop_name": "XX旗舰店",
  "price_granularity": "color",
  "dimensions": [
    {"key": "color", "name": "颜色分类"},
    {"key": "size",  "name": "尺码"}
  ],
  "skus": [
    {
      "options": {"color": "黑色中长款", "size": "2XL"},
      "price_cent": 470,
      "list_price_cent": 1990,
      "price_observed_at": {"color": "黑色中长款", "size": "2XL"},
      "available": true,
      "raw_price": "折后¥4.7"
    }
  ]
}

[必须] price_granularity 取值 "color" 或 "sku"。
"color" 表示价格是按颜色采样的,同颜色下所有尺码共用一个价格。

[必须] price_observed_at 记录读到这个价格时实际选中的是哪个组合。
「黑色/M」和「黑色/2XL」都写 470,但只有其中一个是实测的——
这个字段说清是哪个。下单时价格保护据此判断「这个价格是实测还是推断」。

[建议] list_price_cent 是划线价(样本里的 ¥19.9),可选。

[必须] price_cent 存的是实付价(样本里的 折后¥4.7 / 券后¥4.7),
不是划线价。价格保护比的是会真的花出去的钱。

[必须] 未知字段照旧忽略(Go 的 json.Unmarshal 默���行为),
老版本 Client 不带这些字段时不得报错。

落库

[必须] shop_name 落到 pdd_products.shop_name 列。

[必须] price_granularity 和 price_observed_at 不单独建列,
留在 skus_json 里。理由:它们是随采集结果整体变化的,
拆出来会和 skus_json 各存一份、可能不一致。

[必须] 采集结果里没有 shop_name 时不要覆盖已有值。
Client 这次没采到不代表上次采到的作废了。

界面

列表页表格加一列「店铺」,截断显示,未采到显示 —。

详情弹窗在商品信息区显示店铺名。

[必须] price_granularity == "color" 时,规格表上方给一句提示:

价格按颜色采样,同一颜色下各尺码显示同一价格。
标 ✓ 的是实测价,其余为推断值。

并在实测的那一行(options 与 price_observed_at 相同)标记出来。

理由:不标的话,操作员会以为每个价格都是实测的。
将来大码加价的商品会按低价下单,而没人知道数据是推断来的。

[必须] 状态和标记不能只靠颜色(docs/admin/05 §10)。

预计修改文件

文件 改什么
admin/repository/db.go migration v4;CheckSchema 覆盖新列
admin/repository/migrate_test.go 三起点收敛测试覆盖 v4
admin/repository/pdd.go pddColumns 加 shop_name;SetCollectResult 落库
admin/model/model.go PddProduct.ShopName
admin/service/submit.go collectedData 加 ShopName
admin/service/pdd.go 详情视图带出店铺名、粒度提示、实测标记
admin/templates/pdd/list.html / edit_modal.html 显示
docs/admin/03-data-model.md / 04-client-api.md 契约与表结构
docs/client/04-admin-api-contract.md Client 侧同步

验收标准

  • migration v4 是新增的,v1/v2/v3 一个字节都没改
  • 三起点收敛测试通过且覆盖 v4
  • shop_name 可空,老库迁移后为 NULL 不报错
  • 提交带 shop_name → 落库并在列表和弹窗显示
  • 提交不带 shop_name → 不覆盖已有值,不报错
  • price_granularity == "color" 时弹窗有提示文字
  • 实测那一行有文字标记,不只靠颜色
  • price_cent 取实付价的规则写进文档
  • 老版本 Client 的报文(无新字段)仍能正常提交
  • go vet / gofmt -l . / go test ./... 全过
  • 五个页面均 200

怎么验证

cd D:\chengma\cmautobuy\admin
go vet ./...; gofmt -l .; go test ./... -count=1
go test ./repository/ -run Migrate -v -count=1

端到端:建商品 → 建采集任务 → curl 模拟客户端提交上面那段带
shop_name / price_granularity 的报文 → 打开 /pdd 确认店铺列有值、
弹窗有粒度提示和实测标记。

再提交一次不带 shop_name 的报文,确认原值还在。

风险和回退

风险 应对
又一次原地改迁移 已列为第一条验收项,且 #20 的收敛测试会抓到
操作员把推断价当实测价 弹窗提示 + 逐行标记,已列为验收项
shop_name 被空值覆盖 已列为验收项

回退:git revert。已迁到 v4 的库回退后会因「版本高于程序支持」拒绝启动,
这是 Migrate 已有的正确行为。

## 基本信息 - 类型:需求(含接口契约与数据库结构变更) - 父级大工单:#14 - 所属 MVP / 版本:#15 / MVP - 关联:Client 采集执行工单(本单是它的前置) - 前置:#20(迁移只追加的规则,本单必须遵守) ## 要解决什么 Client 采集回来的数据里要加两样现在契约没有的东西: 1. **店铺名** —— 同一个商品标题在不同店铺都有,价格和靠谱程度不一样。 操作员要能核对「是不是我要的那家店」,光看标题分不出来。 2. **价格采样粒度标记** —— 见下。 ### 为什么需要粒度标记 PDD 的规格面板**只显示当前选中组合的价格**(实测样本 `client/image_xml/737116531267_size.xml`:8 个颜色选项旁边没有各自价格, 整屏只有一处 `折后¥4.7` / `¥19.9`)。 要拿每个颜色的价格必须逐个点击。已定案:**按颜色采样**(点 8 次), 而不是按颜色×尺码全遍历(点约 48 次)。 这意味着「黑色/2XL」的价格是**从「黑色/某个尺码」推出来的,不是实测的**。 如果该商品大码加价,记录的价格会偏低,**下单时的价格保护会误判**。 `[必须]` 所以必须把这件事**标进数据里**,不能让下游以为每个价格都是实测的。 ## 做什么 / 不做什么 做: 1. `pdd_products` 增加 `shop_name` 列(**新增 migration v4**) 2. `submit.go` 从采集结果里取出 `shop_name` 落库 3. 列表页和详情弹窗显示店铺名 4. 详情弹窗在价格按颜色采样时给出提示 5. 更新两侧契约文档 不做: - 不改 Client 任何代码 - 不做按店铺搜索/筛选 - 不改 `skus[]` 里已有的字段含义 ## 怎么做 ### migration 只能追加,不许改 v1/v2/v3 `[必须]` **新增 v4**,`ALTER TABLE pdd_products ADD COLUMN shop_name TEXT`。 理由见 #20:#16 曾经原地改写 v1,导致已经建过库的机器版本号越过它、 永远不重跑,程序拿着对不上的库静默启动,点到页面才 500。 `admin/AGENTS.md` 已写死「migrations 只追加」这条 `[必须]`。 `[必须]` #20 建立的**三起点收敛测试**要把 v4 覆盖进去—— 全新库 / v2 老结构库 / v2 新结构库,迁到最新后 schema 必须一致。 `[必须]` `shop_name` **可空**。老数据没有它;Client 采不到时也留空。 不要加 `NOT NULL DEFAULT ''`——空字符串和「没采到」会分不清。 ### 契约新增两个字段 Client 提交的 `pdd_data` 增加: ```json { "goods_id": "737116531267", "title": "【店铺热销】冰丝吊带裙女夏季薄款内搭连衣裙宽松a字裙中长款打底裙衬裙纯色", "shop_name": "XX旗舰店", "price_granularity": "color", "dimensions": [ {"key": "color", "name": "颜色分类"}, {"key": "size", "name": "尺码"} ], "skus": [ { "options": {"color": "黑色中长款", "size": "2XL"}, "price_cent": 470, "list_price_cent": 1990, "price_observed_at": {"color": "黑色中长款", "size": "2XL"}, "available": true, "raw_price": "折后¥4.7" } ] } ``` `[必须]` `price_granularity` 取值 `"color"` 或 `"sku"`。 `"color"` 表示价格是按颜色采样的,同颜色下所有尺码共用一个价格。 `[必须]` `price_observed_at` 记录**读到这个价格时实际选中的是哪个组合**。 「黑色/M」和「黑色/2XL」都写 470,但只有其中一个是实测的—— 这个字段说清是哪个。下单时价格保护据此判断「这个价格是实测还是推断」。 `[建议]` `list_price_cent` 是划线价(样本里的 `¥19.9`),可选。 `[必须]` `price_cent` 存的是**实付价**(样本里的 `折后¥4.7` / `券后¥4.7`), 不是划线价。价格保护比的是会真的花出去的钱。 `[必须]` 未知字段照旧忽略(Go 的 `json.Unmarshal` 默���行为), 老版本 Client 不带这些字段时不得报错。 ### 落库 `[必须]` `shop_name` 落到 `pdd_products.shop_name` 列。 `[必须]` `price_granularity` 和 `price_observed_at` **不单独建列**, 留在 `skus_json` 里。理由:它们是随采集结果整体变化的, 拆出来会和 `skus_json` 各存一份、可能不一致。 `[必须]` 采集结果里没有 `shop_name` 时**不要覆盖已有值**。 Client 这次没采到不代表上次采到的作废了。 ### 界面 列表页表格加一列「店铺」,截断显示,未采到显示 `—`。 详情弹窗在商品信息区显示店铺名。 `[必须]` `price_granularity == "color"` 时,规格表上方给一句提示: ```text 价格按颜色采样,同一颜色下各尺码显示同一价格。 标 ✓ 的是实测价,其余为推断值。 ``` 并在实测的那一行(`options` 与 `price_observed_at` 相同)标记出来。 理由:不标的话,操作员会以为每个价格都是实测的。 将来大码加价的商品会按低价下单,而没人知道数据是推断来的。 `[必须]` 状态和标记**不能只靠颜色**(`docs/admin/05` §10)。 ## 预计修改文件 | 文件 | 改什么 | |---|---| | `admin/repository/db.go` | migration v4;`CheckSchema` 覆盖新列 | | `admin/repository/migrate_test.go` | 三起点收敛测试覆盖 v4 | | `admin/repository/pdd.go` | `pddColumns` 加 `shop_name`;`SetCollectResult` 落库 | | `admin/model/model.go` | `PddProduct.ShopName` | | `admin/service/submit.go` | `collectedData` 加 `ShopName` | | `admin/service/pdd.go` | 详情视图带出店铺名、粒度提示、实测标记 | | `admin/templates/pdd/list.html` / `edit_modal.html` | 显示 | | `docs/admin/03-data-model.md` / `04-client-api.md` | 契约与表结构 | | `docs/client/04-admin-api-contract.md` | Client 侧同步 | ## 验收标准 - [x] migration **v4 是新增的**,v1/v2/v3 一个字节都没改 - [x] 三起点收敛测试通过且覆盖 v4 - [x] `shop_name` 可空,老库迁移后为 NULL 不报错 - [x] 提交带 `shop_name` → 落库并在列表和弹窗显示 - [x] 提交**不带** `shop_name` → 不覆盖已有值,不报错 - [x] `price_granularity == "color"` 时弹窗有提示文字 - [x] 实测那一行有**文字标记**,不只靠颜色 - [x] `price_cent` 取实付价的规则写进文档 - [x] 老版本 Client 的报文(无新字段)仍能正常提交 - [x] `go vet` / `gofmt -l .` / `go test ./...` 全过 - [x] 五个页面均 200 ## 怎么验证 ```powershell cd D:\chengma\cmautobuy\admin go vet ./...; gofmt -l .; go test ./... -count=1 go test ./repository/ -run Migrate -v -count=1 ``` 端到端:建商品 → 建采集任务 → curl 模拟客户端提交上面那段带 `shop_name` / `price_granularity` 的报文 → 打开 `/pdd` 确认店铺列有值、 弹窗有粒度提示和实测标记。 再提交一次**不带** `shop_name` 的报文,确认原值还在。 ## 风险和回退 | 风险 | 应对 | |---|---| | 又一次原地改迁移 | 已列为第一条验收项,且 #20 的收敛测试会抓到 | | 操作员把推断价当实测价 | 弹窗提示 + 逐行标记,已列为验收项 | | `shop_name` 被空值覆盖 | 已列为验收项 | 回退:`git revert`。已迁到 v4 的库回退后会因「版本高于程序支持」拒绝启动, 这是 `Migrate` 已有的正确行为。
Author
Owner

实施进度:代码完成,待提交

已完成:

  • 新增 schema v4:pdd_products.shop_name TEXT,v1/v2 迁移原文和 v3 建表 SQL 未改写;
  • Migrate 明确按 v1/v2 → 特殊 v3 → v4 顺序执行;
  • CheckSchema 增加 shop_name 关键列检查;
  • 采集结果解析 shop_name、price_granularity、price_observed_at;
  • 店铺名落库,缺失或空值时保留已有店铺名;
  • 完整 pdd_data 继续保存在 skus_json,未拆分粒度字段;
  • PDD 列表增加店铺列,详情增加店铺和按颜色采样提示;
  • 规格表用“✓ 实测 / 推断”文字标记价格来源,不只依赖颜色;
  • Admin 与 Client 两侧契约文档已同步;
  • 新增迁移、兼容、落库、界面模型和五页面 HTTP 冒烟测试。

验证结果:

gofmt -l .                         通过(无输出)
go vet ./...                       通过
go build ./...                     通过
go test ./... -count=1             通过
go test ./repository/ -run Migrate -v -count=1  通过
git diff --check                   通过

五个主页面 /shopee、/pdd、/syb、/tasks、/clients 均由 httptest 验证为 200;PDD 详情同时验证店铺名、粒度提示、“✓ 实测”和“推断”均已渲染。

## 实施进度:代码完成,待提交 已完成: - 新增 schema v4:`pdd_products.shop_name TEXT`,v1/v2 迁移原文和 v3 建表 SQL 未改写; - Migrate 明确按 v1/v2 → 特殊 v3 → v4 顺序执行; - CheckSchema 增加 `shop_name` 关键列检查; - 采集结果解析 `shop_name`、`price_granularity`、`price_observed_at`; - 店铺名落库,缺失或空值时保留已有店铺名; - 完整 `pdd_data` 继续保存在 `skus_json`,未拆分粒度字段; - PDD 列表增加店铺列,详情增加店铺和按颜色采样提示; - 规格表用“✓ 实测 / 推断”文字标记价格来源,不只依赖颜色; - Admin 与 Client 两侧契约文档已同步; - 新增迁移、兼容、落库、界面模型和五页面 HTTP 冒烟测试。 验证结果: ```text gofmt -l . 通过(无输出) go vet ./... 通过 go build ./... 通过 go test ./... -count=1 通过 go test ./repository/ -run Migrate -v -count=1 通过 git diff --check 通过 ``` 五个主页面 `/shopee`、`/pdd`、`/syb`、`/tasks`、`/clients` 均由 `httptest` 验证为 200;PDD 详情同时验证店铺名、粒度提示、“✓ 实测”和“推断”均已渲染。
Author
Owner

实施完成,等待用户验收

  • 实现提交:93beef8 feat: 保存并展示 PDD 店铺与价格采样信息 (#31)
  • 归档提交:61d8d1b docs: 归档任务 #31
  • 本地归档:docs/task/31-admin-pdd-店铺与价格采样粒度.md

自动验证全部通过。未执行的仅有:真实 Client/Android 端到端提交(由 #32 接通)和真实浏览器 1366×768 人工视觉检查。工单保持开启,等待用户验收。

## 实施完成,等待用户验收 - 实现提交:`93beef8` `feat: 保存并展示 PDD 店铺与价格采样信息 (#31)` - 归档提交:`61d8d1b` `docs: 归档任务 #31` - 本地归档:`docs/task/31-admin-pdd-店铺与价格采样粒度.md` 自动验证全部通过。未执行的仅有:真实 Client/Android 端到端提交(由 #32 接通)和真实浏览器 1366×768 人工视觉检查。工单保持开启,等待用户验收。
Author
Owner

验收通过,关闭

  • 实现提交: 93beef8 feat: 保存并展示 PDD 店铺与价格采样信息 (#31)
  • 归档: docs/task/31-admin-pdd-店铺与价格采样粒度.md(状态已更新,4022fee)

架构角色复核要点:

  • 迁移只追加(#20 立的规则):v4 用 ALTER TABLE pdd_products ADD COLUMN shop_name,v1/v2 原文未动,注释里写明了「已经发布的 v1/v2 原文保持不动,老库才能可靠地逐版升级」
  • CheckSchema 覆盖了 shop_name 新列
  • collectedData 解析 shop_name / price_granularity / list_price_cent / price_observed_at,字段名与 Client 侧 SkuResult 完全一致

遗留

sales(已拼数量)和 reviews(评价数量)Client 已在采集并提交(#28 #40),但 Admin 的 collectedData 未解析——数据保留在 skus_json blob 里没有丢,但没有列、不显示、不可查。

这两个是商品热度参考,不参与下单和价格保护,优先级低。要在界面上显示的话需另开工单。

## 验收通过,关闭 - **实现提交:** `93beef8` feat: 保存并展示 PDD 店铺与价格采样信息 (#31) - **归档:** `docs/task/31-admin-pdd-店铺与价格采样粒度.md`(状态已更新,`4022fee`) 架构角色复核要点: - **迁移只追加**(#20 立的规则):v4 用 `ALTER TABLE pdd_products ADD COLUMN shop_name`,v1/v2 原文未动,注释里写明了「已经发布的 v1/v2 原文保持不动,老库才能可靠地逐版升级」 - `CheckSchema` 覆盖了 `shop_name` 新列 - `collectedData` 解析 `shop_name` / `price_granularity` / `list_price_cent` / `price_observed_at`,字段名与 Client 侧 `SkuResult` 完全一致 ### 遗留 `sales`(已拼数量)和 `reviews`(评价数量)Client 已在采集并提交(#28 #40),但 Admin 的 `collectedData` **未解析**——数据保留在 `skus_json` blob 里没有丢,但没有列、不显示、不可查。 这两个是商品热度参考,不参与下单和价格保护,优先级低。要在界面上显示的话需另开工单。
ila closed this issue 2026-08-09 10:19:11 +08:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: chengma/cmautobuy#31