Files
cmautobuy/docs/admin/00-glossary.md
T
chengmaandClaude Opus 5 998c06a2bf feat: PDD 商品数据独立成表 (#16)
原来 pdd_data 是 shopee_products 上的一个 JSON 字段,两个蝦皮商品指向
同一个 PDD 链接时会各存一份、各采一次;collect_status 描述的是 PDD 商品的
状态,却挂在蝦皮商品上,两份可能不一致。

更要紧的是 PDD 商品变动频繁(A 下架就得换 B),而 sku_mappings 只按
shopee_sku_id 做键——换商品后旧映射还在,B 恰好有同名规格但完全是另一件货
时会静默买错,事后查不出来。

改动
- 新增 pdd_products 表:id 主键 + goods_id UNIQUE + 4 个状态值(去掉
  no_link,「未填链接」改由 shopee_products.pdd_goods_id 为空表达)+
  软删除可复活
- shopee_products 去掉 pdd_data / collect_status / collect_error /
  collected_at,pdd_goods_id 改为引用
- sku_mappings 主键改为 (shopee_sku_id, pdd_goods_id),新增 pdd_option_key。
  查映射永远带上当前 PDD 商品,换商品后天然查不到旧映射,不需要删数据;
  换回原商品时旧映射直接复用
- 新增 OptionKey():用 json.Marshal 实现(Go 序列化 map 按键名排序,
  天然规范化),不自己拼字符串——规格文字里可能含 = 或 ;。
  存映射和查 SKU 必须用同一个函数,各写一遍会静默算出不同结果
- 采集结果改落 pdd_products,新增两条校验:
  返回的 goods_id 与请求不符 → 整体回滚拒绝(422),不静默存下;
  skus 为空数组 → 置 failed 而非 collected,否则界面显示"已采集"
  但数据毫无用处

实施时超出工单但必要的三处
- TaskExists 重构为 GetTaskInfo:原函数只返回蝦皮 goods_id,
  而采集结果要按 PDD goods_id 落库,不改取不到正确的键
- 复活时一并清空旧采集结果(skus_json / collect_msg / collected_at),
  否则复活后会显示"已采集"但数据是删除前的
- 删除 repository/shopee.go:两个函数签名全变且已迁到 pdd.go,留着是死代码

已验证(Go 1.23.0)
- go vet / gofmt / go test 全过,55 个测试
- 端到端补验了工单未覆盖的 HTTP 层:goods_id 不符返回 422
  COLLECT_GOODS_MISMATCH 且整体回滚(skus_json 空、任务仍 claimed、
  幂等记录 0 条);skus 为空返回 200 但状态 failed

遗留
- MarkCollecting / SoftDeletePddProduct 暂无调用方,等界面工单接上
- artifact_ref 存 diagnostics 原始 JSON,未按 client-001:artifacts/... 规范化,
  因 Client 侧尚未定义 diagnostics 结构
- 界面未实现(工单明确排除),四个页面仍为骨架

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 10:27:39 +08:00

65 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 00 Admin 术语表
- 文档状态:基线草案
- 用途:Admin 文档里出现的专业词,在这里查一句话解释
跨 Client 和 Admin 共用的词(幂等、SKU、采集任务、采购任务、便携模式……)
在 [Client 术语表](../client/00-glossary.md) 里,本文不重复。
## 1. 业务名词
| 词 | 一句话解释 | 在本项目里指 |
|---|---|---|
| 蝦皮 / Shopee | 台湾的电商平台,**我们在上面卖货** | 订单的来源。导出的报表是繁体中文、金额是台币 |
| 顺运宝 / SYB | 物流服务商,提供**货运单** | 告诉我们"这单要发什么货",是采购的触发源 |
| 货运单 | 顺运宝那边的一条发货记录 | `syb_orders` 表的一行 |
| 拼多多 / PDD | 大陆的电商平台,**我们在上面进货** | 采购的目标平台 |
| 商品規格ID | 蝦皮给每个 SKU 的唯一编号 | `shopee_skus.sku_id`。实测 6092 条零重复,是天然主键 |
| 规格原文 | 蝦皮报表里没拆开的那一列,例如 `黑色,M【建議40-50公斤】` | `shopee_skus.spec_raw`,**永远原样保留** |
| 建议 | 蝦皮规格里的建议体重,例如 `40-50公斤` | 不是"建议采购链接",别理解错 |
| SKU 映射 | "蝦皮的这个规格 = 拼多多的那个规格"的对应关系 | `sku_mappings` 表。**匹配一次,以后同商品自动带出** |
| 采集状态 | 一个 **PDD 商品**的数据采到没有 | `pdd_products.collect_status`。挂在 PDD 商品上,不在蝦皮商品上——被采集的是 PDD 商品 |
## 2. 技术名词
| 词 | 一句话解释 | 在本项目里指 |
|---|---|---|
| Gin | Go 的一个 Web 框架,负责把 URL 路由到你的函数 | 唯一允许使用的 Web 框架 |
| `html/template` | Go 自带的 HTML 模板引擎,**会自动转义**,防 XSS | 所有页面都用它渲染,不引入前端框架 |
| 服务端渲染 | 页面的 HTML 在服务器上拼好再发给浏览器 | 与之相对的是前端框架在浏览器里拼,本项目**不用** |
| htmx | 一个单文件 JS 库,让 HTML 标签直接发请求换局部内容 | 需要局部刷新时可用,**没有构建步骤** |
| upsert | "有就更新、没有就新增",一次操作搞定 | Excel 导入的唯一正确做法,见 §3 |
| cgo | Go 调用 C 代码的机制。**用了就需要装 C 编译器** | 本项目**避开它**,所以 SQLite 驱动选纯 Go 的 |
| `modernc.org/sqlite` | 纯 Go 实现的 SQLite,不需要 cgo | 固定用它,`go build` 直接出 exe |
| excelize | Go 读写 Excel 的库 | 固定用它读蝦皮报表 |
| CSRF | 攻击者诱导你在已登录状态下发出非本意的请求 | 所有写操作都要防,见 [06](06-quality-security.md) §4 |
| 参数化查询 | SQL 里用 `?` 占位、值单独传,而不是拼字符串 | 防 SQL 注入的唯一正确做法 |
## 3. 为什么导入必须是 upsert
这条单独说,因为**做错了会丢数据**。
蝦皮报表可以反复导入(每次导出的都是"最近有成绩的商品",不是全量)。
而 **PDD 链接是人工一条条填上去的**,只存在我们自己的库里,报表里没有。
所以导入时:
| 做法 | 后果 |
|---|---|
| 先 `DELETE` 再 `INSERT` | **人工填的 PDD 链接、SKU 映射全没了** ✗ |
| 按主键 upsert | 报表里有的字段更新,人工填的字段原样保留 ✓ |
主键:商品用 `商品ID`,SKU 用 `商品規格ID`。
## 4. 蝦皮报表的两层结构
一个文件里混了两种行,别当成一种:
| 行类型 | 判断方法 | 条数(样本) | 导入到 |
|---|---|---|---|
| 商品汇总行 | `商品規格ID` 是 `-` | 5195 | `shopee_products` |
| SKU 行 | `商品規格ID` 是数字 | 6092 | `shopee_skus` |
平均每个商品只有 1.17 个 SKU —— 因为这份报表**只包含有销售成绩的 SKU,不是完整目录**。
所以订单来了查不到 SKU 是正常现象,界面必须支持**手动新增**,见 [01 需求](01-requirements.md) §4.1。