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>
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
| 规格原文 | 蝦皮报表里没拆开的那一列,例如 `黑色,M【建議40-50公斤】` | `shopee_skus.spec_raw`,**永远原样保留** |
|
||||
| 建议 | 蝦皮规格里的建议体重,例如 `40-50公斤` | 不是"建议采购链接",别理解错 |
|
||||
| SKU 映射 | "蝦皮的这个规格 = 拼多多的那个规格"的对应关系 | `sku_mappings` 表。**匹配一次,以后同商品自动带出** |
|
||||
| 采集状态 | 某个蝦皮商品对应的 PDD 商品数据采到没有 | `shopee_products.collect_status` |
|
||||
| 采集状态 | 一个 **PDD 商品**的数据采到没有 | `pdd_products.collect_status`。挂在 PDD 商品上,不在蝦皮商品上——被采集的是 PDD 商品 |
|
||||
|
||||
## 2. 技术名词
|
||||
|
||||
|
||||
@@ -197,18 +197,24 @@ Admin 的职责是把这三方的数据串起来,最终产出 Client 能执行
|
||||
|
||||
## 6. 状态定义
|
||||
|
||||
### 6.1 采集状态(蝦皮商品)
|
||||
### 6.1 采集状态
|
||||
|
||||
| 值 | 中文 | 含义 |
|
||||
|---|---|---|
|
||||
| `no_link` | 未填链接 | 还没填 PDD 链接 |
|
||||
| `pending` | 未采集 | 已填链接,还没发起采集 |
|
||||
| `collecting` | 采集中 | 采集任务已创建,尚未回结果 |
|
||||
| `collected` | 已采集 | `pdd_data` 已就绪 |
|
||||
| `failed` | 采集失败 | Client 报告失败,可重新采集 |
|
||||
界面上显示 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 个状态是两套,不要混
|
||||
|
||||
+205
-45
@@ -99,31 +99,21 @@ CREATE TABLE shopee_products (
|
||||
shopee_status TEXT, -- 蝦皮「商品當前狀態」
|
||||
main_sku_code TEXT, -- 蝦皮「主商品貨號」
|
||||
|
||||
-- 下面三个是我们自己维护的,报表里没有,导入时绝不能覆盖
|
||||
pdd_goods_url TEXT, -- ★ 人工填写
|
||||
pdd_goods_id TEXT, -- 从 url 解析出来
|
||||
pdd_data TEXT, -- ★ 采集结果 JSON
|
||||
-- 下面两个是我们自己维护的,报表里没有,导入时绝不能覆盖
|
||||
pdd_goods_url TEXT, -- ★ 人工填写的 PDD 链接原文
|
||||
pdd_goods_id TEXT, -- 从 url 解析,指向 pdd_products.goods_id
|
||||
|
||||
collect_status TEXT NOT NULL DEFAULT 'no_link'
|
||||
CHECK (collect_status IN (
|
||||
'no_link', 'pending', 'collecting',
|
||||
'collected', 'failed'
|
||||
)),
|
||||
collect_error TEXT,
|
||||
collected_at TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_shopee_products_status
|
||||
ON shopee_products(collect_status);
|
||||
CREATE INDEX idx_shopee_products_pdd ON shopee_products(pdd_goods_id);
|
||||
```
|
||||
|
||||
- `pdd_data` 存 Client 采回来的 PDD 商品数据,结构和
|
||||
[Client 侧 `pdd_data`](../client/03-data-model.md) §8.1 一致,
|
||||
里面有 `dimensions` 和 `skus`,匹配弹窗右侧就是从它渲染的。
|
||||
- `[必须]` `pdd_data` 放在**商品级**,不是订单级。
|
||||
一个 PDD 商品采一次,所有相关订单共用。
|
||||
`[必须]` **采集结果和采集状态不在这张表里**,它们属于 PDD 商品,见 §4。
|
||||
|
||||
`pdd_goods_id` 表示"这个蝦皮商品**当前**对应哪个 PDD 商品"。
|
||||
PDD 商品下架换代时改这里,是一个随时会变的关联,不是永久绑定。
|
||||
|
||||
### 3.2 `shopee_skus` SKU 级
|
||||
|
||||
@@ -221,7 +211,120 @@ ON CONFLICT(goods_id) DO UPDATE SET
|
||||
导入结束返回 `{商品数, SKU数, 解析失败行号列表}`,页面上显示出来。
|
||||
**不要静默跳过失败行。**
|
||||
|
||||
## 4. `syb_orders` 顺运宝货运单
|
||||
## 4. `pdd_products` 拼多多商品
|
||||
|
||||
```sql
|
||||
CREATE TABLE pdd_products (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
goods_id TEXT NOT NULL UNIQUE, -- 从 PDD 链接解析
|
||||
url TEXT NOT NULL, -- 操作员填的链接原文
|
||||
title TEXT, -- 采集回来,人工核对用
|
||||
skus_json TEXT, -- 采集结果,结构见 §4.2
|
||||
|
||||
collect_status TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK (collect_status IN (
|
||||
'pending', 'collecting', 'collected', 'failed'
|
||||
)),
|
||||
collect_msg TEXT, -- 失败原因
|
||||
artifact_ref TEXT, -- 诊断产物位置
|
||||
collected_at TEXT,
|
||||
|
||||
deleted_at TEXT, -- 软删除
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_pdd_products_status ON pdd_products(collect_status);
|
||||
```
|
||||
|
||||
### 4.1 几个关键决定
|
||||
|
||||
**为什么 `goods_id` 不是主键却必须 UNIQUE**
|
||||
|
||||
主键用了自增 `id`,那 `goods_id` 就**不再天然防重**了。少了 UNIQUE,
|
||||
同一个 PDD 商品会被存成好几行:采好几遍、映射说不清指向哪一行。
|
||||
|
||||
配套规则:`[必须]` 保存链接时**先从 URL 解析出 `goods_id`,按它查重**,
|
||||
不要按 URL 查重——同一个商品的 URL 有很多写法(带不带分享参数),
|
||||
按 URL 查会漏掉,照样存重复。解析不出来就报错,让操作员给完整链接。
|
||||
|
||||
**为什么状态里没有"未填链接"**
|
||||
|
||||
这张表里有这一行,就说明链接已经填了。"未填链接"是**蝦皮侧**的状态
|
||||
(`shopee_products.pdd_goods_id` 为空)。界面上仍然显示 5 种,
|
||||
只是数据来源不同,见 [01 需求](01-requirements.md) §6.1。
|
||||
|
||||
**为什么用软删除**
|
||||
|
||||
`sku_mappings` 指向这张表。硬删会把人工攒了很久的匹配成果一起带走。
|
||||
软删除后界面不再显示,但记录和映射都还在。
|
||||
|
||||
`[必须]` 操作员重新填同一个链接时**要能复活**(清 `deleted_at`、状态置回
|
||||
`pending`、清空旧采集结果)。不复活的话 `goods_id` 的 UNIQUE 会让插入失败,
|
||||
操作员会看到一个莫名其妙的错误。
|
||||
|
||||
**为什么不存截图**
|
||||
|
||||
按已定案的 Artifact 策略([Client 契约](../client/04-admin-api-contract.md) §10 待确认 #3),
|
||||
客户端**只报本地引用、不上传文件**。截图在客户端那台机器上,Admin 显示不了。
|
||||
所以存 `artifact_ref`(形如 `client-001:artifacts/PDD-0001/attempt-xxx/`),
|
||||
告诉操作员去哪台机器的哪个目录捞。
|
||||
|
||||
### 4.2 `skus_json` 的结构
|
||||
|
||||
由 Client 采集后原样提交,Admin **不做转换**:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": 1,
|
||||
"goods_id": "737116531267",
|
||||
"title": "【现货】西装外套三件套",
|
||||
"captured_at": "2026-08-07T08:00:00Z",
|
||||
"dimensions": [
|
||||
{"key": "color", "name": "颜色分类"},
|
||||
{"key": "size", "name": "尺码"}
|
||||
],
|
||||
"skus": [
|
||||
{"options": {"color": "黑色", "size": "M"},
|
||||
"price_cent": 1256, "available": true, "raw_price": "¥12.56"},
|
||||
{"options": {"color": "白色", "size": "M"},
|
||||
"price_cent": 1256, "available": false, "raw_price": "¥12.56"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
每个字段都对应 Admin 的一个实际用途,没有多余的:
|
||||
|
||||
| 字段 | Admin 拿它干什么 | 不给会怎样 |
|
||||
|---|---|---|
|
||||
| `skus[].options` | 匹配弹窗列出规格供人选 | 没东西可选,匹配做不了 |
|
||||
| `skus[].price_cent` | 建采购任务时带出 `max_price_cent` | 价格保护填不了,Client 会拒绝执行 |
|
||||
| `skus[].available` | 不给缺货规格建任务 | 白跑一趟,Client 到手机上才发现卖光 |
|
||||
| `goods_id` / `title` | 核对"采的是不是要的那个商品" | 链接跳转、采错商品时静默存错 |
|
||||
| `dimensions` | 界面按顺序渲染下拉框 | Go 的 map 无序,不知道该先显示颜色还是尺码 |
|
||||
| `raw_price` | 价格解析出错时对账 | 只有数字,出错了没法查 |
|
||||
|
||||
`[必须]` 几条硬规则:
|
||||
|
||||
- **`price_cent` 是整数分**,不是 `12.56` 也不是 `"12.56"`。这个数要参与价格保护比对,是会花钱的判断,禁止浮点。
|
||||
- **采不到价格时给 `null`,不要给 0**。Admin 遇到 `null` 当"未知"处理并拒绝建任务,绝不当成 0 元。
|
||||
- **`options` 嵌一层,不平铺 `color`/`size`**。支持任意多个维度,碰到三维商品(颜色/尺码/款式)平铺的结构直接装不下。
|
||||
- **`dimensions` 只给 `key` 和 `name`,不给 values**。values 能从 `skus` 去重推出来,存两份迟早不一致。
|
||||
|
||||
### 4.3 落库时的两条校验
|
||||
|
||||
`[必须]` Client 提交采集结果时,Admin 必须校验:
|
||||
|
||||
1. **返回的 `goods_id` 必须等于请求采集的那个。** 不等说明链接跳转了或采错商品,
|
||||
要拒绝(`422 COLLECT_GOODS_MISMATCH`)并整体回滚。不拦的话,会把 B 的规格价格
|
||||
存到 A 名下,之后按它下单就是买错东西。
|
||||
2. **`skus` 为空要记成 `failed`,不是 `collected`。** 采到 0 个规格对业务毫无用处
|
||||
(商品下架、页面改版、解析器没认出来),显示"已采集"会让操作员以为好了,
|
||||
等建任务时才发现不对。
|
||||
|
||||
完整的采集提交原文仍然存进 `tasks.result_data`,审计链不断。
|
||||
|
||||
## 5. `syb_orders` 顺运宝货运单
|
||||
|
||||
```sql
|
||||
CREATE TABLE syb_orders (
|
||||
@@ -258,30 +361,82 @@ CREATE INDEX idx_syb_orders_list ON syb_orders(updated_at DESC, syb_id DESC);
|
||||
|
||||
**"匹配状态"是派生的,不存字段**:`sku_mappings` 里有对应记录就是"已匹配"。
|
||||
|
||||
## 5. `sku_mappings` 规格映射
|
||||
## 6. `sku_mappings` 规格映射
|
||||
|
||||
这张表是"蝦皮的这个规格 = 拼多多的那个规格",**匹配一次,以后复用**。
|
||||
"蝦皮的这个规格 = 拼多多的那个规格",**匹配一次,以后复用**。
|
||||
|
||||
```sql
|
||||
CREATE TABLE sku_mappings (
|
||||
shopee_sku_id TEXT PRIMARY KEY,
|
||||
goods_id TEXT NOT NULL,
|
||||
pdd_options TEXT NOT NULL, -- JSON: {"color":"黑色","size":"M码"}
|
||||
mapped_at TEXT NOT NULL,
|
||||
mapped_by TEXT,
|
||||
shopee_sku_id TEXT NOT NULL,
|
||||
pdd_goods_id TEXT NOT NULL, -- ★ 这条映射属于哪个 PDD 商品
|
||||
pdd_option_key TEXT NOT NULL, -- 规范化的组合键,见 §6.2
|
||||
pdd_options TEXT NOT NULL, -- 原始 options 对象,显示用
|
||||
goods_id TEXT NOT NULL, -- 蝦皮商品 ID,方便按商品批量查
|
||||
mapped_at TEXT NOT NULL,
|
||||
mapped_by TEXT,
|
||||
PRIMARY KEY (shopee_sku_id, pdd_goods_id),
|
||||
FOREIGN KEY (shopee_sku_id) REFERENCES shopee_skus(sku_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_sku_mappings_goods ON sku_mappings(goods_id);
|
||||
CREATE INDEX idx_sku_mappings_pdd ON sku_mappings(pdd_goods_id);
|
||||
```
|
||||
|
||||
- `pdd_options` 用 JSON 而不是固定的"颜色/尺码"两列,
|
||||
因为 PDD 商品可能有第三个规格维度
|
||||
(Client 侧 [01](../client/01-requirements.md) §11 已明确要求按任意维度设计)。
|
||||
- `[必须]` 打开匹配弹窗时**先查这张表**,有记录就自动带出,操作员只需确认。
|
||||
这是省人工的关键,不要做成每张订单都从头匹配。
|
||||
### 6.1 为什么主键要带上 `pdd_goods_id`
|
||||
|
||||
## 6. `tasks` 任务
|
||||
PDD 商品下架换代很频繁——A 买不到了就得换 B。
|
||||
|
||||
假设蝦皮商品 X 原来对应 PDD 商品 A,操作员匹配好了"黑色/M → 黑色/M码";
|
||||
后来 A 下架,换成了 B。如果映射只按 `shopee_sku_id` 存,那条旧映射还在,
|
||||
但它描述的是 **A 的规格**:
|
||||
|
||||
| | 后果 |
|
||||
|---|---|
|
||||
| 运气好 | B 没有"黑色/M码",建任务时找不到会报错,还算安全 |
|
||||
| **运气坏** | B 恰好也有"黑色/M码",但完全是另一件衣服 → **静默买错,事后查不出来** |
|
||||
|
||||
把 `pdd_goods_id` 放进主键后,`[必须]` 查映射**永远带上"当前对应的 PDD 商品"**:
|
||||
|
||||
```sql
|
||||
SELECT ... FROM sku_mappings
|
||||
WHERE shopee_sku_id = ?
|
||||
AND pdd_goods_id = (蝦皮商品当前的 pdd_goods_id)
|
||||
```
|
||||
|
||||
换成 B 就自然查不到 A 的映射,界面显示"待匹配"。**不需要在换商品时记得去删旧数据**
|
||||
——靠查询条件天然隔离,忘不了。
|
||||
|
||||
附带好处:A 的映射还留着。A 补货换回去时,之前的匹配成果直接复用。
|
||||
|
||||
### 6.2 `pdd_option_key` 的规范化
|
||||
|
||||
采集回来的 PDD 数据里**没有 SKU 编号**,一个规格只能靠它的 options 组合来认。
|
||||
而 JSON 对象的键是无序的:
|
||||
|
||||
```
|
||||
存映射时:{"color":"黑色","size":"M"}
|
||||
采回来时:{"size":"M","color":"黑色"}
|
||||
```
|
||||
|
||||
这两个是同一个规格,但字符串不相等。直接比原始 JSON 会匹配不上,
|
||||
而且是**静默失效**——不报错,只是查不到,最后表现为"明明匹配过却说待匹配"。
|
||||
|
||||
`[必须]` 所以要有一个规范化函数,`service.OptionKey()`:
|
||||
|
||||
```go
|
||||
OptionKey(map[string]string{"size": "M", "color": "黑色"})
|
||||
// -> {"color":"黑色","size":"M"}
|
||||
```
|
||||
|
||||
实现直接用 `json.Marshal` —— Go 序列化 map 时**会按键名排序**,正好就是我们要的
|
||||
规范化,不用自己拼字符串(自己拼容易漏掉值里含分隔符、含引号之类的边界情况)。
|
||||
|
||||
`[必须]` **这个函数只能有一处实现。** 存映射用它算 key,查规格也用它算 key,
|
||||
两边必须逐字节一致。如果 Client 那边也算一份、或者别处再写一个"差不多"的版本,
|
||||
只要有一点点不同(空格、转义、键序),映射就会静默对不上。
|
||||
**Client 只上报 `options` 对象,key 一律由 Admin 这一个函数算。**
|
||||
|
||||
## 7. `tasks` 任务
|
||||
|
||||
采集任务和采购任务共用一张表,用 `task_type` 区分。
|
||||
|
||||
@@ -337,7 +492,7 @@ CREATE INDEX idx_tasks_order ON tasks(order_no);
|
||||
|
||||
状态含义见 [01 需求](01-requirements.md) §6.2。
|
||||
|
||||
## 7. `task_claims` 领取历史
|
||||
## 8. `task_claims` 领取历史
|
||||
|
||||
记录"哪台客户端领过哪个任务"。
|
||||
|
||||
@@ -363,7 +518,7 @@ CREATE INDEX idx_task_claims_client ON task_claims(client_id);
|
||||
`[必须]` 领取成功时写入;提交结果时用它做权限判断。
|
||||
同一客户端重复领同一任务只更新时间,不报错。
|
||||
|
||||
## 8. `clients` 客户端
|
||||
## 9. `clients` 客户端
|
||||
|
||||
```sql
|
||||
CREATE TABLE clients (
|
||||
@@ -385,22 +540,27 @@ CREATE TABLE clients (
|
||||
- `[必须]` 设置页使用独立登记接口幂等新增或更新 Client,claim 保留隐式登记作为兼容兜底。
|
||||
- `[必须]` 不设心跳接口;登记、claim、result 和 failure 都刷新 `last_seen_at`,见 [04](04-client-api.md) §1.1、§3。
|
||||
|
||||
## 9. 数据关系总览
|
||||
## 10. 数据关系总览
|
||||
|
||||
```text
|
||||
shopee_products ──1:N──→ shopee_skus
|
||||
│ │
|
||||
│ pdd_data │ 1:1
|
||||
│ (采集结果) ↓
|
||||
│ sku_mappings
|
||||
│ ↑
|
||||
│ │ 查映射
|
||||
syb_orders ────────────────────┘
|
||||
│
|
||||
└──创建──→ tasks ──分配──→ clients
|
||||
│ pdd_goods_id │ shopee_sku_id
|
||||
│ (当前对应哪个 ↓
|
||||
│ PDD 商品,可换) sku_mappings ──pdd_goods_id──┐
|
||||
↓ │
|
||||
pdd_products ←────────────────────────────────────────┘
|
||||
(skus_json 里是所有规格和价格)
|
||||
|
||||
syb_orders ──创建──→ tasks ──分配──→ clients
|
||||
```
|
||||
|
||||
## 10. 与 Client 数据模型的关系
|
||||
两条关联都可以变,这是有意的:
|
||||
|
||||
- `shopee_products.pdd_goods_id`:PDD 商品下架换代时改
|
||||
- `sku_mappings` 按 `(蝦皮SKU, PDD商品)` 存:换了商品自然查不到旧映射
|
||||
|
||||
## 11. 与 Client 数据模型的关系
|
||||
|
||||
Admin 和 Client **各有一个 SQLite,互不相通**,只通过接口交换数据。
|
||||
|
||||
@@ -408,7 +568,7 @@ Admin 和 Client **各有一个 SQLite,互不相通**,只通过接口交换
|
||||
|---|---|---|
|
||||
| 任务编号 | `tasks.task_id` | `pdd_tasks.remote_task_id` |
|
||||
| 任务状态 | 7 个(§6) | 8 个,是本机执行状态 |
|
||||
| 采集结果 | `shopee_products.pdd_data` | `pdd_tasks.pdd_data` |
|
||||
| 采集结果 | `pdd_products.skus_json` | `pdd_tasks.pdd_data` |
|
||||
|
||||
`[必须]` **两边的状态是两套,不要试图同步。**
|
||||
Admin 只知道"发出去了 / 收到结果了",中间过程看不到,这是有意的设计。
|
||||
|
||||
@@ -161,7 +161,9 @@ Idempotency-Key: <task_id>:<attempt_id>:result-v1
|
||||
|
||||
1. 用 `Idempotency-Key` 查是否处理过 → 处理过就返回上次的结果,**不重复落库**;
|
||||
2. 把 `pdd_data` 写进 `tasks.result_data`;
|
||||
3. 采集任务:同时写进 `shopee_products.pdd_data`,`collect_status` 置 `collected`;
|
||||
3. 采集任务:校验返回的 `goods_id` 与请求一致(不一致返回 `422 COLLECT_GOODS_MISMATCH`),
|
||||
再写进 `pdd_products.skus_json`,`collect_status` 置 `collected`;
|
||||
**`skus` 为空要置 `failed` 而不是 `collected`**,见 [03 数据模型](03-data-model.md) §4.3;
|
||||
4. `tasks.status` 置 `succeeded`;
|
||||
5. 刷新客户端 `last_seen_at`;
|
||||
6. 返回 `{"accepted": true, "result_id": "...", "accepted_at": "..."}`。
|
||||
@@ -201,8 +203,8 @@ Idempotency-Key: <task_id>:<attempt_id>:failure-v1
|
||||
| `failed` | `failed` | |
|
||||
| `cancelled` | `cancelled` | |
|
||||
|
||||
采集任务失败时,同步把 `shopee_products.collect_status` 置 `failed`,
|
||||
并把错误信息写进 `collect_error`,界面上要看得见。
|
||||
采集任务失败时,同步把 `pdd_products.collect_status` 置 `failed`,
|
||||
错误信息写进 `collect_msg`、诊断产物位置写进 `artifact_ref`,界面上要看得见。
|
||||
|
||||
`[必须]` §4.1 的无条件接受**同样适用于本接口**。
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
| 商品名称 | 截断 + 悬停完整 |
|
||||
| 颜色 / 尺码 / 建议 | 解析失败的显示为 `—` 并**整行标黄**,提示需人工补 |
|
||||
| PDD 链接 | 空的显示"**未填写**"并标红,这是最需要操作员注意的状态 |
|
||||
| 采集状态 | 未填链接 / 未采集 / 采集中 / 已采集 / 采集失败 |
|
||||
| 采集状态 | 未填链接 / 未采集 / 采集中 / 已采集 / 采集失败。**数据来自两张表**,判断方式见 [01 需求](01-requirements.md) §6.1 |
|
||||
| 更新时间 | 本地时区 |
|
||||
|
||||
`[必须]` 状态不能只靠颜色区分,必须有文字。
|
||||
|
||||
Reference in New Issue
Block a user