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:
@@ -58,8 +58,12 @@ X-Client-Id: client-001
|
||||
**处理步骤:**
|
||||
|
||||
1. 先做客户端注册/更新(§3);
|
||||
2. 查 `tasks`:`assigned_client = X-Client-Id` 且 `status = 'assigned'`,
|
||||
按 `priority DESC, created_at` 取**一条**;
|
||||
2. 查 `tasks`,**两种任务都要查**:
|
||||
- 指定给本机的:`assigned_client = X-Client-Id` 且 `status = 'assigned'`
|
||||
- 无主的:`assigned_client IS NULL` 且 `status = 'pending'`
|
||||
|
||||
排序 `ORDER BY (assigned_client IS NULL), priority DESC, created_at`
|
||||
—— **指定给本机的优先**;
|
||||
3. 没有就返回 `204 No Content`;
|
||||
4. 有就把 `status` 改成 `claimed`、记 `claimed_at`,返回任务。
|
||||
|
||||
@@ -69,8 +73,12 @@ X-Client-Id: client-001
|
||||
`[必须]` 第 4 步的查询和更新要在**同一个事务**里,用条件更新防并发:
|
||||
|
||||
```sql
|
||||
UPDATE tasks SET status = 'claimed', claimed_at = ?, updated_at = ?
|
||||
WHERE task_id = ? AND status = 'assigned';
|
||||
UPDATE tasks
|
||||
SET status = 'claimed', assigned_client = ?, claimed_at = ?, updated_at = ?
|
||||
WHERE task_id = ?
|
||||
AND ( (status = 'assigned' AND assigned_client = ?)
|
||||
OR (status = 'pending' AND assigned_client IS NULL) );
|
||||
-- 两种情况合成一条:对"指定给我的",写 assigned_client 是写同一个值,无副作用。
|
||||
-- 检查影响行数,为 0 说明被别人抢先了,重新取下一条
|
||||
```
|
||||
|
||||
@@ -268,7 +276,10 @@ Client 契约里散落的 Admin 侧硬要求,汇总在这里,**可以直接
|
||||
- [x] claim 隐式登记**不更新名称**
|
||||
- [x] 登记和 claim **共用同一套字段校验**,非法内容返回 `422 INVALID_CLIENT_PROFILE`
|
||||
- [x] 校验不通过时不写库
|
||||
- [ ] `claim` 一次只返回一个任务,且只返回分配给该客户端的
|
||||
- [ ] `claim` 一次只返回一个任务
|
||||
- [ ] 能领到无主任务(`assigned_client IS NULL` + `pending`),领取时写入领取者
|
||||
- [ ] 指定给本机的任务优先于无主任务
|
||||
- [ ] 不会返回指定给别的客户端的任务
|
||||
- [ ] `claim` 原子完成,并发下不会把同一任务发给两个客户端
|
||||
- [ ] `claim` 无任务时返回 `204`,不是 `200` 加空对象
|
||||
- [ ] 响应不含租约、不含 Admin 侧状态
|
||||
|
||||
Reference in New Issue
Block a user