Files
cmautobuy/docs/admin/01-requirements.md
T

610 lines
36 KiB
Markdown
Raw Normal View History

2026-08-06 15:49:04 +08:00
# 01 Admin 产品需求基线
- 文档状态:基线草案,待需求评审
- 适用范围:`admin/`
2026-08-10 01:22:22 +08:00
- 产品类型:集中部署的 Web 管理端(Go + Gin + HTML 模板 + MySQL 8.4)
2026-08-06 15:49:04 +08:00
本文档中 `[必须]` / `[建议]` / `[待定]` 的含义见 [文档索引](../README.md#文档标注说明)。
没有标注的默认是 `[必须]`。看不懂的词查 [术语表](00-glossary.md)。
## 1. 背景与目标
我们在**蝦皮**(台湾)卖货,在**拼多多**(大陆)进货,中间由**顺运宝**提供货运单。
Admin 的职责是把这三方的数据串起来,最终产出 Client 能执行的采购任务。
产品目标:
1. 导入并维护蝦皮商品档案,为每个商品登记对应的拼多多链接。
2. 同步顺运宝货运单,知道"这单该买什么"。
3. 把蝦皮的颜色尺码,对应到拼多多的颜色尺码,**且这个对应关系可复用**。
4. 生成采购任务并分配给指定客户端,跟踪执行结果。
5. 管理客户端清单,知道谁在干活。
6. 通过登录保护 Admin 网页,并由管理员维护采购员账号。
2026-08-06 15:49:04 +08:00
## 2. 用户与外部系统
- **管理员:**首次初始化 Admin,执行全部业务操作,并创建、禁用或重置采购员账号。
- **采购员:**登录后执行现有业务操作,但不能管理用户。
2026-08-06 15:49:04 +08:00
- **Client:**领取任务、在安卓设备上执行、提交结果。契约见 [Client 侧文档](../client/04-admin-api-contract.md)。
- **蝦皮:**不同来源文件由第三方脚本归一化,再提交到 Admin 商品目录接口。
2026-08-06 15:49:04 +08:00
- **顺运宝:**提供货运单,`[待定]` 同步方式待确认,MVP 先做占位按钮。
- **拼多多:**由 Client 操作,Admin 不直接接触。
## 3. 完整业务链路
2026-08-07 11:27:06 +08:00
这条链路是理解全部五个模块的关键,先看懂它:
2026-08-06 15:49:04 +08:00
```text
第三方商品目录脚本 ──批量接口──→ 商品表 + SKU 表
2026-08-06 15:49:04 +08:00
│
编辑弹窗:人工填 PDD 链接
2026-08-07 11:27:06 +08:00
│
2026-08-06 15:49:04 +08:00
↓
2026-08-07 11:27:06 +08:00
PDD 商品页 ──────→ PDD 商品表
│ 点【创建采集任务】
↓
创建采集任务(不指定 Client)→ 谁领到算谁的
2026-08-06 15:49:04 +08:00
↓
Client 采回 PDD 的颜色尺码和价格
↓
2026-08-07 11:27:06 +08:00
存入 PDD 商品表.skus_json
2026-08-06 15:49:04 +08:00
│
顺运宝货运单 ─同步─→ 货运单表
│ 双击行
↓
匹配弹窗:蝦皮规格 ←→ PDD 规格
(匹配过的自动带出,只有新规格要手工做)
↓
创建采购任务 → 分配 Client → 被领取
↓
Client 下单 → 提交结果 → 人工付款
```
两个要点:
- **采集是商品级的**,一个 PDD 商品采一次,所有相关订单共用结果。
- **匹配是 SKU 级的且可复用**,同一个蝦皮 SKU 只需人工匹配一次。
## 4. 产品范围
2026-08-07 11:27:06 +08:00
顶级导航固定五个模块,顺序不变:
2026-08-06 15:49:04 +08:00
| # | 模块 | 职责 |
|---|---|---|
| 1 | 蝦皮数据 | 商品档案、PDD 链接、发起采集 |
2026-08-07 11:27:06 +08:00
| 2 | PDD 商品 | PDD 商品档案、发起采集、查看采回来的规格价格 |
| 3 | 顺运宝数据 | 货运单、规格匹配、生成采购任务 |
| 4 | 采集采购 | 采集任务和采购任务的执行进度跟踪 |
2026-08-07 11:27:06 +08:00
| 5 | 客户端列表 | 客户端注册与状态 |
2026-08-06 15:49:04 +08:00
2026-08-07 11:27:06 +08:00
五个模块**统一使用三段式页面布局**:顶部工具条 / 中间带勾选的表格 / 底部状态条。
2026-08-06 15:49:04 +08:00
详见 [05 界面规范](05-ui-specification.md)。
2026-08-07 11:27:06 +08:00
PDD 商品之所以单独一个模块,是因为它在数据上就是**独立实体**
(自己的表、自己的采集状态,可以被多个蝦皮商品共用),
见 [03 数据模型 §4](03-data-model.md)。挂在蝦皮模块下面的话,
同一个 PDD 商品被两个蝦皮商品引用时就说不清该显示在谁名下。
2026-08-06 15:49:04 +08:00
### 4.1 蝦皮数据模块
**顶部工具条:** 导入记录按钮、批量创建 PDD 采集任务按钮、状态筛选
2026-08-13 12:14:02 +08:00
(全部 / 待补规格 / 未填 PDD 链接 / 已填链接)、分类(商品 ID / 商品名称 / 店铺名)、
关键词和搜索按钮;管理员另有回收站入口以及删除/恢复按钮。
`[必须]` 状态筛选是**刚需**,不是锦上添花(工单 #43):本页的主要用途是维护
(找出需要补规格的商品、找出还没关联 PDD 链接的商品),只靠商品 ID 搜索的话,
操作员得先知道 ID 才能搜——而"哪些商品需要处理"恰恰是不知道 ID 的时候才要问的。
实测样本 5195 个商品里只有 6 个待补规格,只做分页要翻 260 页才能找全,等于找不到。
界面细节和筛选条件的 SQL 见 [05 界面规范](05-ui-specification.md) §4.1。
2026-08-06 15:49:04 +08:00
2026-08-13 12:14:02 +08:00
**中间表格**(一个商品一行,SKU 数据聚合显示):
2026-08-06 15:49:04 +08:00
| 列 | 说明 |
|---|---|
| 勾选 | 支持批量删除、批量创建 PDD 采集任务 |
2026-08-06 15:49:04 +08:00
| 商品 ID | 蝦皮商品编号 |
| 商品名称 | |
2026-08-13 12:14:02 +08:00
| 颜色 / 尺码 / SKU / 待补 | 聚合计数,待补大于 0 时整行提示 |
| PDD 商品 ID | 已关联时显示商品 ID,完整链接放在悬停提示和详情;空的要显眼 |
2026-08-06 15:49:04 +08:00
| 采集状态 | 见 §6 |
| 更新时间 | |
2026-08-07 11:27:06 +08:00
**双击行打开编辑弹窗**,这是**从蝦皮商品出发**填 PDD 链接、发起采集的入口:
2026-08-06 15:49:04 +08:00
2026-08-13 12:14:02 +08:00
- 可编辑当前 PDD 商品链接;规格表摘要只读,每条现有 SKU 可展开修改颜色、尺码和建议说明;
- 人工修改只写字段级来源,不修改规格原文、规格键、真实 SKU ID 或 `is_manual`;后续
目录导入和 SYB 低优先级补全不得覆盖人工字段,主动清空的建议说明也按人工值保护;
- 保存链接时从完整链接解析 `goods_id`,在同一事务内复用或复活 PDD 商品档案并更新当前关联;
- 更换成不同 `goods_id` 时必须明确勾选确认,旧规格映射保留但不会被当前关联查询误用;
- 弹窗内提供【创建采集任务】按钮,复用 PDD 商品模块的幂等与超时重试规则;
- 蝦皮列表支持勾选商品后批量创建 PDD 采集任务。系统按蝦皮商品当前关联取得
PDD 商品并按 PDD 商品 ID 去重,可选当前账号可见客户端;未关联、PDD 已删除、
已采集和正常采集中的商品按原因统计并跳过。采集失败、采集超时或采集中但已无
有效任务的商品可以恢复创建。该入口不修改 PDD 关联,不替代商品详情里的单商品入口;
- `[必须]` 采集操作**只出现在顶部批量入口和商品详情弹窗里**,不要放在每个表格行——
2026-08-06 15:49:04 +08:00
一个商品有 N 个 SKU 行,放行上就是 N 个按钮干同一件事,还会建出 N 个重复任务。
- `[必须]` 蝦皮商品删除为管理员软删除。正常业务查询默认排除;SKU、PDD 关联和历史
数据保留。存在待分配、待领取或已领取任务时整批拒绝;恢复只能从管理员“已删除”
列表显式执行,顺运宝同步和商品目录导入不得自动恢复。
2026-08-06 15:49:04 +08:00
`[注意]` PDD 商品模块(§4.2)继续作为商品档案、采集状态和故障诊断页;
蝦皮详情和顺运宝处理详情是带业务上下文的关联入口,两者调用同一个 Service。
2026-08-07 11:27:06 +08:00
2026-08-06 15:49:04 +08:00
**其他要求:**
- `[必须]` 支持**手动新增**一条 SKU。报表只含有销售成绩的 SKU(样本里平均每商品仅 1.17 个),
订单来了查无此 SKU 是常态,必须能补。
- `[必须]` 导入是 **upsert**,绝不允许先清空再导入,否则人工填的 PDD 链接会被洗掉。
- `[必须]` 规格原文永远保留,解析失败留空,不要猜。
**底部状态条:** 最近一次导入的时间、条数、失败行数。
2026-08-07 11:27:06 +08:00
### 4.2 PDD 商品模块
维护拼多多商品档案,并发起采集。这条链路**不依赖蝦皮和顺运宝的任何数据**,
可以单独跑通:建商品 → 建采集任务 → Client 领走执行 → 提交结果 → 页面显示已采集。
**顶部工具条:** 添加 PDD 商品、批量导入、创建采集任务、采集状态筛选、
商品 ID/链接搜索框、搜索和删除。
2026-08-07 11:27:06 +08:00
**中间表格:**
| 列 | 说明 |
|---|---|
| 勾选 | 支持批量删除、批量建采集任务 |
| 商品 ID | 从链接解析出来的 `goods_id` |
| 标题 | 采集回来的,未采集时显示占位文案 |
| PDD 链接 | 截断显示,可点开 |
| 采集状态 | 见 §6.1,用中文文字,不能只靠颜色 |
| 规格数 | 从 `skus_json` 算 |
| 采集时间 | 未采集显示 `—` |
| 更新时间 | |
`[必须]` **规格数必须显示。** 采到 1 个和采到 20 个差别很大——
只采到 1 个通常意味着客户端没点开规格面板,是采集有问题。
不显示这一列的话,要点进每个商品才能发现。
**创建:** `[必须]` 只填 PDD 链接,其余字段全靠采集回填。
`[必须]` **链接必须能解析出 `goods_id`,解析不出来直接报错并且不写库**。
不接受短链接——`goods_id` 上有 UNIQUE 约束,防重全靠它,
拿不到就没法查重,同一个商品会存成好几行。
**批量导入:** 接受第一列标题为“拼多多链接”的一列式 `.xlsx`,读取第一个
非空工作表,忽略空工作表和空行,一次最多 5000 条。逐行复用单条创建的链接解析、
去重和软删除恢复规则;已存在有效商品保留采集结果。无效行不阻塞其他合法行,
但必须显示 Excel 行号和原因;数据库异常时合法行整体回滚。
上传只建立商品档案,**不得自动创建采集任务**。导入结果保留本次合法且去重后的
完整商品集合,采购员显式确认后才能为这一集合批量创建任务;该集合不受列表分页限制,
客户端选择、权限、状态跳过和任务去重继续使用现有规则。
2026-08-07 11:27:06 +08:00
**双击行打开弹窗:** 查看商品信息,可编辑链接,并显示采回来的规格和价格。
- `[必须]` **规格和价格必须显示**,这是采集结果的全部价值所在——
不显示的话操作员没法确认"采得对不对、是不是我要的那个商品"。
- `[必须]` 维度顺序按 `skus_json` 里的 `dimensions`(Go 的 map 无序,必须靠它定顺序)。
- `[必须]` 价格显示成 `¥12.56`,底层存整数分;`price_cent` 为 `null` 时显示"未采到",
**不得显示成 ¥0.00**。
- `[必须]` 编辑链接时新链接必须还是**同一个商品**。换成别的商品要新建一条:
这一行上挂着采集结果和 SKU 映射,`goods_id` 一换那些数据就全指到错的商品上了。
**创建采集任务:**
- 普通批量创建仍跳过已采集商品;商品详情中的“重新采集”是独立的明确操作,
允许为已采集商品创建一条新任务。
- 重新采集开始时只把状态改为采集中,不清空旧标题、店铺、规格、价格和采集时间;
Client 成功提交后再覆盖旧结果。未超时的采集中商品不得重复建任务。
- `[必须]` 默认**不指定客户端**,谁领到就在领取时标记谁;采购员也可以明确指定
当前账号可见的客户端,指定后只等待该客户端领取,界面必须提示离线等待风险。
2026-08-07 11:27:06 +08:00
- `[必须]` 按 `goods_id` 去重;`collecting` 状态的跳过,并在结果里说明跳过了几个。
- `[必须]` 按钮文案用「**创建采集任务**」,不要用「采集」。它做的是建一个任务,
不是立刻去采——真正的采集要等 Client 来领、去手机上跑,可能几秒也可能几分钟。
**删除:** 软删除。二次确认要写明"重新创建同一链接可恢复,**但采集结果会清空**"。
不硬删是因为 SKU 映射指向它,硬删会把人工攒了很久的匹配成果一起带走。
**底部状态条:** 各采集状态的条数统计。
### 4.3 顺运宝数据模块
2026-08-06 15:49:04 +08:00
**顶部工具条:** 日期范围、同步、同步记录、创建采集任务、创建采购任务、处理阶段、
店铺和订单号筛选、清除、搜索、删除。店铺管理通过管理员主导航进入,不在工具条重复显示。
2026-08-06 15:49:04 +08:00
订单号筛选支持一次精确搜索多条订单号。采购员可以直接粘贴 Excel 单列数据,也可以使用
中英文逗号、分号、空格、Tab 或换行分隔;系统去除首尾空白并按首次出现顺序去重。
每次最多接收 100 个不同订单号,原始输入最多 4000 个字符,单个订单号最多 191 个字符。
订单号只与完整订单号匹配,不再同时模糊匹配商品标题。搜索结果显示输入订单数、当前店铺和
处理阶段组合条件下命中的订单数、商品明细数,以及未命中的订单号;输入不合法时在搜索框附近
提示,不返回服务器错误。分页和列表写操作返回时保留规范化后的订单号条件。
2026-08-06 15:49:04 +08:00
- `[待定]` 同步按钮 MVP 阶段只做占位:点击后提示"同步功能待接入",不发请求。
**中间表格:**
| 列 | 说明 |
|---|---|
| 勾选 | 支持批量删除、批量建任务 |
| 订单号 | |
| 店铺 | 顺运宝货运单店铺名称,长内容省略,悬停查看全文 |
2026-08-06 15:49:04 +08:00
| 商品标题 | |
| 规格 | 顺运宝商品规格原文,长内容省略,悬停查看全文 |
2026-08-06 15:49:04 +08:00
| 蝦皮商品 ID | 关联蝦皮数据模块 |
| 数量 | |
| 价格 | 蝦皮售价,**台币分**,见 §7 |
| 图片 | 存 URL,表格里显示缩略图 |
| 下一步 | 每行只显示当前最需要完成的一个操作 |
2026-08-06 15:49:04 +08:00
| 更新时间 | |
原始的完整货运单 JSON 存 `syb_data` 字段,**不作为表格列显示**,在详情里看。
货运单明细 ID 继续作为勾选、详情和任务关联使用的内部标识,但不单独显示为列表列。
处理阶段仍按实时业务事实推导,并用于顶部筛选、批量选择门禁、下一步路由和详情说明,
但不作为独立列表列显示。待人工核对、缺少规格和数量异常必须分别显示“核对采购订单”、
“核对缺失规格”和“核对采购数量”,避免隐藏阶段列后产生歧义。
桌面端列表使用剩余视口高度并固定表头,让横向滚动条在浏览长列表时可到达;工具条或
条件提示高度变化时列表自动伸缩。窄窗口使用自然页面高度。清除筛选时保留当前同步日期范围。
2026-08-06 15:49:04 +08:00
**双击行或点击下一步打开处理弹窗:**
2026-08-10 12:24:23 +08:00
- 顺运宝的 `product_spec` 是匹配主链路的真实来源;系统不再要求先确认蝦皮 SKU。
- “规格身份键” `spec_key` 只对顺运宝规格原文去除首尾空白、折叠连续空白,用于判断是否是同一条规格;颜色、尺寸和建议的解析只用于显示与推荐,不改变身份。
- 找到蝦皮商品后,可在同一弹窗填写带 `goods_id` 的完整 PDD 链接,或直接输入
6~24 位纯数字 PDD 商品 ID;纯 ID 先转换为标准商品链接,再复用现有 PDD 商品档案和
采集结果。关联完成后仍由采购员另行创建采集任务,换成其他商品仍须明确确认。
- 顺运宝同步出的蝦皮商品 ID 如果已经在 `shopee_products` 关联有效、未删除的 PDD
商品,新货运单明细必须直接复用该关系并进入后续采集或规格匹配阶段,不要求采购员
重复输入 PDD URL。关系仍只保存在蝦皮商品上,顺运宝明细不复制 PDD 字段。
2026-08-06 15:49:04 +08:00
- 显示蝦皮规格原文和当前 PDD 商品 `skus_json` 里的动态规格组合与人民币价格;
- PDD 维度来自采集结果的 `dimensions`,不得写死成颜色、尺码两个维度;
2026-08-06 15:49:04 +08:00
- 操作员选好对应关系,点保存;
2026-08-10 12:24:23 +08:00
- `[必须]` 保存的是**可复用的商品规格映射**,键为
`(蝦皮商品 ID, spec_key, PDD 商品 ID)`,不是这一张订单的临时数据。
下次遇到同商品、同规格和同 PDD 商品时自动带出。
- `[必须]` 该商品尚未采集(`skus_json` 为空)时,弹窗直接展示关联和创建采集任务入口,
2026-08-06 15:49:04 +08:00
而不是显示一个空列表让人困惑。
- 规则引擎只对可购买候选做 A/B/C/冲突分层和稳定排序;只有唯一、无额外维度歧义的 A 级才预选。
- 预选不会自动保存或创建采购任务。页面必须显示推荐理由,采购员点击保存后才写映射和只追加的决策审计。
2026-08-06 15:49:04 +08:00
**创建采购任务:** 只有映射仍有效且没有进行中采购任务的行可勾选。确认弹窗列出
当前账号可见的全部客户端都可选择,暂时离线或尚未就绪也可提前指派并等待其就绪后领取。
新任务固定为真实下单(不支付),提交表单即表示创建真实采购任务,
不再额外要求风险复选框或确认短语;服务端仍按 §5 全量复核。
2026-08-06 15:49:04 +08:00
**底部状态条:** 最近同步时间、待匹配条数。
### 4.4 采集采购模块
2026-08-06 15:49:04 +08:00
`tasks` 是**一张表**,用 `task_type` 区分采集和采购。这个模块把两种任务
放在同一个列表里显示——分开成两个页面的话,采集任务建出来了,
操作员却只能盯着 PDD 商品的 `collect_status` 猜任务本身怎么样了
(被谁领了、领了多久、失败在哪一步),完全看不出任务这一层的信息。
`task_id` 同时是页面业务单号和给 Client 的稳定主键:采集任务按
`cj1`、`cj2` ……递增,采购任务按 `cg1`、`cg2` ……独立递增。任务类型
仍以 `task_type` 为准,不允许仅根据编号前缀推导业务逻辑。
`[必须]` 名字是「**采集采购**」,不是「任务」。它直接点出这一页装的是
哪两类任务,比泛称「任务」更能让操作员一眼知道点进去看什么。
`[必须]` 路由 `/tasks` 不变,避免已有的书签和文档链接失效。
**顶部工具条:**
```text
[类型▾ 全部] [状态▾ 全部] [创建人▾ 全部] 关键词[____________] [搜索] [删除]
```
- **类型**:全部 / 采集 / 采购。
- **状态**:全部 + 7 个状态,见 §6.2。
- **关键词**:同时匹配任务编号、采购订单号、PDD 商品 ID;从顺运宝创建的采集任务
还要匹配其全部来源顺运宝订单号和明细 ID。
- **创建人**:仅管理员显示,可选全部账号(包含已禁用账号)或“历史任务”。
`[必须]` 采购员登录后只可查看自己创建的任务;URL 参数不能扩大范围。管理员查看
全部任务,`created_by_user_id` 为空的存量记录显示为“历史任务”。列表、统计、分页、
详情和删除必须使用同一权限范围,采购员直接访问他人详情统一返回 404。
`[必须]` 筛选比搜索更常用:这个页面最常被问的问题是"有没有卡住的任务",
不是"订单 SO-001 怎么样了",所以类型和状态筛选要放在最前面。
2026-08-06 15:49:04 +08:00
**中间表格:**
两种任务的业务字段完全不同——采购有订单号/颜色尺码/数量/价格上限,
采集只有 PDD 链接。并列显示的话采集任务行会有一半是空列,
所以改成固定列 + 一列「目标」概括业务信息:
2026-08-06 15:49:04 +08:00
| 列 | 说明 |
|---|---|
| 勾选 | 支持批量删除 |
| 任务编号 | `task_id` |
| 类型 | 采集 / 采购,文字,不能只靠颜色 |
| **目标** | 采集:`PDD <pdd_goods_id>`(能 join 到未删除的商品标题时追加显示);采购:`<order_no> · <颜色/尺码> · <数量>件 · ≤<价格上限>` |
| 状态 | 中文,7 个取值见 §6.2 |
| 客户端 | `assigned_client`;**无主任务显示 `—`**(#17 之后采集任务默认无主,这一列会大量为空) |
| 创建人 | 仅管理员列表显示;存量 NULL 显示“历史任务” |
| 更新时间 | 本地时区 |
2026-08-06 15:49:04 +08:00
`[必须]` 目标列所需字段全在 `tasks` 表上(`pdd_goods_id` / `order_no` /
`pdd_options` / `quantity` / `max_price_cent`),不需要 join。
`[建议]` 采集任务的目标 join `pdd_products` 取标题显示更友好,
但 join 不到或商品已软删除时必须退回只显示 `pdd_goods_id`,不能空着。
`[必须]` 目标列的拼接逻辑在 service 层组装成一个字符串,模板只负责显示——
散在模板里没人维护得住。
`[必须]` 价格上限显示成 `¥42.00`,底层是整数分。
**双击行打开详情弹窗:**
`[必须]` 详情内容只读,不提供改派、重试、取消或直接复制旧任务的写操作。
采购任务允许导航回原顺运宝明细重新确认,但导航本身不得创建任务。
弹窗显示:类型、状态、创建人、分配客户端、领取时间、完成时间;执行参数
(PDD 链接,采购任务额外显示目标规格、数量、价格上限);错误信息
(错误码、错误说明,没有错误时不显示这一段)。
采购任务额外显示来源信息:蝦皮订单号、顺运宝明细 ID、蝦皮商品 ID 和 PDD 商品 ID。
“查看原货运单”使用 `syb_id` 精确打开原明细。只有 `failed`、`cancelled` 显示
“重新创建采购任务”,并进入原明细的采购确认流程;其他状态不得显示。重新创建仍须
按最新 PDD 关联、规格映射、数量、价格和客户端执行现有服务端校验。
采购任务额外显示独立的“PDD 核单结果”:PDD 订单编号、下单时间和
“上报时付款状态”。这些字段从现有 `result_data.purchase` 读取;下单时间转换为
Admin 本地时区,付款状态只是 Client 核单上报时的快照,Admin 不查询 PDD 实时状态。
尚未上报、订单结果不确定和结果格式异常必须使用不同的明确文字;异常时仍保留原始结果。
`[必须]` 采购专有字段(数量、价格上限、目标规格)在采集任务的弹窗里
**整段隐藏**,不显示空行。
`[建议]` `result_data`(Client 提交的完整结果)默认折叠,提供展开查看;
过长时截断显示。
`[必须]` 批量删除是全有或全无:提交中只要混入不存在或不属于当前采购员的任务,
整批拒绝且一条也不删除。
**底部状态条:**
```text
共 42 条 · 待分配 3 · 待领取 5 · 已领取 2 · 成功 30 · 需人工 1 · 失败 1 · 已取消 0
```
`[必须]` 统计要**跟随当前筛选**。筛了「采集」就只统计采集任务,
否则数字和表格对不上,操作员会以为页面出错。
2026-08-06 15:49:04 +08:00
2026-08-07 11:27:06 +08:00
### 4.5 客户端列表模块
2026-08-06 15:49:04 +08:00
**顶部工具条:** 客户端名称搜索框、搜索按钮;管理员另有删除按钮。
2026-08-06 15:49:04 +08:00
**中间表格:** 名称、序列号、状态、当前负责人、最近活动时间、更新时间;
管理员另有勾选和操作列。
2026-08-06 15:49:04 +08:00
**注册方式:**
- `[必须]` 设置页通过独立登记接口幂等新增或更新 Client;该接口不得领取或修改任务。
- `[必须]` 领取接口保留隐式登记作为旧 Client 的兼容兜底。
- `[必须]` 不设心跳接口,在线状态由登记、领取和提交产生的最近活动时间派生。
2026-08-06 15:49:04 +08:00
- `[必须]` `状态` 是**派生字段,不存库**:
最近活动时间在 N 分钟内算"在线",否则"离线"。`[建议]` N 默认 10 分钟。
- 名称由客户端上报,操作员可以在 Admin 这边改成好记的名字。
**采购员归属:**
- 一个采购员可以绑定多台客户端;一台客户端同一时间最多绑定一个采购员,也允许未绑定。
- 只有管理员能绑定、转交、解绑和删除客户端;采购员只读看到当前绑定给自己的客户端。
- 转交和解绑保留负责人、操作管理员、开始/结束时间等完整历史。
- 归属只影响 Web 可见范围和采购任务创建时的客户端候选,不改变 Client 四接口,
也不改动已分配、领取或执行中的任务。
- 新绑定目标必须是启用中的采购员。已禁用采购员的历史不删除。
2026-08-06 15:49:04 +08:00
**底部状态条:** 在线 / 离线数量统计。
### 4.6 AI 规格批量匹配(#199)
- AI 集成保留在 Admin 内部,不增加独立微服务;MVP 使用 OpenAI 兼容协议。
- 只有管理员能够维护服务商、模型和部署级密钥;采购员只能发起规格匹配。
- API Key 不进入数据库、`data/`、日志或 HTTP 响应,保存后只显示固定掩码和尾四位。
- 模型只能选择服务端提供的当前可购买候选;不确定、超时或硬校验失败时保持人工处理。
- AI 匹配不会创建采购任务,不改变 Client 接口,也不放宽真实采购门禁。
- 采购员在顺运宝列表勾选最多 100 条可匹配明细后创建批次;批次固定使用创建时的服务商、
模型和运行参数快照。相同蝦皮商品、顺运宝规格及 PDD 候选上下文在一个批次内只调用一次,
其余明细复用结果。
- 页面持续显示批次总数、已处理、成功、复用、待人工、失败和逐条原因。Admin 重启时把未完成
批次标为中断,已经保存的映射不回滚;操作员可以重新勾选未成功条目安全重试。
- “AI规格匹配”是当前有效映射的来源筛选,不替代“可创建采购任务”等业务阶段。人工修改后
当前来源立即变为人工,但历史 AI 决策和批次记录继续保留。
### 4.7 采购运行时真机规格解析(#255)
采购任务已经在 Client 上选定颜色,但任务目标尺码和当前 PDD 页面文字无法精确对应时,
Client 可以把这一刻页面显示的可购买尺码提交给 Admin 做一次解析。该能力只用于解决
“任务规格与真机候选文字不一致”,不会改变正常精确匹配流程。
- Admin 必须先核对采购任务、任务版本、PDD 商品、任务原始规格和该 Client 的领取历史;
任一身份不一致都拒绝,并且不产生解析决策。
- 候选编号、原文、颜色和尺码由 Client 按页面顺序固定。Admin 重新计算候选快照哈希,
AI 只能选择本次请求内的候选短编号,不能生成或改写 PDD 规格。
- 先执行确定性规则:忽略大小写、空白和分隔符,处理已明确的繁简差异,并支持唯一的
`kg / 公斤 / 千克 / 斤` 单值或正向区间等价。只有唯一候选等价时才自动返回;多个等价、
倒序或多个候选同时重叠时直接返回 `uncertain`,不交给 AI 猜一个。
- 规则没有唯一结论时才使用当前已启用且通过连接测试的 AI 配置;模型结论仍须通过候选
白名单、冲突/缺失维度和管理员置信度阈值门禁。模型超时、格式错误或越权候选都形成
可审计结论,不把模型输出直接写进任务。
- `pdd_products.skus_json.spec_source=shopee_backfill` 只用于诊断规格来源,既不自动触发 AI,
也不拒绝本次解析;是否需要解析只由真机候选和任务目标能否唯一对应决定。
- 相同任务、执行尝试和候选快照幂等重放时返回第一次的完整响应;并发相同请求只完成一条
最终决策。候选观察与最终决策分两个短事务,调用模型期间不持有数据库事务。
- 解析记录是只追加的运行审计。它不得修改 `tasks` 状态、版本、目标规格、价格、
`task_runs.irreversible_action_at` 或 `pdd_products.skus_json`,也不得保存控件树、截图、订单、
收货信息、Cookie、Token 或 API Key。
- Admin 返回候选后,Client 仍须重新读取页面并继续执行商品、规格、数量、总价、地址、
不可逆标记和单次提交门禁;运行时解析不能绕过任何真实下单安全检查。
2026-08-06 15:49:04 +08:00
## 5. 创建采购任务的校验
`[必须]` 下面任何一条不满足就不允许创建,并明确告诉操作员缺什么:
1. 该蝦皮商品已填 PDD 链接;
2. 当前 PDD 商品已采集成功(`skus_json` 非空);
2026-08-10 12:24:23 +08:00
3. 该顺运宝规格非空,且已有当前 PDD 商品的规格映射;
2026-08-06 15:49:04 +08:00
4. 数量大于 0;
5. **人民币单价上限已填且大于 0** —— 默认从当前映射的 PDD 规格价格带出,操作员可改但不允许为空;页面显示 `单价 × 数量` 的订单总价上限,后端按最新数量重新计算并把总价写入 `max_price_cent`;
6. 已选择分配的客户端;
7. 所选客户端存在且在当前账号可见范围内;其在线状态和 `purchase_mode` 只影响何时领取,不阻止提前指派;
8. 同一顺运宝明细没有 `pending` / `assigned` / `claimed` 的采购任务;
9. 同一顺运宝明细没有 `succeeded` 的采购任务;成功采购是终态,禁止重复采购;
10. 同一顺运宝明细没有 `manual_review` 的采购任务;该状态可能已经下单,必须先人工核对,禁止重新采购。
`failed` 和 `cancelled` 继续沿用现有人工判断后重新创建策略。本规则必须在服务端校验,
不能只依赖顺运宝列表的复选框和按钮状态。
2026-08-06 15:49:04 +08:00
2026-08-10 17:52:48 +08:00
Admin 新建采购任务固定使用不可变的 `execution_mode=live`,不向采购员提供模式选择:
- `live` 会创建真实未付款订单,管理员和其他正常状态用户均可创建;点击明确标注的创建按钮并提交表单即表示创建,不再要求额外复选框或确认短语;
- 必须显式指定当前账号可见的 Client;不按最后登记的 `purchase_mode` 限制创建,未就绪或离线 Client 等待就绪后领取;
- 创建时记录操作用户和操作时间。取消、重派和结果回传均不得修改执行模式。
2026-08-10 17:52:48 +08:00
- 数据库和 API 继续保留 `dry_run/live`,历史任务和缺省兼容值仍按 `dry_run`,不得批量升级历史任务。
2026-08-06 15:49:04 +08:00
第 5 条是硬要求:Client 契约规定采购任务必须带明确的价格保护
(见 [Client 契约](../client/04-admin-api-contract.md) §4),没有它 Client 会拒绝执行。
## 6. 状态定义
2026-08-07 10:27:39 +08:00
### 6.1 采集状态
2026-08-06 15:49:04 +08:00
2026-08-07 10:27:39 +08:00
界面上显示 5 种,但**数据来自两张表**——采集的对象是 PDD 商品,
所以状态存在 `pdd_products` 上,不在蝦皮商品上。
| 界面显示 | 怎么判断 |
|---|---|
| 未填链接 | `shopee_products.pdd_goods_id` 为空 |
| 未采集 | `pdd_products.collect_status = 'pending'` |
| 采集中 | `= 'collecting'` |
| 已采集 | `= 'collected'` |
| 采集失败 | `= 'failed'`,原因在 `collect_msg` |
2026-08-06 15:49:04 +08:00
`[必须]` `collecting` 状态的商品不允许再建采集任务,按钮置灰并提示"已在采集中"。
2026-08-07 10:27:39 +08:00
`[必须]` 状态挂在 PDD 商品上,好处是**两个蝦皮商品指向同一个 PDD 链接时,
状态只有一份**——不会各记一份还可能不一致,也不会把同一个商品采两遍。
2026-08-06 15:49:04 +08:00
### 6.2 任务状态(采集任务和采购任务通用)
这是 **Admin 侧的状态**,和 Client 本地的 8 个状态是两套,不要混
(Client 侧见 [03 数据模型](../client/03-data-model.md) §7)。
| 值 | 中文 | 什么时候 |
|---|---|---|
| `pending` | 待分配 | 刚创建,还没指定客户端 |
| `assigned` | 待领取 | 已分配给某个客户端 |
| `claimed` | 已领取 | 客户端领走了,正在执行 |
| `succeeded` | 成功 | 收到结果 |
| `manual_review` | 需人工 | 客户端报告需要人处理 |
| `failed` | 失败 | 客户端报告失败 |
| `cancelled` | 已取消 | 人工取消 |
顺运宝页面根据关联采购任务实时推导采购阶段:`succeeded` 显示“采购完成”,
`manual_review` 显示“采购待人工核对”,活动任务显示“已创建采购任务”。优先级按
成功、待人工核对、活动任务、其他处理阶段排列;完成和待核对行都只能查看任务,
不能再次创建采购任务。
2026-08-06 15:49:04 +08:00
`[必须]` **Admin 看不到客户端执行到哪一步**(没有心跳,是有意的)。
`claimed` 之后就只能等结果。想知道细节看 Client 那边的界面。
客户端提交失败时带的 `status`,按下表落地:
| 客户端报告 | Admin 置为 |
|---|---|
| `retry_wait` | `assigned`(等它再来领) |
| `manual_review` | `manual_review` |
| `failed` | `failed` |
| `cancelled` | `cancelled` |
## 7. 金额与币种
`[必须]` **一律用整数存,禁止浮点。** 字段名带单位后缀。
| 场景 | 币种 | 字段示例 |
|---|---|---|
| 蝦皮售价 | 台币 | `price_twd_cent` |
| PDD 采购单价、订单总价上限 | 人民币 | SKU `price_cent`、任务 `max_price_cent` |
2026-08-06 15:49:04 +08:00
`[必须]` **两种币种不得混用,不得互相换算后覆盖原值。**
采购任务里的单价及订单总价上限是**人民币**:单价默认来源是采集回来的 PDD SKU 价格并由采购员逐行确认,订单总价上限由后端按 `单价上限 × 数量` 计算,
2026-08-06 15:49:04 +08:00
和蝦皮的台币售价没有换算关系。
`[待定]` 是否需要展示汇率或利润,待业务确认。MVP 不做。
## 8. MVP 范围
MVP 包含:
2026-08-07 11:27:06 +08:00
- 五模块页面框架和统一的三段式布局;
2026-08-10 01:22:22 +08:00
- MySQL 8.4 建库与追加式迁移;SQLite 仅作为一次性历史数据迁移来源;
- 商品目录批量接口(蝦皮/PDD/关联 upsert)、导入记录、蝦皮搜索、批量删除、手动新增、编辑弹窗;
2026-08-06 15:49:04 +08:00
- PDD 链接录入与发起采集;
- 顺运宝数据的**手工录入或造数**(同步按钮占位);
- 规格匹配弹窗与可复用映射;
- 创建采购任务并分配客户端;
- 给 Client 的四个接口:登记、领取、提交结果、提交失败;
- 客户端自动注册与在线状态;
- 第一次启动时初始化一个管理员;
- Admin 网页登录、退出和 Session;
- 管理员创建、禁用和重置采购员账号。
- 管理员绑定、转交和解绑客户端;采购员只读查看自己的客户端。
2026-08-06 15:49:04 +08:00
MVP 之后:
- 接入顺运宝真实同步;
- 打包成 exe;
- 统计报表。
## 9. 非目标
- 不做面向外部的公网服务,只在内网/本机运行。
- 不直接对接蝦皮和拼多多的接口。
- 不由模型选择或更换 PDD 商品链接;规格自动匹配只能从后端当前可购买候选中选择,硬校验不通过时仍由人确认。
2026-08-06 15:49:04 +08:00
- 不自动付款。
- 不开放用户自助注册;第一个管理员只能通过首次初始化创建。
- 不做复杂 RBAC、细粒度页面权限、单点登录和第三方登录。
- 本阶段不做 Client 登录或 API Key,不改变 Client 四接口契约。
2026-08-06 15:49:04 +08:00
## 10. 非功能需求
2026-08-10 01:22:22 +08:00
- 集中部署,操作人员规模是个位数;不追求大规模并发,但任务领取必须保证并发唯一。
2026-08-06 15:49:04 +08:00
- 页面在 1366×768 上可正常使用。
- 商品目录批次和 PDD Excel 导入应在可接受时间内完成,并显示结果统计。
2026-08-06 15:49:04 +08:00
- 所有写操作有 CSRF 防护,所有 SQL 参数化。
- 密码只保存成熟算法生成的哈希;Session 有过期、退出和账号禁用失效机制。
- 首次管理员、采购员初始密码和重置密码统一要求至少 6 个字符,最多 72 个字节;
前端提示与服务端校验必须一致。
2026-08-06 15:49:04 +08:00
- 日志、页面、导出不含 token、密码、Cookie。
- 数据放程序旁边的 `data/`,便携模式,见 [02 架构](02-architecture.md) §6。
## 11. 待确认事项
不许因此停工。先按临时默认值做,定了再按工单改。
| # | 待确认什么 | 临时默认值 |
|---|---|---|
| 1 | 顺运宝同步方式(接口?导出文件?) | 按钮占位,数据靠手工录入或造数 |
| 2 | 蝦皮有无全量规格导出 | 没有。靠反复导入累积 + 手动新增补齐 |
| 3 | Admin 是否需要登录 | 已定案:MVP 实现管理员初始化、网页登录和最小账号管理 |
2026-08-06 15:49:04 +08:00
| 4 | 是否需要利润和汇率展示 | 不做 |
**已定案:**
2026-08-07 11:27:06 +08:00
- PDD 链接**人工填写**,不自动抓取。
- PDD 商品是**独立模块**,创建商品和创建采集任务都在那里;
蝦皮数据模块的编辑弹窗负责的是「这个蝦皮商品对应哪个 PDD 商品」。
- 采集任务和采购任务都允许指定当前账号可见的客户端,也允许留空;留空时由符合条件的客户端领取。
2026-08-06 15:49:04 +08:00
- 任务**分配给指定客户端**,Client 只领分给自己的。
- **不加心跳接口**;设置页显式登记,领取接口保留兼容登记,在线状态由最近活动时间派生。
2026-08-10 12:24:23 +08:00
- 规格映射**独立成表且可复用**,按 `(蝦皮商品 ID, spec_key, PDD 商品 ID)` 隔离;换 PDD 商品不会误用旧映射,换回时可继续复用。
- 货运单的规格原文来自顺运宝;不再依赖蝦皮 `商品規格ID` 才能关联 PDD 和建立规格映射。
- Admin 不提供固定默认密码;第一次启动由用户创建第一个管理员,之后初始化入口永久关闭。
- 账号只有 `admin`(管理员)和 `purchaser`(采购员)两种固定角色;采购员不能管理用户。
- Web 登录只保护 HTML 页面,`/api/v1/client/*` 不使用网页登录 Session,现有 Client 行为保持不变。
2026-08-06 15:49:04 +08:00
- Go 版本固定 **1.23.0**。