仓库从单子项目变成两个:client(Python + PyQt5 桌面端)和 admin(Go + Gin + HTML 模板 Web 管理端)。两者技术栈完全不同, 规则各自独立,只通过三个 HTTP 接口交互。 新增 admin/AGENTS.md 和 docs/admin/ 共 9 份文档,结构与 docs/client/ 对齐。 关键设计决策(均已与用户确认) - 四模块:蝦皮数据 / 顺运宝数据 / 采购任务 / 客户端列表, 统一三段式布局(工具条 / 带勾选的表格 / 状态条) - 蝦皮数据拆成商品表 + SKU 表:报表本身就是两层结构, 且 PDD 链接是商品级的,放 SKU 级会重复维护 - pdd_data 存商品级,一个 PDD 商品采一次,所有订单共用 - SKU 映射独立成表且可复用,同一蝦皮 SKU 只需人工匹配一次 - PDD 链接人工填写,采集按钮就放在填链接的编辑弹窗里 - 任务分配给指定客户端;不加心跳,注册在领取时完成, 在线状态由 last_seen_at 派生 - SQLite 驱动固定 modernc.org/sqlite(纯 Go 免 cgo,go build 直接出 exe) - 不引入前端框架和 npm 构建,只允许原生 JS 或 htmx 基于样本文件 raw_data/蝦皮数据样本.xlsx 实测得出的约束 - 11287 行 = 5195 商品汇总行 + 6092 SKU 行,导入必须分开处理 - 商品規格ID 零重复,是天然主键 - 规格原文两种格式各占 53.4% / 46.6%,只解析【】会漏掉一半 - 平均每商品仅 1.17 个 SKU,报表不是全量目录, 因此必须支持手动新增,且货运单外键不能加硬约束 同步更新 - 根 AGENTS.md 开头的指针改为两个子项目对照表 - docs/README.md 重构为双子项目索引 - .gitignore 加 admin/data/、admin.exe、Excel 锁文件 说明:Gitea 尚未配置,本次无对应工单号。 raw_data/ 未提交,含台币销售额等商业数据,待用户决定。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
12 KiB
01 Admin 产品需求基线
- 文档状态:基线草案,待需求评审
- 适用范围:
admin/ - 产品类型:本地运行的 Web 管理端(Go + Gin + HTML 模板)
本文档中 [必须] / [建议] / [待定] 的含义见 文档索引。
没有标注的默认是 [必须]。看不懂的词查 术语表。
1. 背景与目标
我们在蝦皮(台湾)卖货,在拼多多(大陆)进货,中间由顺运宝提供货运单。 Admin 的职责是把这三方的数据串起来,最终产出 Client 能执行的采购任务。
产品目标:
- 导入并维护蝦皮商品档案,为每个商品登记对应的拼多多链接。
- 同步顺运宝货运单,知道"这单该买什么"。
- 把蝦皮的颜色尺码,对应到拼多多的颜色尺码,且这个对应关系可复用。
- 生成采购任务并分配给指定客户端,跟踪执行结果。
- 管理客户端清单,知道谁在干活。
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 客户端列表模块
顶部工具条: 客户端名称搜索框、搜索按钮、删除按钮。
中间表格: 勾选、名称、序列号、状态、最近活动时间、更新时间。
注册方式:
[必须]客户端调用领取接口时自动注册:新序列号就新增,已有就更新。 不设单独的注册或心跳接口,理由见 04 Client 接口实现 §3。[必须]状态是派生字段,不存库: 最近活动时间在 N 分钟内算"在线",否则"离线"。[建议]N 默认 10 分钟。- 名称由客户端上报,操作员可以在 Admin 这边改成好记的名字。
底部状态条: 在线 / 离线数量统计。
5. 创建采购任务的校验
[必须] 下面任何一条不满足就不允许创建,并明确告诉操作员缺什么:
- 该蝦皮商品已填 PDD 链接;
- 该蝦皮商品已采集成功(
pdd_data非空); - 该蝦皮 SKU 已有 PDD 规格映射;
- 数量大于 0;
- 价格上限已填且大于 0 —— 默认从
pdd_data里该 SKU 的价格带出,操作员可改,但不允许为空; - 已选择分配的客户端。
第 5 条是硬要求:Client 契约规定采购任务必须带明确的价格保护 (见 Client 契约 §4),没有它 Client 会拒绝执行。
6. 状态定义
6.1 采集状态(蝦皮商品)
| 值 | 中文 | 含义 |
|---|---|---|
no_link |
未填链接 | 还没填 PDD 链接 |
pending |
未采集 | 已填链接,还没发起采集 |
collecting |
采集中 | 采集任务已创建,尚未回结果 |
collected |
已采集 | pdd_data 已就绪 |
failed |
采集失败 | Client 报告失败,可重新采集 |
[必须] collecting 状态的商品不允许再建采集任务,按钮置灰并提示"已在采集中"。
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。