Prechádzať zdrojové kódy

改用Redis实现闪送骑手独占锁

qmj 1 deň pred
rodič
commit
52c16633f4

+ 2 - 2
specs/024-flash-delivery/design.md

@@ -234,6 +234,6 @@ COMPLETED
 
 ### 11.3 急送独占与并发
 
-所有骑手接单入口先锁定同一 `info_user` 骑手行,再查询该骑手的有效外卖和闪送任务。抢急送时要求不存在未送达外卖及 `ACCEPTED/PICKED_UP` 闪送;抢普通外卖或普通闪送时,如果已有 `ACCEPTED/PICKED_UP` 急送则拒绝。急送进入 `DELIVERED`、`COMPLETED` 或 `CANCELLED` 后解除独占。
+所有骑手接单入口先获取同一 Redisson 骑手锁 `lock:delivery:rider:{riderId}`,再查询该骑手的有效外卖和闪送任务。抢急送时要求不存在未送达外卖及 `ACCEPTED/PICKED_UP` 闪送;抢普通外卖或普通闪送时,如果已有 `ACCEPTED/PICKED_UP` 急送则拒绝。急送进入 `DELIVERED`、`COMPLETED` 或 `CANCELLED` 后解除独占。
 
-同一骑手的外卖和闪送接单必须共享这把数据库行锁,确保并发请求不能分别在两张订单表中同时通过“无冲突”检查。
+同一骑手的外卖和闪送接单必须共享这把分布式锁,确保并发请求不能分别在两张订单表中同时通过“无冲突”检查。锁使用 Redisson 看门狗续期,不指定固定租期,并通过事务同步回调在提交或回滚完成后释放;获取超时、线程中断或 Redis 异常均拒绝本次接单。数据库订单条件更新继续负责防止不同骑手抢中同一订单。

+ 3 - 2
specs/024-flash-delivery/plan.md

