Files
cmautobuy/docs/admin/01-requirements.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

13 KiB
Raw Blame History

01 Admin 产品需求基线

  • 文档状态:基线草案,待需求评审
  • 适用范围:admin/
  • 产品类型:本地运行的 Web 管理端(Go + Gin + HTML 模板)

本文档中 [必须] / [建议] / [待定] 的含义见 文档索引。 没有标注的默认是 [必须]。看不懂的词查 术语表。

1. 背景与目标

我们在蝦皮(台湾)卖货,在拼多多(大陆)进货,中间由顺运宝提供货运单。 Admin 的职责是把这三方的数据串起来,最终产出 Client 能执行的采购任务。

产品目标:

  1. 导入并维护蝦皮商品档案,为每个商品登记对应的拼多多链接。
  2. 同步顺运宝货运单,知道"这单该买什么"。
  3. 把蝦皮的颜色尺码,对应到拼多多的颜色尺码,且这个对应关系可复用。
  4. 生成采购任务并分配给指定客户端,跟踪执行结果。
  5. 管理客户端清单,知道谁在干活。

2. 用户与外部系统

  • **操作人员:**导入报表、填 PDD 链接、发起采集、做规格匹配、创建并分配任务、处理异常。
  • **Client:**领取任务、在安卓设备上执行、提交结果。契约见 Client 侧文档。
  • **蝦皮:**只提供 Excel 报表导出,没有接口对接。
  • **顺运宝:**提供货运单,[待定] 同步方式待确认,MVP 先做占位按钮。
  • **拼多多:**由 Client 操作,Admin 不直接接触。

3. 完整业务链路

这条链路是理解全部四个模块的关键,先看懂它:

蝦皮报表 ──导入──→ 商品表 + SKU 表
                        │
              编辑弹窗:人工填 PDD 链接
                        │ 点【采集】
                        ↓
                  创建采集任务 → 分配 Client → 被领取
                        ↓
              Client 采回 PDD 的颜色尺码和价格
                        ↓
                  存入 商品表.pdd_data
                        │
顺运宝货运单 ─同步─→ 货运单表
                        │ 双击行
                        ↓
              匹配弹窗:蝦皮规格 ←→ PDD 规格
              (匹配过的自动带出,只有新规格要手工做)
                        ↓
                  创建采购任务 → 分配 Client → 被领取
                        ↓
                  Client 下单 → 提交结果 → 人工付款

两个要点:

  • 采集是商品级的,一个 PDD 商品采一次,所有相关订单共用结果。
  • 匹配是 SKU 级的且可复用,同一个蝦皮 SKU 只需人工匹配一次。

4. 产品范围

顶级导航固定四个模块,顺序不变:

# 模块 职责
1 蝦皮数据 商品档案、PDD 链接、发起采集
2 顺运宝数据 货运单、规格匹配、生成采购任务
3 采购任务 执行进度跟踪
4 客户端列表 客户端注册与状态

四个模块统一使用三段式页面布局:顶部工具条 / 中间带勾选的表格 / 底部状态条。 详见 05 界面规范。

4.1 蝦皮数据模块

顶部工具条: 导入按钮、商品 ID 搜索框、搜索按钮、删除按钮、批量采集按钮。

中间表格(按 SKU 展开显示,数据来自商品表和 SKU 表联查):

列 说明
勾选 支持批量删除、批量采集
商品 ID 蝦皮商品编号
商品名称
颜色 / 尺码 / 建议 从规格原文解析,解析不出来留空并标记
PDD 链接 人工填写,空的要显眼
采集状态 见 §6
更新时间

双击行打开编辑弹窗,这是 PDD 链接唯一的录入口:

  • 可编辑:PDD 链接、颜色、尺码、建议;
  • 弹窗内提供【采集】按钮,紧挨 PDD 链接输入框;
  • [必须] 采集按钮只出现在弹窗里,不要放在每个表格行—— 一个商品有 N 个 SKU 行,放行上就是 N 个按钮干同一件事,还会建出 N 个重复任务。

