For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 为闪送订单增加创建时收件账号绑定和“我收的”查询,并用骑手级 Redisson 分布式锁保证 URGENT 跨外卖、闪送独占。
Architecture: flash_delivery_order.receiver_user_id 保存创建时唯一匹配的普通用户,不动态追溯;用户列表通过 role 在数据库分页前切换寄件人/收件人条件。RiderDeliveryLockService 统一串行化同一骑手的接单请求,RiderDeliveryExclusivityService 统一查询两类进行中订单;闪送和外卖接单都在同一锁键内完成检查与原子更新。
Tech Stack: Java 21、Spring Boot、Spring Transaction、MyBatis/MyBatis-Plus、MySQL、Redisson、JUnit 5、Mockito。
WAITING_ACCEPTANCE。updatesql/sql.md,不得直接执行。@RequestHeader String token,查询参数显式使用 @RequestParam。ruoyi-admin -> ruoyi-system 依赖方向,跨订单集成服务放在 ruoyi-admin。C:\Users\qmj\.jdks\graalvm-jdk-21.0.7。Files:
ruoyi-system/src/main/java/com/ruoyi/system/domain/flash/FlashDeliveryOrder.javaruoyi-system/src/main/java/com/ruoyi/system/mapper/InfoUserMapper.javaruoyi-system/src/main/java/com/ruoyi/system/mapper/flash/FlashDeliveryOrderMapper.javaruoyi-system/src/main/resources/mapper/infouser/InfoUserMapper.xmlruoyi-system/src/main/resources/mapper/flash/FlashDeliveryOrderMapper.xmlruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationService.javaruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryUserController.javaruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationServiceTest.javaruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryControllerContractTest.javaruoyi-system/src/test/java/com/ruoyi/system/mapper/flash/FlashDeliveryMapperXmlTest.javaInterfaces:
delivery.phone from FlashDeliveryCreateRequest and token-derived current user ID.Produces: FlashDeliveryOrder.receiverUserId; InfoUserMapper.selectOrdinaryUserIdsByNormalizedPhone(String); FlashDeliveryOrderMapper.transitionByParticipant(...); GET /system/flashDelivery/orders?page=1&size=10&role=receiver; participant-aware detail and confirmation.
[x] Step 1: Write receiver-binding and participant-access tests
Add service tests whose observable assertions are:
when(userMapper.selectOrdinaryUserIdsByNormalizedPhone("886912345678"))
.thenReturn(List.of(88L));
fixture.service.create(7L, requestWithDeliveryPhone("+886 912-345-678"));
assertEquals(88L, insertedOrder.getReceiverUserId());
when(userMapper.selectOrdinaryUserIdsByNormalizedPhone(anyString()))
.thenReturn(List.of(88L, 99L));
fixture.service.create(7L, requestWithDeliveryPhone("+886 912-345-678"));
assertNull(insertedOrder.getReceiverUserId());
fixture.service.userOrders(88L, 1, 10, "receiver");
assertTrue(capturedQuery.getSqlSegment().contains("receiver_user_id"));
assertDoesNotThrow(() -> fixture.service.userDetail(88L, orderId));
assertThrows(ServiceException.class,
() -> fixture.service.userCancel(88L, orderId, reasonRequest()));
Add Controller reflection tests requiring optional @RequestParam String role. Extend the mapper XML test to assert receiverUserId mapping and participant transition SQL.
[x] Step 2: Run tests and verify RED
$env:JAVA_HOME='C:\Users\qmj\.jdks\graalvm-jdk-21.0.7'
$env:PATH="$env:JAVA_HOME\bin;$env:PATH"
mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryApplicationServiceTest,FlashDeliveryControllerContractTest,FlashDeliveryMapperXmlTest' -Dsurefire.failIfNoSpecifiedTests=false test
Expected: compilation or assertions fail because receiverUserId, role-aware list, participant transition and phone lookup do not exist.
Add:
private Long receiverUserId;
Add InfoUserMapper lookup returning at most two IDs so ambiguous matches are detectable:
List<Long> selectOrdinaryUserIdsByNormalizedPhone(@Param("normalizedPhone") String normalizedPhone);
The XML query must require user_type='0', active/non-deleted account, normalize stored phone by removing whitespace, +, -, ( and ), and use LIMIT 2. In create, resolve once before insert and set only when the returned list contains exactly one ID. Idempotent retries return the original order without resolving again.
Change the list signature to:
IPage<FlashDeliveryUserOrderListView> userOrders(
Long userId, int pageNum, int pageSize, String role)
Accept only sender and receiver, default null/blank to sender, and apply user_id or receiver_user_id before selectPage. Detail allows either participant; cancellation remains creator-only. Replace transitionByUser with transitionByParticipant and participant-aware SQL:
WHERE id = #{id}
AND (user_id = #{userId} OR receiver_user_id = #{userId})
AND status = #{expectedStatus}
Run the Step 2 command. Expected: all specified tests pass with zero failures and errors.
Files:
ruoyi-admin/src/main/java/com/ruoyi/app/order/RiderDeliveryLockService.javaruoyi-admin/src/main/java/com/ruoyi/app/order/RiderDeliveryExclusivityService.javaruoyi-admin/src/test/java/com/ruoyi/app/order/RiderDeliveryLockServiceTest.javaruoyi-admin/src/test/java/com/ruoyi/app/order/RiderDeliveryExclusivityServiceTest.javaruoyi-admin/src/main/resources/i18n/messages.propertiesruoyi-admin/src/main/resources/i18n/messages_zh_CN.propertiesruoyi-admin/src/main/resources/i18n/messages_zh_TW.propertiesruoyi-admin/src/main/resources/i18n/messages_en_US.propertiesruoyi-admin/src/main/resources/i18n/messages_vi.propertiesInterfaces:
RedissonClient, IPosOrderService, FlashDeliveryOrderMapper, rider ID and target service type.Produces: RiderDeliveryLockService.withLock(Long, LockedCall<T>); RiderDeliveryExclusivityService.assertCanAcceptFood(Long); assertCanAcceptFlash(Long,String).
[x] Step 1: Write failing lock lifecycle tests
Test the real lock wrapper behavior around a mocked external Redisson boundary:
when(redissonClient.getLock("lock:delivery:rider:123")).thenReturn(lock);
when(lock.tryLock(3L, TimeUnit.SECONDS)).thenReturn(true);
String result = service.withLock(123L, () -> "accepted");
assertEquals("accepted", result);
Verify through behavior that acquisition timeout, interruption and runtime Redis failure throw ServiceException; outside a transaction the lock releases in finally; with active transaction synchronization it remains held until afterCompletion.
Cover these literal outcomes:
URGENT + active food -> reject
URGENT + active normal flash -> reject
URGENT + no active work -> allow
normal flash + active URGENT -> reject
food + active URGENT -> reject
food + active normal flash -> allow
Active flash statuses are exactly ACCEPTED and PICKED_UP; active food uses qs_id=riderId, delivery_status in (1,2), state in (0,1,2) and after_sale_status=0.
[x] Step 3: Run tests and verify RED
mvn -pl ruoyi-admin -am -Dtest='RiderDeliveryLockServiceTest,RiderDeliveryExclusivityServiceTest' -Dsurefire.failIfNoSpecifiedTests=false test
Expected: compilation fails because both services are absent.
RiderDeliveryLockService uses:
RLock lock = redissonClient.getLock("lock:delivery:rider:" + riderId);
boolean acquired = lock.tryLock(3L, TimeUnit.SECONDS);
Do not pass a lease time so Redisson watchdog renews the lock. Register TransactionSynchronization.afterCompletion and unlock only when lock.isHeldByCurrentThread(); if no synchronization is active, release in finally. Translate timeout, interruption and Redis failure to the existing system-busy/operation-interrupted i18n errors.
RiderDeliveryExclusivityService uses real MyBatis-Plus count queries and throws the new flash.delivery.rider.exclusive.conflict message when rules reject acceptance.
Run the Step 3 command. Expected: all specified tests pass with zero failures and errors.
Files:
ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationService.javaruoyi-admin/src/main/java/com/ruoyi/app/order/OrderLifecycleService.javaruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationServiceTest.javaruoyi-admin/src/test/java/com/ruoyi/app/order/OrderLifecycleServiceTest.javaupdatesql/sql.mddocs/flash-delivery-app-api.mdspecs/024-flash-delivery/quickstart.mdspecs/024-flash-delivery/tasks.mdInterfaces:
Produces: both effective acceptance paths execute eligibility checks and order update under lock:delivery:rider:{riderId}; documented receiver role API and SQL migration.
[x] Step 1: Write failing integration-level service tests
For flash acceptance, assert the returned order is accepted only when withLock invokes the callback and exclusivity allows it; assert exclusive conflict leaves orderMapper.accept uncalled. For food acceptance, assert OrderLifecycleService.acceptDeliveryByRider enters the same rider lock and checks assertCanAcceptFood before applying CAS.
[x] Step 2: Run tests and verify RED
mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryApplicationServiceTest,OrderLifecycleServiceTest,RiderDeliveryLockServiceTest,RiderDeliveryExclusivityServiceTest' -Dsurefire.failIfNoSpecifiedTests=false test
Expected: assertions fail because acceptance methods do not invoke the shared lock and exclusivity services.
Wrap FlashDeliveryApplicationService.accept as:
return riderDeliveryLockService.withLock(riderId, () -> {
FlashDeliveryOrder order = requireOrder(orderId);
exclusivityService.assertCanAcceptFlash(riderId, order.getServiceType());
// existing conditional accept, log and response
});
Wrap the full body of OrderLifecycleService.acceptDeliveryByRider with the same lock and call assertCanAcceptFood(riderId) before applyCas. Keep the existing per-order conditional update; do not lock pickup or delivery operations.
Append, but do not execute:
ALTER TABLE flash_delivery_order
ADD COLUMN receiver_user_id BIGINT NULL COMMENT '创建时匹配的收件用户ID' AFTER user_id,
ADD KEY idx_flash_receiver_list (receiver_user_id, create_time);
Document role=sender/receiver, default sender, participant detail/signature rules, no historical claim, integer TWD, no payment and the UI copy “发布后等待附近骑手接单”. Mark T055–T060 complete only after their corresponding source and docs exist.
[x] Step 5: Run focused tests and full module verification
$env:JAVA_HOME='C:\Users\qmj\.jdks\graalvm-jdk-21.0.7'
$env:PATH="$env:JAVA_HOME\bin;$env:PATH"
mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryApplicationServiceTest,FlashDeliveryControllerContractTest,FlashDeliveryMapperXmlTest,OrderLifecycleServiceTest,RiderDeliveryLockServiceTest,RiderDeliveryExclusivityServiceTest,FlashDeliveryI18nContractTest' -Dsurefire.failIfNoSpecifiedTests=false test
mvn -pl ruoyi-admin -am -DskipTests package
git diff --check
Expected: tests report zero failures/errors, package exits 0, and diff check produces no errors.
Inspect git diff --name-only and git diff --cached --name-only; stage only Phase 12 production, tests, five i18n bundles, SQL and flash-delivery documents. Commit with a Chinese subject. Do not push unless the user requests it.