@@ -536,10 +536,11 @@ The staged list must not contain `.claude/homunculus/observations.jsonl`、`.tmp
 
 ### 急送独占
 
-1. 为骑手用户增加 `SELECT ... FOR UPDATE` 行锁方法,闪送接单和当前有效外卖接单入口在事务内首先获取同一行锁。
+1. 新增共享骑手接单锁服务,使用 Redisson 锁键 `lock:delivery:rider:{riderId}`;闪送接单和当前有效外卖接单入口在事务内首先获取同一把锁。
 2. 抢 `URGENT` 前检查该骑手没有未送达外卖,也没有 `ACCEPTED/PICKED_UP` 闪送。
 3. 抢其他任务前检查该骑手没有 `ACCEPTED/PICKED_UP` 的 `URGENT` 闪送。
-4. 独占冲突使用统一国际化业务错误;条件更新仍作为订单被他人抢走的最终并发保护。
+4. 使用不指定固定租期的 `tryLock(waitTime, unit)` 交由 Redisson 看门狗续期,并通过事务同步回调在事务完成后释放;获取失败或 Redis 异常时拒绝接单。
+5. 独占冲突使用统一国际化业务错误;数据库条件更新仍作为订单被其他骑手抢走的最终并发保护。
 
 ### 测试与交付
 

+ 2 - 2
specs/024-flash-delivery/spec.md

@@ -145,7 +145,7 @@
 3. **假如** 骑手已有未送达的外卖或有效闪送任务,**当** 抢 1 对 1 急送订单,**那么** 系统拒绝接单。
 4. **假如** 骑手已承接 1 对 1 急送且订单仍处于已接单或已取件,**当** 再抢外卖、普通闪送或另一笔急送,**那么** 系统拒绝接单。
 5. **假如** 急送订单已送达、完成或取消,**当** 骑手再次抢单,**那么** 独占限制解除。
-6. **假如** 急送与其他任务并发抢单,**当** 请求针对同一骑手到达,**那么** 所有相关接单入口使用同一骑手锁串行检查,不能绕过独占规则。
+6. **假如** 急送与其他任务并发抢单,**当** 请求针对同一骑手到达,**那么** 所有相关接单入口使用同一骑手级 Redis 分布式锁串行检查,不能绕过独占规则。
 
 ## 边界情况
 
@@ -233,7 +233,7 @@
 - **FR-063**:用户订单列表新增 `role` 参数,只接受 `sender` 和 `receiver`,省略时兼容为 `sender`;筛选必须在数据库分页前完成。
 - **FR-064**:订单创建人和已绑定收件人均可查询订单详情并确认签收;只有订单创建人可以主动取消订单。交付 PIN 对两类参与者可见。
 - **FR-065**:`URGENT` 表示 1 对 1 独占配送。骑手已有未送达外卖、`ACCEPTED` 或 `PICKED_UP` 闪送时不得接急送;骑手存在 `ACCEPTED` 或 `PICKED_UP` 急送时不得再接任何外卖或闪送。
-- **FR-066**:所有有效骑手接单入口必须先锁定同一骑手用户行,再检查跨外卖和闪送订单的独占条件并执行接单,避免并发绕过
+- **FR-066**:所有有效骑手接单入口必须使用同一 Redisson 锁键 `lock:delivery:rider:{riderId}`,在持锁期间检查跨外卖和闪送订单的独占条件并执行接单;锁必须在数据库事务完成后释放,获取超时或 Redis 异常时按失败关闭原则拒绝接单
 - **FR-067**:闪送继续保持非支付业务,创建成功直接进入 `WAITING_ACCEPTANCE`,不增加待支付状态、支付倒计时、支付接口或退款流程。
 - **FR-068**:所有闪送金额继续使用整数 TWD;蓝湖稿中的小数金额仅为视觉占位,不改变接口和数据库金额类型。
 - **FR-069**:用户端不得展示固定或伪造的附近骑手人数及预计接单分钟数,统一使用“发布后等待附近骑手接单”;本期不增加实时骑手数量或接单预测接口。

+ 2 - 2
specs/024-flash-delivery/tasks.md

@@ -100,7 +100,7 @@
 - [ ] T055 更新规格、设计、数据模型和 API 契约,固定非支付、整数 TWD、静态等待接单文案、创建时收件账号匹配及急送独占规则
 - [ ] T056 先扩展 Mapper、应用服务和 Controller 测试,覆盖收件账号唯一匹配、无匹配、重复匹配、历史不追溯、`role` 列表、参与者详情/签收、收件人禁止取消,并确认旧实现红灯
 - [ ] T057 新增 `receiver_user_id` 实体/XML/索引和普通用户手机号精确匹配查询,实现创建时固化、寄件人/收件人数据库分页及参与者权限
-- [ ] T058 先补跨外卖/闪送接单测试,覆盖忙碌骑手拒绝急送、急送阻止其他接单、终态释放和并发串行检查,并确认旧实现红灯
-- [ ] T059 以统一骑手行锁实现 `URGENT` 跨订单独占,在当前有效外卖与闪送接单入口返回一致的国际化业务错误
+- [ ] T058 先补跨外卖/闪送接单测试,覆盖骑手级 Redisson 锁、忙碌骑手拒绝急送、急送阻止其他接单、终态释放和并发串行检查,并确认旧实现红灯
+- [ ] T059 以共享锁键 `lock:delivery:rider:{riderId}` 实现 `URGENT` 跨订单独占,事务完成后释放锁,并在当前有效外卖与闪送接单入口返回一致的国际化业务错误
 - [ ] T060 在 `updatesql/sql.md` 追加 `receiver_user_id` 字段和索引迁移,仅记录不执行;同步 App 接口文档和五语言错误文案
 - [ ] T061 使用 JDK 21 运行闪送、外卖接单和 i18n 定向测试、模块构建与差异检查,核对暂存范围后使用中文提交