feat: 任务改用采集采购独立序号 (#172)

This commit is contained in:
chengma
2026-08-12 09:21:21 +08:00
parent eca3a77084
commit 9ad067503e
15 changed files with 523 additions and 44 deletions
+4
View File
@@ -253,6 +253,10 @@ PDD 商品之所以单独一个模块,是因为它在数据上就是**独立
操作员却只能盯着 PDD 商品的 `collect_status` 猜任务本身怎么样了
(被谁领了、领了多久、失败在哪一步),完全看不出任务这一层的信息。
`task_id` 同时是页面业务单号和给 Client 的稳定主键:采集任务按
`cj1`、`cj2` ……递增,采购任务按 `cg1`、`cg2` ……独立递增。任务类型
仍以 `task_type` 为准,不允许仅根据编号前缀推导业务逻辑。
`[必须]` 名字是「**采集采购**」,不是「任务」。它直接点出这一页装的是
哪两类任务,比泛称「任务」更能让操作员一眼知道点进去看什么。
+29 -5
View File
@@ -110,7 +110,9 @@ SQLite 同一时刻只允许一个写事务,连接放太开会互相抢锁、
生产 MySQL 迁移继续独立追加;v12(#165)新增 `task_syb_sources`,并把没有任何
有效采集任务的孤立 `pdd_products.collect_status='collecting'` 回收到 `pending`。
历史 SQLite migrations 保持冻结,不追加该表。
v13(#172)新增 `task_sequences`,把存量 `tasks.task_id` 按类型和创建时间确定性改为
`cjN` / `cgN`,同步更新领取历史和顺运宝来源外键。历史 SQLite migrations
保持冻结,不追加 v12/v13 生产表结构。
**v3 为什么丢弃旧 `sku_mappings` 数据(见 #20):** 新主键需要 `pdd_option_key`,
这是 Go 的 `service.OptionKey()` 用 `json.Marshal` 算出来的规范化键,SQL 语句
@@ -631,7 +633,7 @@ FROM spec_mapping_decisions WHERE suggested_option_key IS NULL GROUP BY rules_ve
```sql
CREATE TABLE tasks (
task_id TEXT PRIMARY KEY, -- 给 Client 的稳定编号,如 PDD-20260806-0001
task_id TEXT PRIMARY KEY, -- 稳定业务主键:采集 cjN,采购 cgN
task_type TEXT NOT NULL CHECK (task_type IN ('collect', 'purchase')),
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'assigned', 'claimed',
@@ -671,6 +673,10 @@ CREATE INDEX idx_tasks_list ON tasks(updated_at DESC, task_id DESC);
CREATE INDEX idx_tasks_order ON tasks(order_no);
```
`task_id` 不是另外的展示别名,而是真实主键。采集和采购分别从 1
开始递增,不共用计数器。分配编号与插入任务必须在同一事务中,以避免
并发重号和创建失败消耗序号。Client 应把这个值当作不透明字符串原样保存。
生产 MySQL v5 在 `tasks` 追加以下安全字段(历史 SQLite 结构不改):
| 字段 | 含义 |
@@ -703,7 +709,21 @@ CREATE INDEX idx_tasks_order ON tasks(order_no);
状态含义见 [01 需求](01-requirements.md) §6.2。
### 7.1 `task_syb_sources` 采集任务来源(生产 MySQL v12)
### 7.1 `task_sequences` 任务序列(生产 MySQL v13)
```sql
CREATE TABLE task_sequences (
task_type VARCHAR(20) COLLATE utf8mb4_bin PRIMARY KEY,
current_value BIGINT NOT NULL DEFAULT 0,
CHECK (task_type IN ('collect', 'purchase')),
CHECK (current_value >= 0)
);
```
表中只有 `collect` 和 `purchase` 两行。创建任务时先在当前事务内对对应行
`current_value + 1`,再读取并组成 `cjN` 或 `cgN`;InnoDB 行锁保证并发唯一。
### 7.2 `task_syb_sources` 采集任务来源(生产 MySQL v12)
从顺运宝创建采集任务时,同一个 PDD 商品可能对应多个货运单明细。来源使用关联表,
不能压进 `tasks.syb_id` 单列;后者仍只表示采购任务自身的顺运宝明细。
@@ -715,7 +735,7 @@ CREATE TABLE task_syb_sources (
created_at VARCHAR(35) NOT NULL,
PRIMARY KEY (task_id, syb_id),
KEY idx_task_syb_sources_syb (syb_id, task_id),
FOREIGN KEY (task_id) REFERENCES tasks(task_id) ON DELETE CASCADE,
FOREIGN KEY (task_id) REFERENCES tasks(task_id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (syb_id) REFERENCES syb_orders(syb_id) ON DELETE CASCADE
);
```
@@ -735,12 +755,16 @@ CREATE TABLE task_claims (
task_id TEXT NOT NULL,
client_id TEXT NOT NULL,
claimed_at TEXT NOT NULL,
PRIMARY KEY (task_id, client_id)
PRIMARY KEY (task_id, client_id),
FOREIGN KEY (task_id) REFERENCES tasks(task_id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE INDEX idx_task_claims_client ON task_claims(client_id);
```
生产 MySQL v13 清理了无对应任务的孤儿领取历史,并加入上述外键;
`ON UPDATE CASCADE` 保证主键迁移时领取历史同步改号。
**为什么需要这张表:** [04 Client 接口实现](04-client-api.md) §4.1 要求
"只有**从未分配给该客户端**的任务才返回 403"。
但 `tasks.assigned_client` 只记**当前**归属,任务一旦重派给别人,
+5 -1
View File
@@ -17,6 +17,10 @@ POST /api/v1/client/tasks/{task_id}/result 提交成功结果
POST /api/v1/client/tasks/{task_id}/failure 提交失败/需人工
```
`task_id` 是不透明的稳定字符串,当前采集任务为 `cjN`、采购任务为
`cgN`。Client 必须原样保存和回传,不校验旧前缀,不从编号推导类型;
业务类型始终以响应中的 `type` 为准。
`[必须]` **不得新增"让 Client 查询状态"类接口**,也不得加回租约和心跳。
理由见 Client 契约 §1.1:本项目人工付款,重复下单只产生重复的**未付款**订单,
不值得为它引入一整套中断逻辑。
@@ -90,7 +94,7 @@ UPDATE tasks
```json
{
"task": {
"id": "PDD-20260806-0001",
"id": "cg1",
"type": "purchase",
"execution_mode": "dry_run",
"version": 1,
+7 -3
View File
@@ -662,10 +662,14 @@ placeholder 写「任务编号 / 订单号 / 商品 ID」,**不要写全「PDD
```text
☐ │ 任务编号 │ 类型 │ 执行模式 │ 目标 │ PDD 店铺 │ 状态 │ 客户端 │ 更新时间
☐ │ PDD-20260807-01 │ 采集 │ 采集 │ PDD 737116531267 │ 某某店铺 │ 已领取 │ 办公室-01 │ 15:20
☐ │ PDD-20260807-02 │ 采购 │ 真实下单(不支付)│ SO-001 · 黑色/M · 2件 · ≤¥42.00 │ 某某店铺 │ 待领取 │ 办公室-02 │ 15:22
☐ │ cj123 │ 采集 │ 采集 │ PDD 737116531267 │ 某某店铺 │ 已领取 │ 办公室-01 │ 15:20
☐ │ cg45 │ 采购 │ 真实下单(不支付)│ SO-001 · 黑色/M · 2件 · ≤¥42.00 │ 某某店铺 │ 待领取 │ 办公室-02 │ 15:22
```
- `[必须]` **任务编号列**直接显示真实 `task_id`:采集为 `cjN`,采购为
`cgN`;关键词支持完整或部分编号查询。“类型”列仍显示明确文字,
不能只靠前缀或颜色区分。
- `[必须]` **目标列**:采集显示 `PDD <pdd_goods_id>`(能 join 到未删除商品
的标题时追加显示);采购显示 `<order_no> · <颜色/尺码> · <数量>件 · ≤<价格上限>`。
拼接逻辑在 service 层组装成一个字符串,模板只负责显示。
@@ -685,7 +689,7 @@ placeholder 写「任务编号 / 订单号 / 商品 ID」,**不要写全「PDD
```text
┌──────────────────────────────────────────────┐
│ 任务 PDD-20260807-01 │
│ 任务 cj123 │
├──────────────────────────────────────────────┤
│ 类型 采集 │
│ 执行模式 采集 │