feat: 增加真实采购任务安全模式 (#98)

This commit is contained in:
chengma
2026-08-10 15:11:38 +08:00
parent ba6710a2a2
commit 3aaa40cfe0
23 changed files with 642 additions and 66 deletions
+7
View File
@@ -344,6 +344,13 @@ PDD 商品之所以单独一个模块,是因为它在数据上就是**独立
7. 所选客户端存在且在当前账号可见范围内;
8. 同一顺运宝明细没有 `pending` / `assigned` / `claimed` 的采购任务。
采购任务创建时必须选择不可变的执行模式:
- `dry_run`(采购演练)是默认值,历史任务也按此处理;
- `live` 会创建真实未付款订单,管理员和其他正常状态用户均可创建,但必须勾选风险确认并输入“创建未付款订单”;
- `live` 必须显式指定当前账号可见、且最后登记能力为 `purchase_mode=live` 的 Client;
- 创建时记录确认用户和确认时间。取消、重派和结果回传均不得修改执行模式。
第 5 条是硬要求:Client 契约规定采购任务必须带明确的价格保护
(见 [Client 契约](../client/04-admin-api-contract.md) §4),没有它 Client 会拒绝执行。
+15
View File
@@ -134,6 +134,10 @@ MySQL 迁移 v3(工单 #88)是追加式迁移:新增 `spec_mappings`,为
再转换能找到顺运宝商品与规格的映射。复跑、字段已建但约束未建等中断状态必须自动收敛;
启动自检除表名外还校验 v3 主键、长度、二进制排序规则和 `source` 取值约束。
MySQL 迁移 v5(工单 #98)为 `tasks` 追加 `execution_mode`、`live_confirmed_by`
和 `live_confirmed_at`,并增加模式/审计 CHECK 与领取索引。旧任务通过数据库默认值
统一成为 `dry_run`;每条 DDL 可重放,列、约束和索引全部通过自检后才记录 v5。
## 3. 蝦皮数据
蝦皮报表**一个文件里混了两层数据**,所以拆成两张表。
@@ -677,6 +681,17 @@ CREATE INDEX idx_tasks_list ON tasks(updated_at DESC, task_id DESC);
CREATE INDEX idx_tasks_order ON tasks(order_no);
```
生产 MySQL v5 在 `tasks` 追加以下安全字段(历史 SQLite 结构不改):
| 字段 | 含义 |
|---|---|
| `execution_mode` | `dry_run` / `live`,非空且默认 `dry_run`;创建后不可修改 |
| `live_confirmed_by` | 创建真实任务的正常状态 Admin 用户;演练任务必须为空 |
| `live_confirmed_at` | 真实任务阻断确认时间;演练任务必须为空 |
`chk_tasks_execution_mode` 和 `chk_tasks_live_confirmation` 在数据库层阻止非法模式及
缺少审计信息的真实任务;`idx_tasks_claim_mode` 支持按客户端能力领取。
`[必须]` 两条硬约束,来自 [Client 契约](../client/04-admin-api-contract.md) §4:
1. **`pdd_goods_url` 不能为空**,否则 Client 无法执行(它那边是 `NOT NULL`)。
+5
View File
@@ -92,6 +92,7 @@ UPDATE tasks
"task": {
"id": "PDD-20260806-0001",
"type": "purchase",
"execution_mode": "dry_run",
"version": 1,
"priority": 10,
"payload": {
@@ -109,6 +110,10 @@ UPDATE tasks
`[必须]` **响应里不含租约**,也不含 Admin 侧状态。Client 不关心这些。
`execution_mode` 只允许 `dry_run` / `live`,历史任务默认 `dry_run`。领取查询必须按
Client 上报的 `capabilities.purchase_mode` 过滤:`dry_run` Client 只能看到演练任务,
`live` Client 可以看到两种;过滤只决定能否领取,绝不改变任务自身模式。
`[必须]` `payload.goods_url` 必须有值;采购任务的 `quantity` 和 `max_price_cent` 必须有值。
这些在建任务时就该校验住(见 [01 需求](01-requirements.md) §5),
不要等到这里才发现发不出去。
+5 -4
View File
@@ -569,12 +569,12 @@ placeholder 写「任务编号 / 订单号 / 商品 ID」,**不要写全「PDD
### 7.2 表格列
☐ / 任务编号 / 类型 / **目标** / 状态 / 客户端 / 更新时间
☐ / 任务编号 / 类型 / **执行模式** / **目标** / 状态 / 客户端 / 更新时间
```text
☐ │ 任务编号 │ 类型 │ 目标 │ 状态 │ 客户端 │ 更新时间
☐ │ PDD-20260807-01 │ 采集 │ PDD 737116531267 │ 已领取 │ 办公室-01 │ 15:20
☐ │ PDD-20260807-02 │ 采购 │ SO-001 · 黑色/M · 2件 · ≤¥42.00 │ 待领取 │ — │ 15:22
☐ │ 任务编号 │ 类型 │ 执行模式 │ 目标 │ 状态 │ 客户端 │ 更新时间
☐ │ PDD-20260807-01 │ 采集 │ 采集 │ PDD 737116531267 │ 已领取 │ 办公室-01 │ 15:20
☐ │ PDD-20260807-02 │ 采购 │ 真实下单(不支付)│ SO-001 · 黑色/M · 2件 · ≤¥42.00 │ 待领取 │ 办公室-02 │ 15:22
```
- `[必须]` **目标列**:采集显示 `PDD <pdd_goods_id>`(能 join 到未删除商品
@@ -596,6 +596,7 @@ placeholder 写「任务编号 / 订单号 / 商品 ID」,**不要写全「PDD
│ 任务 PDD-20260807-01 │
├──────────────────────────────────────────────┤
│ 类型 采集 │
│ 执行模式 采集 │
│ 状态 已领取 │
│ 分配客户端 办公室-01 │
│ 领取时间 2026-08-07 15:20:31 │