24 KiB
01 Admin 产品需求基线
- 文档状态:基线草案,待需求评审
- 适用范围:
admin/ - 产品类型:集中部署的 Web 管理端(Go + Gin + HTML 模板 + MySQL 8.4)
本文档中 [必须] / [建议] / [待定] 的含义见 文档索引。
没有标注的默认是 [必须]。看不懂的词查 术语表。
1. 背景与目标
我们在蝦皮(台湾)卖货,在拼多多(大陆)进货,中间由顺运宝提供货运单。 Admin 的职责是把这三方的数据串起来,最终产出 Client 能执行的采购任务。
产品目标:
- 导入并维护蝦皮商品档案,为每个商品登记对应的拼多多链接。
- 同步顺运宝货运单,知道"这单该买什么"。
- 把蝦皮的颜色尺码,对应到拼多多的颜色尺码,且这个对应关系可复用。
- 生成采购任务并分配给指定客户端,跟踪执行结果。
- 管理客户端清单,知道谁在干活。
- 通过登录保护 Admin 网页,并由管理员维护采购员账号。
2. 用户与外部系统
- **管理员:**首次初始化 Admin,执行全部业务操作,并创建、禁用或重置采购员账号。
- **采购员:**登录后执行现有业务操作,但不能管理用户。
- **Client:**领取任务、在安卓设备上执行、提交结果。契约见 Client 侧文档。
- **蝦皮:**只提供 Excel 报表导出,没有接口对接。
- **顺运宝:**提供货运单,
[待定]同步方式待确认,MVP 先做占位按钮。 - **拼多多:**由 Client 操作,Admin 不直接接触。
3. 完整业务链路
这条链路是理解全部五个模块的关键,先看懂它:
蝦皮报表 ──导入──→ 商品表 + SKU 表
│
编辑弹窗:人工填 PDD 链接
│
↓
PDD 商品页 ──────→ PDD 商品表
│ 点【创建采集任务】
↓
创建采集任务(不指定 Client)→ 谁领到算谁的
↓
Client 采回 PDD 的颜色尺码和价格
↓
存入 PDD 商品表.skus_json
│
顺运宝货运单 ─同步─→ 货运单表
│ 双击行
↓
匹配弹窗:蝦皮规格 ←→ PDD 规格
(匹配过的自动带出,只有新规格要手工做)
↓
创建采购任务 → 分配 Client → 被领取
↓
Client 下单 → 提交结果 → 人工付款
两个要点:
- 采集是商品级的,一个 PDD 商品采一次,所有相关订单共用结果。
- 匹配是 SKU 级的且可复用,同一个蝦皮 SKU 只需人工匹配一次。
4. 产品范围
顶级导航固定五个模块,顺序不变:
| # | 模块 | 职责 |
|---|---|---|
| 1 | 蝦皮数据 | 商品档案、PDD 链接、发起采集 |
| 2 | PDD 商品 | PDD 商品档案、发起采集、查看采回来的规格价格 |
| 3 | 顺运宝数据 | 货运单、规格匹配、生成采购任务 |
| 4 | 采集采购 | 采集任务和采购任务的执行进度跟踪 |
| 5 | 客户端列表 | 客户端注册与状态 |
五个模块统一使用三段式页面布局:顶部工具条 / 中间带勾选的表格 / 底部状态条。 详见 05 界面规范。
PDD 商品之所以单独一个模块,是因为它在数据上就是独立实体 (自己的表、自己的采集状态,可以被多个蝦皮商品共用), 见 03 数据模型 §4。挂在蝦皮模块下面的话, 同一个 PDD 商品被两个蝦皮商品引用时就说不清该显示在谁名下。
4.1 蝦皮数据模块
顶部工具条: 导入按钮、状态筛选(全部 / 待补规格 / 未填 PDD 链接 / 已填链接)、 商品 ID 搜索框、搜索按钮、删除按钮。采集从商品详情中发起,避免脱离商品上下文。
[必须] 状态筛选是刚需,不是锦上添花(工单 #43):本页的主要用途是维护
(找出需要补规格的商品、找出还没关联 PDD 链接的商品),只靠商品 ID 搜索的话,
操作员得先知道 ID 才能搜——而"哪些商品需要处理"恰恰是不知道 ID 的时候才要问的。
实测样本 5195 个商品里只有 6 个待补规格,只做分页要翻 260 页才能找全,等于找不到。
界面细节和筛选条件的 SQL 见 05 界面规范 §4.1。
中间表格(按 SKU 展开显示,数据来自商品表和 SKU 表联查):
| 列 | 说明 |
|---|---|
| 勾选 | 支持批量删除 |
| 商品 ID | 蝦皮商品编号 |
| 商品名称 | |
| 颜色 / 尺码 / 建议 | 从规格原文解析,解析不出来留空并标记 |
| PDD 链接 | 人工填写,空的要显眼 |
| 采集状态 | 见 §6 |
| 更新时间 |
双击行打开编辑弹窗,这是从蝦皮商品出发填 PDD 链接、发起采集的入口:
- 可编辑当前 PDD 商品链接;规格表继续只读展示;
- 保存链接时从完整链接解析
goods_id,在同一事务内复用或复活 PDD 商品档案并更新当前关联; - 更换成不同
goods_id时必须明确勾选确认,旧规格映射保留但不会被当前关联查询误用; - 弹窗内提供【创建采集任务】按钮,复用 PDD 商品模块的幂等与超时重试规则;
[必须]采集按钮在本页只出现在弹窗里,不要放在每个表格行—— 一个商品有 N 个 SKU 行,放行上就是 N 个按钮干同一件事,还会建出 N 个重复任务。
[注意] PDD 商品模块(§4.2)继续作为商品档案、采集状态和故障诊断页;
蝦皮详情和顺运宝处理详情是带业务上下文的关联入口,两者调用同一个 Service。
其他要求:
[必须]支持手动新增一条 SKU。报表只含有销售成绩的 SKU(样本里平均每商品仅 1.17 个), 订单来了查无此 SKU 是常态,必须能补。[必须]导入是 upsert,绝不允许先清空再导入,否则人工填的 PDD 链接会被洗掉。[必须]规格原文永远保留,解析失败留空,不要猜。
底部状态条: 最近一次导入的时间、条数、失败行数。
4.2 PDD 商品模块
维护拼多多商品档案,并发起采集。这条链路不依赖蝦皮和顺运宝的任何数据, 可以单独跑通:建商品 → 建采集任务 → Client 领走执行 → 提交结果 → 页面显示已采集。
顶部工具条: 创建按钮、创建采集任务按钮、采集状态筛选、商品 ID/链接搜索框、搜索按钮、删除按钮。
中间表格:
| 列 | 说明 |
|---|---|
| 勾选 | 支持批量删除、批量建采集任务 |
| 商品 ID | 从链接解析出来的 goods_id |
| 标题 | 采集回来的,未采集时显示占位文案 |
| PDD 链接 | 截断显示,可点开 |
| 采集状态 | 见 §6.1,用中文文字,不能只靠颜色 |
| 规格数 | 从 skus_json 算 |
| 采集时间 | 未采集显示 — |
| 更新时间 |
[必须] 规格数必须显示。 采到 1 个和采到 20 个差别很大——
只采到 1 个通常意味着客户端没点开规格面板,是采集有问题。
不显示这一列的话,要点进每个商品才能发现。
创建: [必须] 只填 PDD 链接,其余字段全靠采集回填。
[必须] 链接必须能解析出 goods_id,解析不出来直接报错并且不写库。
不接受短链接——goods_id 上有 UNIQUE 约束,防重全靠它,
拿不到就没法查重,同一个商品会存成好几行。
双击行打开弹窗: 查看商品信息,可编辑链接,并显示采回来的规格和价格。
[必须]规格和价格必须显示,这是采集结果的全部价值所在—— 不显示的话操作员没法确认"采得对不对、是不是我要的那个商品"。[必须]维度顺序按skus_json里的dimensions(Go 的 map 无序,必须靠它定顺序)。[必须]价格显示成¥12.56,底层存整数分;price_cent为null时显示"未采到", 不得显示成 ¥0.00。[必须]编辑链接时新链接必须还是同一个商品。换成别的商品要新建一条: 这一行上挂着采集结果和 SKU 映射,goods_id一换那些数据就全指到错的商品上了。
创建采集任务:
[必须]不指定客户端,谁领到就在领取时标记谁。采集是纯读取操作, 哪台机器跑都一样,指定了反而会在那台机器关着的时候干等。[必须]按goods_id去重;collecting状态的跳过,并在结果里说明跳过了几个。[必须]按钮文案用「创建采集任务」,不要用「采集」。它做的是建一个任务, 不是立刻去采——真正的采集要等 Client 来领、去手机上跑,可能几秒也可能几分钟。
删除: 软删除。二次确认要写明"重新创建同一链接可恢复,但采集结果会清空"。 不硬删是因为 SKU 映射指向它,硬删会把人工攒了很久的匹配成果一起带走。
底部状态条: 各采集状态的条数统计。
4.3 顺运宝数据模块
顶部工具条: 同步按钮、订单号搜索框、搜索按钮、创建采购任务按钮、删除按钮。
[待定]同步按钮 MVP 阶段只做占位:点击后提示"同步功能待接入",不发请求。
中间表格:
| 列 | 说明 |
|---|---|
| 勾选 | 支持批量删除、批量建任务 |
| 货运单 ID | |
| 订单号 | |
| 商品标题 | |
| 蝦皮商品 ID | 关联蝦皮数据模块 |
| 数量 | |
| 价格 | 蝦皮售价,台币分,见 §7 |
| 图片 | 存 URL,表格里显示缩略图 |
| 处理阶段 | 采购数据异常(顺运宝未提供规格)/ 未关联 PDD / 待采集 / 采集中 / 采集失败 / 规格待匹配 / 可采购 / 已创建任务 |
| 下一步 | 每行只显示当前最需要完成的一个操作 |
| 更新时间 |
原始的完整货运单 JSON 存 syb_data 字段,不作为表格列显示,在详情里看。
双击行或点击下一步打开处理弹窗:
-
顺运宝的
product_spec是匹配主链路的真实来源;系统不再要求先确认蝦皮 SKU。 -
“规格身份键”
spec_key只对顺运宝规格原文去除首尾空白、折叠连续空白,用于判断是否是同一条规格;颜色、尺寸和建议的解析只用于显示与推荐,不改变身份。 -
找到蝦皮商品后,可在同一弹窗填写完整 PDD 链接并创建采集任务;链接关联规则与蝦皮详情完全一致。
-
显示蝦皮规格原文和当前 PDD 商品
skus_json里的动态规格组合与人民币价格; -
PDD 维度来自采集结果的
dimensions,不得写死成颜色、尺码两个维度; -
操作员选好对应关系,点保存;
-
[必须]保存的是可复用的商品规格映射,键为(蝦皮商品 ID, spec_key, PDD 商品 ID),不是这一张订单的临时数据。 下次遇到同商品、同规格和同 PDD 商品时自动带出。 -
[必须]该商品尚未采集(skus_json为空)时,弹窗直接展示关联和创建采集任务入口, 而不是显示一个空列表让人困惑。 -
规则引擎只对可购买候选做 A/B/C/冲突分层和稳定排序;只有唯一、无额外维度歧义的 A 级才预选。
-
预选不会自动保存或创建采购任务。页面必须显示推荐理由,采购员点击保存后才写映射和只追加的决策审计。
创建采购任务: 只有映射仍有效且没有进行中采购任务的行可勾选。确认弹窗要求 选择当前账号可见的客户端,并逐行确认人民币价格上限;服务端仍按 §5 全量复核。
底部状态条: 最近同步时间、待匹配条数。
4.4 采集采购模块
tasks 是一张表,用 task_type 区分采集和采购。这个模块把两种任务
放在同一个列表里显示——分开成两个页面的话,采集任务建出来了,
操作员却只能盯着 PDD 商品的 collect_status 猜任务本身怎么样了
(被谁领了、领了多久、失败在哪一步),完全看不出任务这一层的信息。
[必须] 名字是「采集采购」,不是「任务」。它直接点出这一页装的是
哪两类任务,比泛称「任务」更能让操作员一眼知道点进去看什么。
[必须] 路由 /tasks 不变,避免已有的书签和文档链接失效。
顶部工具条:
[类型▾ 全部] [状态▾ 全部] 关键词[____________] [搜索] [删除]
- 类型:全部 / 采集 / 采购。
- 状态:全部 + 7 个状态,见 §6.2。
- 关键词:同时匹配任务编号、订单号、PDD 商品 ID。
[必须] 筛选比搜索更常用:这个页面最常被问的问题是"有没有卡住的任务",
不是"订单 SO-001 怎么样了",所以类型和状态筛选要放在最前面。
中间表格:
两种任务的业务字段完全不同——采购有订单号/颜色尺码/数量/价格上限, 采集只有 PDD 链接。并列显示的话采集任务行会有一半是空列, 所以改成固定列 + 一列「目标」概括业务信息:
| 列 | 说明 |
|---|---|
| 勾选 | 支持批量删除 |
| 任务编号 | task_id |
| 类型 | 采集 / 采购,文字,不能只靠颜色 |
| 目标 | 采集:PDD <pdd_goods_id>(能 join 到未删除的商品标题时追加显示);采购:<order_no> · <颜色/尺码> · <数量>件 · ≤<价格上限> |
| 状态 | 中文,7 个取值见 §6.2 |
| 客户端 | assigned_client;无主任务显示 —(#17 之后采集任务默认无主,这一列会大量为空) |
| 更新时间 | 本地时区 |
[必须] 目标列所需字段全在 tasks 表上(pdd_goods_id / order_no /
pdd_options / quantity / max_price_cent),不需要 join。
[建议] 采集任务的目标 join pdd_products 取标题显示更友好,
但 join 不到或商品已软删除时必须退回只显示 pdd_goods_id,不能空着。
[必须] 目标列的拼接逻辑在 service 层组装成一个字符串,模板只负责显示——
散在模板里没人维护得住。
[必须] 价格上限显示成 ¥42.00,底层是整数分。
双击行打开详情弹窗:
[必须] 只读。改派 / 重试 / 取消是后续工单的范围,本页不提供入口。
弹窗显示:类型、状态、分配客户端、领取时间、完成时间;执行参数 (PDD 链接,采购任务额外显示目标规格、数量、价格上限);错误信息 (错误码、错误说明,没有错误时不显示这一段)。
[必须] 采购专有字段(数量、价格上限、目标规格)在采集任务的弹窗里
整段隐藏,不显示空行。
[建议] result_data(Client 提交的完整结果)默认折叠,提供展开查看;
过长时截断显示。
底部状态条:
共 42 条 · 待分配 3 · 待领取 5 · 已领取 2 · 成功 30 · 需人工 1 · 失败 1 · 已取消 0
[必须] 统计要跟随当前筛选。筛了「采集」就只统计采集任务,
否则数字和表格对不上,操作员会以为页面出错。
4.5 客户端列表模块
顶部工具条: 客户端名称搜索框、搜索按钮;管理员另有删除按钮。
中间表格: 名称、序列号、状态、当前负责人、最近活动时间、更新时间; 管理员另有勾选和操作列。
注册方式:
[必须]设置页通过独立登记接口幂等新增或更新 Client;该接口不得领取或修改任务。[必须]领取接口保留隐式登记作为旧 Client 的兼容兜底。[必须]不设心跳接口,在线状态由登记、领取和提交产生的最近活动时间派生。[必须]状态是派生字段,不存库: 最近活动时间在 N 分钟内算"在线",否则"离线"。[建议]N 默认 10 分钟。- 名称由客户端上报,操作员可以在 Admin 这边改成好记的名字。
采购员归属:
- 一个采购员可以绑定多台客户端;一台客户端同一时间最多绑定一个采购员,也允许未绑定。
- 只有管理员能绑定、转交、解绑和删除客户端;采购员只读看到当前绑定给自己的客户端。
- 转交和解绑保留负责人、操作管理员、开始/结束时间等完整历史。
- 归属只影响 Web 可见范围和采购任务创建时的客户端候选,不改变 Client 四接口, 也不改动已分配、领取或执行中的任务。
- 新绑定目标必须是启用中的采购员。已禁用采购员的历史不删除。
底部状态条: 在线 / 离线数量统计。
5. 创建采购任务的校验
[必须] 下面任何一条不满足就不允许创建,并明确告诉操作员缺什么:
- 该蝦皮商品已填 PDD 链接;
- 当前 PDD 商品已采集成功(
skus_json非空); - 该顺运宝规格非空,且已有当前 PDD 商品的规格映射;
- 数量大于 0;
- 价格上限已填且大于 0 —— 默认从当前映射的 PDD 规格价格带出,操作员可改,但不允许为空;
- 已选择分配的客户端;
- 所选客户端存在且在当前账号可见范围内;
- 同一顺运宝明细没有
pending/assigned/claimed的采购任务。
第 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 包含:
- 五模块页面框架和统一的三段式布局;
- MySQL 8.4 建库与追加式迁移;SQLite 仅作为一次性历史数据迁移来源;
- 蝦皮 Excel 导入(upsert)、搜索、批量删除、手动新增、编辑弹窗;
- PDD 链接录入与发起采集;
- 顺运宝数据的手工录入或造数(同步按钮占位);
- 规格匹配弹窗与可复用映射;
- 创建采购任务并分配客户端;
- 给 Client 的四个接口:登记、领取、提交结果、提交失败;
- 客户端自动注册与在线状态;
- 第一次启动时初始化一个管理员;
- Admin 网页登录、退出和 Session;
- 管理员创建、禁用和重置采购员账号。
- 管理员绑定、转交和解绑客户端;采购员只读查看自己的客户端。
MVP 之后:
- 接入顺运宝真实同步;
- 打包成 exe;
- 统计报表。
9. 非目标
- 不做面向外部的公网服务,只在内网/本机运行。
- 不直接对接蝦皮和拼多多的接口。
- 不自动决定买哪个商品——PDD 链接和规格匹配都由人确认。
- 不自动付款。
- 不开放用户自助注册;第一个管理员只能通过首次初始化创建。
- 不做复杂 RBAC、细粒度页面权限、单点登录和第三方登录。
- 本阶段不做 Client 登录或 API Key,不改变 Client 四接口契约。
10. 非功能需求
- 集中部署,操作人员规模是个位数;不追求大规模并发,但任务领取必须保证并发唯一。
- 页面在 1366×768 上可正常使用。
- 导入 1 万行 Excel 应在可接受时间内完成,并显示进度或结果统计。
- 所有写操作有 CSRF 防护,所有 SQL 参数化。
- 密码只保存成熟算法生成的哈希;Session 有过期、退出和账号禁用失效机制。
- 首次管理员、采购员初始密码和重置密码统一要求至少 6 个字符,最多 72 个字节; 前端提示与服务端校验必须一致。
- 日志、页面、导出不含 token、密码、Cookie。
- 数据放程序旁边的
data/,便携模式,见 02 架构 §6。
11. 待确认事项
不许因此停工。先按临时默认值做,定了再按工单改。
| # | 待确认什么 | 临时默认值 |
|---|---|---|
| 1 | 顺运宝同步方式(接口?导出文件?) | 按钮占位,数据靠手工录入或造数 |
| 2 | 蝦皮有无全量规格导出 | 没有。靠反复导入累积 + 手动新增补齐 |
| 3 | Admin 是否需要登录 | 已定案:MVP 实现管理员初始化、网页登录和最小账号管理 |
| 4 | 是否需要利润和汇率展示 | 不做 |
已定案:
- PDD 链接人工填写,不自动抓取。
- PDD 商品是独立模块,创建商品和创建采集任务都在那里; 蝦皮数据模块的编辑弹窗负责的是「这个蝦皮商品对应哪个 PDD 商品」。
- 采集任务不指定客户端,谁领到算谁的;采购任务仍可分配,也允许留空。
- 任务分配给指定客户端,Client 只领分给自己的。
- 不加心跳接口;设置页显式登记,领取接口保留兼容登记,在线状态由最近活动时间派生。
- 规格映射独立成表且可复用,按
(蝦皮商品 ID, spec_key, PDD 商品 ID)隔离;换 PDD 商品不会误用旧映射,换回时可继续复用。 - 货运单的规格原文来自顺运宝;不再依赖蝦皮
商品規格ID才能关联 PDD 和建立规格映射。 - Admin 不提供固定默认密码;第一次启动由用户创建第一个管理员,之后初始化入口永久关闭。
- 账号只有
admin(管理员)和purchaser(采购员)两种固定角色;采购员不能管理用户。 - Web 登录只保护 HTML 页面,
/api/v1/client/*不使用网页登录 Session,现有 Client 行为保持不变。 - Go 版本固定 1.23.0。