feat: claim 支持领取无主任务 (#17)

PDD 商品页要能「创建采集任务」而不指定客户端——采集只是浏览商品页,
没有副作用,哪台设备采都一样。但原来的查询是

    WHERE assigned_client = ? AND status = 'assigned'

无主任务(assigned_client 为空 + pending)永远没人能领,建出来就是死的。

改动
- ClaimNextTask 同时查两种:指定给本机的 + 无主的
- 排序 ORDER BY (assigned_client IS NULL), priority DESC, created_at
  ——指定给本机的优先。显式分配是人为决定,应当先兑现
- 原子更新两种情况合成一条语句:对"指定给我的"写 assigned_client
  是写同一个值无副作用;对无主的,这一步就是"谁领到就标记谁"
- 表结构不用动(assigned_client 本来可空,status 已有 pending)

推翻了一条已定案的规则
Client 契约 §5.1 原写「Admin 只把任务分配给指定的 Client」,
现改为两种并存并说明各自适用场景:
- 采集任务不指定客户端
- 采购任务可指定可留空。涉及钱和账号——不同设备可能登着不同的
  拼多多账号,需要指定账号时必须显式分配,留空即接受"谁先抢到谁下单"

Client 侧对两种没有区别,不需要知道任务原来有没有主。

已验证(Go 1.23.0)
- 新增 7 个测试,全量 62 个全过
- 并发抢占用例重复 20 次稳定:8 个客户端抢同一条无主任务,
  正好 1 个拿到,且 assigned_client 记的就是那个赢家
- 既有测试未受影响,"只领分配给自己的"仍然成立

一处仍未解决的风险(已记入 #17 风险表)
tasks 表没有字段标记"该任务需要真实下单",所以契约里
"不向 dry_run 客户端分配真实下单任务"实际无法执行。
真实下单开关关闭时不出问题,开启前必须补该字段。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
chengma
2026-08-07 10:53:32 +08:00
co-authored by Claude Opus 5
parent ab988b39de
commit dd387e3ee5
4 changed files with 264 additions and 14 deletions
+22 -2
View File
@@ -216,7 +216,26 @@ POST /api/v1/client/tasks/claim
### 5.1 Admin 侧的分配语义
已确认:**Admin 只把任务分配给指定的 Client**,`claim` 只会返回分配给本 `X-Client-Id` 的任务。
任务分两种,`claim` **两种都会返回**:
| 类型 | `assigned_client` | Admin 侧状态 | 谁能领 |
|---|---|---|---|
| **指定分配** | 某个 Client 编号 | `assigned` | 只有那个 Client |
| **无主** | 空 | `pending` | **谁先抢到算谁的**,领取时才记下领取者 |
`[必须]` **指定给本机的优先于无主的。** 显式分配是人为决定,应当先兑现;
无主任务谁抢都一样,可以等。
哪种任务用哪种方式:
- **采集任务不指定客户端。** 采集只是浏览商品页,没有副作用,哪台设备采都一样,
没必要每次都挑一台。
- **采购任务可以指定,也允许留空。** 涉及钱和账号——不同设备可能登着不同的
拼多多账号,买到谁头上是有区别的。**需要指定账号时必须显式分配**,
留空就意味着接受"谁先抢到谁去下单"。
Client 侧对这两种没有任何区别:调 `claim`,拿到任务就做,做完提交。
**不需要知道这个任务原来有没有主。**
因此 Client 完全不需要看到全局任务池,本地也不缓存未领取的任务
(见 [03 数据模型](03-data-model.md) §3.1)。操作人员想知道队列里还有多少活,
@@ -353,7 +372,8 @@ Admin 还没做完,下面这些要和 Admin 一起定。**不许因此停工**
**已定案(不再是待确认项):**
- Admin 只把任务分配给指定 Client,Client 不读全局任务池。见 §5.1。
- 任务分「指定分配」和「无主」两种,`claim` 两种都返回,指定的优先。
采集任务不指定,采购任务可指定可留空。Client 不读全局任务池。见 §5.1。
- **没有租约、没有心跳、没有状态回查。** 任务流程只有 §5 / §6 / §7 三个调用;设置页另有 §4.1 的幂等登记调用。
- Admin 必须无条件接受已派发过的 Client 提交的结果。见 §6.1。