其他要求:

  • [必须] 支持手动新增一条 SKU。报表只含有销售成绩的 SKU(样本里平均每商品仅 1.17 个), 订单来了查无此 SKU 是常态,必须能补。
  • [必须] 导入是 upsert,绝不允许先清空再导入,否则人工填的 PDD 链接会被洗掉。
  • [必须] 规格原文永远保留,解析失败留空,不要猜。

底部状态条: 最近一次导入的时间、条数、失败行数。

4.2 顺运宝数据模块

顶部工具条: 同步按钮、订单号搜索框、搜索按钮、创建采购任务按钮、删除按钮。

  • [待定] 同步按钮 MVP 阶段只做占位:点击后提示"同步功能待接入",不发请求。

中间表格:

列 说明
勾选 支持批量删除、批量建任务
货运单 ID
订单号
商品标题
蝦皮商品 ID 关联蝦皮数据模块
规格 SKU 蝦皮的规格编号
数量
价格 蝦皮售价,台币分,见 §7
图片 存 URL,表格里显示缩略图
匹配状态 已匹配 / 待匹配
更新时间

原始的完整货运单 JSON 存 syb_data 字段,不作为表格列显示,在详情里看。

双击行打开匹配弹窗:

  • 左边显示蝦皮的颜色尺码,右边显示该商品 pdd_data 里的 PDD 颜色尺码;
  • 操作员选好对应关系,点保存;
  • [必须] 保存的是可复用的 SKU 映射,不是这一张订单的临时数据。 下次遇到同一个蝦皮 SKU 自动带出,操作员只需确认。
  • [必须] 该商品尚未采集(pdd_data 为空)时,弹窗要明确提示"请先到蝦皮数据模块采集", 而不是显示一个空列表让人困惑。

创建采购任务: 勾选若干行 → 点按钮 → 校验通过后生成任务。校验规则见 §5。

底部状态条: 最近同步时间、待匹配条数。

4.3 采购任务模块

顶部工具条: 订单号搜索框、搜索按钮、删除按钮。

中间表格:

列 说明
勾选 支持批量删除
订单号
商品标题
颜色 / 尺码 PDD 侧的规格,不是蝦皮的
数量
价格上限 人民币分,见 §7
蝦皮 ID
分配客户端 可改派
状态 见 §6
更新时间

底部状态条: 各状态的任务条数统计。

4.4 客户端列表模块

顶部工具条: 客户端名称搜索框、搜索按钮、删除按钮。

中间表格: 勾选、名称、序列号、状态、最近活动时间、更新时间。

注册方式:

  • [必须] 设置页通过独立登记接口幂等新增或更新 Client;该接口不得领取或修改任务。
  • [必须] 领取接口保留隐式登记作为旧 Client 的兼容兜底。
  • [必须] 不设心跳接口,在线状态由登记、领取和提交产生的最近活动时间派生。
  • [必须] 状态 是派生字段,不存库: 最近活动时间在 N 分钟内算"在线",否则"离线"。[建议] N 默认 10 分钟。
  • 名称由客户端上报,操作员可以在 Admin 这边改成好记的名字。

底部状态条: 在线 / 离线数量统计。

5. 创建采购任务的校验

[必须] 下面任何一条不满足就不允许创建,并明确告诉操作员缺什么:

  1. 该蝦皮商品已填 PDD 链接;
  2. 该蝦皮商品已采集成功(pdd_data 非空);
  3. 该蝦皮 SKU 已有 PDD 规格映射;
  4. 数量大于 0;
  5. 价格上限已填且大于 0 —— 默认从 pdd_data 里该 SKU 的价格带出,操作员可改,但不允许为空;
  6. 已选择分配的客户端。

第 5 条是硬要求:Client 契约规定采购任务必须带明确的价格保护 (见 Client 契约 §4),没有它 Client 会拒绝执行。

6. 状态定义

6.1 采集状态

界面上显示 5 种,但数据来自两张表——采集的对象是 PDD 商品, 所以状态存在 pdd_products 上,不在蝦皮商品上。

界面显示 怎么判断
未填链接 shopee_products.pdd_goods_id 为空
未采集 pdd_products.collect_status = 'pending'
采集中 = 'collecting'
已采集 = 'collected'
采集失败 = 'failed',原因在 collect_msg

[必须] collecting 状态的商品不允许再建采集任务,按钮置灰并提示"已在采集中"。

[必须] 状态挂在 PDD 商品上,好处是两个蝦皮商品指向同一个 PDD 链接时, 状态只有一份——不会各记一份还可能不一致,也不会把同一个商品采两遍。

6.2 任务状态(采集任务和采购任务通用)

这是 Admin 侧的状态,和 Client 本地的 8 个状态是两套,不要混 (Client 侧见 03 数据模型 §7)。

值 中文 什么时候
pending 待分配 刚创建,还没指定客户端
assigned 待领取 已分配给某个客户端
claimed 已领取 客户端领走了,正在执行
succeeded 成功 收到结果
manual_review 需人工 客户端报告需要人处理
failed 失败 客户端报告失败
cancelled 已取消 人工取消

[必须] Admin 看不到客户端执行到哪一步(没有心跳,是有意的)。 claimed 之后就只能等结果。想知道细节看 Client 那边的界面。

客户端提交失败时带的 status,按下表落地:

客户端报告 Admin 置为
retry_wait assigned(等它再来领)
manual_review manual_review
failed failed
cancelled cancelled

7. 金额与币种

[必须] 一律用整数存,禁止浮点。 字段名带单位后缀。

场景 币种 字段示例
蝦皮售价 台币 price_twd_cent
PDD 采购价、价格上限 人民币 max_price_cent

[必须] 两种币种不得混用,不得互相换算后覆盖原值。 采购任务里的价格上限是人民币,来源是采集回来的 PDD 价格, 和蝦皮的台币售价没有换算关系。

[待定] 是否需要展示汇率或利润,待业务确认。MVP 不做。

8. MVP 范围

MVP 包含:

  • 四模块页面框架和统一的三段式布局;
  • SQLite 建库与迁移;
  • 蝦皮 Excel 导入(upsert)、搜索、批量删除、手动新增、编辑弹窗;
  • PDD 链接录入与发起采集;
  • 顺运宝数据的手工录入或造数(同步按钮占位);
  • 规格匹配弹窗与可复用映射;
  • 创建采购任务并分配客户端;
  • 给 Client 的四个接口:登记、领取、提交结果、提交失败;
  • 客户端自动注册与在线状态。

MVP 之后:

  • 接入顺运宝真实同步;
  • 打包成 exe;
  • 权限与多用户;
  • 统计报表。

9. 非目标

  • 不做面向外部的公网服务,只在内网/本机运行。
  • 不直接对接蝦皮和拼多多的接口。
  • 不自动决定买哪个商品——PDD 链接和规格匹配都由人确认。
  • 不自动付款。
  • MVP 不做用户登录和权限体系。

10. 非功能需求

  • 单机运行,操作人员规模是个位数,不追求高并发。
  • 页面在 1366×768 上可正常使用。
  • 导入 1 万行 Excel 应在可接受时间内完成,并显示进度或结果统计。
  • 所有写操作有 CSRF 防护,所有 SQL 参数化。
  • 日志、页面、导出不含 token、密码、Cookie。
  • 数据放程序旁边的 data/,便携模式,见 02 架构 §6。

11. 待确认事项

不许因此停工。先按临时默认值做,定了再按工单改。

# 待确认什么 临时默认值
1 顺运宝同步方式(接口?导出文件?) 按钮占位,数据靠手工录入或造数
2 蝦皮有无全量规格导出 没有。靠反复导入累积 + 手动新增补齐
3 Admin 是否需要登录 MVP 不做,仅本机/内网访问
4 是否需要利润和汇率展示 不做

已定案:

  • PDD 链接人工填写,入口在蝦皮数据模块的编辑弹窗,采集按钮也在那里。
  • 任务分配给指定客户端,Client 只领分给自己的。
  • 不加心跳接口;设置页显式登记,领取接口保留兼容登记,在线状态由最近活动时间派生。
  • SKU 映射独立成表且可复用,同一蝦皮 SKU 只人工匹配一次。
  • 货运单的"规格 SKU"直接对应蝦皮 商品規格ID,不需要转换。 但不加外键——本地查不到该 SKU 是常态,见 03 数据模型 §4。
  • Go 版本固定 1.23.0。