--- description: "Task list for feature 028-flash-delivery-push" --- # Tasks: 闪送订单状态变更消息推送 **Input**: Design documents from `/specs/028-flash-delivery-push/` **Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/push.md **Tests**: quickstart.md「自动验证」明确要求单测(`FlashDeliveryNotificationServiceTest` 新建 + `FlashDeliveryApplicationServiceTest` 扩展),故包含测试任务且先行(红→绿)。 **Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. ## Format: `[ID] [P?] [Story] Description` - **[P]**: Can run in parallel (different files, no dependencies) - **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) - Include exact file paths in descriptions ## Path Conventions - 主代码:`ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/` - 测试:`ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/` - 构建命令(Git Bash):`export JAVA_HOME="C:/Users/qmj/.jdks/graalvm-jdk-21.0.7" && export PATH="$JAVA_HOME/bin:$PATH" && mvn -pl ruoyi-admin -am test -Dtest='...'` --- ## Phase 1: Setup **Purpose**: 确认基线全绿,避免把存量问题带进本特性 - [x] T001 运行既有闪送单测确认基线全绿:`mvn -pl ruoyi-admin -am test -Dtest='FlashDeliveryApplicationServiceTest,FlashDeliveryStateMachineTest,FlashDeliveryControllerContractTest'`(JDK21 环境变量见 Path Conventions)——基线 79 测试 1 存量失败(`createRejectsUnsupportedPayType`,源于在途 payType 白名单注释改动,非本特性范围,全程保持不新增失败即可) --- ## Phase 2: Foundational (Blocking Prerequisites) **Purpose**: 通知服务基础设施 + 应用服务装配,所有 user story 依赖 - [x] T002 新建 `ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryNotificationService.java`:`@Service`;构造器注入 `InfoUserMapper`(`com.ruoyi.system.mapper`)与 `PushEventService`(`com.ruoyi.app.utils.event`);私有 `runAfterCommit(Runnable)`(镜像 `DeliveryOrderNotificationService.java:156-167`);包级可见 `sendToUser(FlashDeliveryOrder, String contentKey)` / `sendToRider(FlashDeliveryOrder, String contentKey)`:`runAfterCommit` → `userMapper.selectById` 取 `InfoUser` → `AsyncManager.me().execute(TimerTask)` → `PayPush.userPushHandleLocal` / `qsPushHandleLocal`,title 固定 `no.message.push.message`,body=`OrderPushBodyDto.getJson(order.getOrderNo(), 状态串, -1, 3)`,ddId=`order.getOrderNo()`;推送异常捕获仅记日志不上抛 - [x] T003 `FlashDeliveryApplicationService.java:76-94` 构造器追加第 10 个依赖 `FlashDeliveryNotificationService` 并存字段;全仓更新 `new FlashDeliveryApplicationService(` 调用点(`ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationServiceTest.java` 等,先 grep 定位)补 mock 参数;编译并运行 T001 测试集保持全绿 **Checkpoint**: 通知服务骨架就绪且零行为变化(尚无 notify 入口被调用) --- ## Phase 3: User Story 1 - 寄件用户收到骑手接单通知 (Priority: P1) 🎯 MVP **Goal**: 骑手抢单成功后,寄件用户收到「骑手已接单,NO:{orderNo}」推送与消息中心记录 **Independent Test**: 对一笔 WAITING_ACCEPTANCE 订单执行 `accept` 成功 → 验证 `notifyAccepted` 被调用;抢单失败(条件更新 0 行)→ 不调用 ### Tests for User Story 1(先行,跑红) - [x] T004 [US1] 新建 `ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryNotificationServiceTest.java`:spy 服务 + `doNothing` 打断 `sendToUser`/`sendToRider`(镜像 `DeliveryOrderNotificationServiceTest.java` 手法),断言 `notifyAccepted(order)` 走 `sendToUser(order, "no.message.push.delivery.personnel.receiving.order")`;同时在 `FlashDeliveryApplicationServiceTest.java` 增补:accept 成功路径 verify `notifyAccepted`、`orderMapper.accept` 返回 0 时 never 调用 ### Implementation for User Story 1 - [x] T005 [US1] 在 `FlashDeliveryNotificationService.java` 实现 `notifyAccepted(FlashDeliveryOrder)`(runAfterCommit 后推 userId,状态串 `ACCEPTED`);在 `FlashDeliveryApplicationService.java` `accept`(约 :485 `writeLog` 之后、`return` 之前,`withLock` lambda 内)调用 `notificationService.notifyAccepted(target)` - [x] T006 [US1] 运行 `mvn -pl ruoyi-admin -am test -Dtest='FlashDeliveryNotificationServiceTest,FlashDeliveryApplicationServiceTest'` 确认由红转绿 **Checkpoint**: MVP——抢单通知可独立演示(quickstart.md 手动场景 3) --- ## Phase 4: User Story 2 - 寄件用户收到取件与送达通知 (Priority: P1) **Goal**: 骑手取件、送达成功后,寄件用户分别收到「骑手配送中」「骑手已送达」推送 **Independent Test**: 对 ACCEPTED 订单 `pickup` 成功 → `notifyPickedUp`;对 PICKED_UP 订单 `deliver` 成功 → `notifyDelivered`;任一失败 → 不调用 ### Tests for User Story 2(先行,跑红) - [x] T007 [US2] `FlashDeliveryNotificationServiceTest.java` 增补:`notifyPickedUp` 走 `sendToUser(order, "no.message.push.delivery.personnel.qspsz.order")`、`notifyDelivered` 走 `sendToUser(order, "no.message.push.delivery.personnel.qsysd.order")`;`FlashDeliveryApplicationServiceTest.java` 增补:`transitionWithProof` 成功按 next 分派对应 notify、`orderMapper.transitionByRider` 返回 0 时两者均 never ### Implementation for User Story 2 - [x] T008 [US2] `FlashDeliveryNotificationService.java` 实现 `notifyPickedUp` / `notifyDelivered`(状态串 `PICKED_UP` / `DELIVERED`);`FlashDeliveryApplicationService.java` `transitionWithProof`(约 :781 `writeLog` 之后)按 `next` 参数分派调用(仅 PICKED_UP/DELIVERED 两值,其它忽略) - [x] T009 [US2] 运行 Story 2 测试集确认由红转绿 **Checkpoint**: 履约进度通知完整(quickstart.md 手动场景 4、5) --- ## Phase 5: User Story 3 - 骑手收到订单取消通知 (Priority: P2) **Goal**: 用户/平台取消成功且已有接单骑手时,骑手收到「订单已取消,NO:{orderNo}」推送;未接单取消零推送 **Independent Test**: ACCEPTED 状态 `userCancel`/`adminCancel` 成功 → `notifyCancelled`;WAITING_ACCEPTANCE 取消(riderId 空)→ 跳过;取消失败 → 不调用 ### Tests for User Story 3(先行,跑红) - [x] T010 [US3] `FlashDeliveryNotificationServiceTest.java` 增补:`notifyCancelled(order)` 在 riderId 非空时走 `sendToRider(order, "no.message.push.order.cancelled")`、riderId 为空时零交互;`FlashDeliveryApplicationServiceTest.java` 增补:`userCancel`/`adminCancel` 成功路径 verify `notifyCancelled`、`orderMapper.cancel` 返回 0 时 never ### Implementation for User Story 3 - [x] T011 [US3] `FlashDeliveryNotificationService.java` 实现 `notifyCancelled`(riderId 判空跳过,状态串 `CANCELLED`,推骑手端);`FlashDeliveryApplicationService.java` 在 `userCancel`(约 :397)与 `adminCancel`(约 :622)的 `writeLog` 之后调用 - [x] T012 [US3] 运行 Story 3 测试集确认由红转绿 **Checkpoint**: 取消通知完整(quickstart.md 手动场景 6、7、8) --- ## Phase 6: Polish & Cross-Cutting Concerns **Purpose**: 排除事件防回归 + 全量验证 - [x] T013 反例断言(FR-010):`FlashDeliveryApplicationServiceTest.java` 对 `complete`、`cancelExpiredScheduledOrder`、`tipAdd`、`create` 增加 notificationService 零交互(`verifyNoInteractions` 或 `never`)断言;`FlashDeliveryAutoCompleteTaskTest.java` 如涉及应用服务 mock 一并核对 - [x] T014 全量回归:`mvn -pl ruoyi-admin -am test` 455 测试仅 1 个已知存量失败(`createRejectsUnsupportedPayType`,在途 payType 特性所致);编译通过;git status 确认本特性改动仅 4 个代码文件(2 新建 2 增量)+ specs/028 + CLAUDE.md + feature.json,其余为并行在途特性(预约单窗口)的既有改动`mvn -pl ruoyi-admin -am test`(全部模块测试)+ `mvn -pl ruoyi-admin -am compile` 通过;grep 确认闪送模块外无文件改动(`git status` 仅闪送相关文件与 specs/028) - [x] T015 快照核对:本特性无 `updatesql/sql.md` 新条目(零数据库变更)、无 i18n 文件改动(零新 key,全部复用 `no.message.push.*`)、`ruoyi-system` 零改动;工作树中 i18n/sql.md/system 变更均属并行在途特性,与 028 无关确认无 `updatesql/sql.md` 新条目需求(零数据库变更)、无 i18n 文件改动(零新 key)、`ruoyi-system` 零改动;如有偏差回填 `specs/028-flash-delivery-push/research.md` --- ## Dependencies & Execution Order ### Phase Dependencies - **Setup (Phase 1)**: 无依赖,立即开始 - **Foundational (Phase 2)**: 依赖 Phase 1;**阻塞全部 user story**(T003 之后才能写各 story 的钩子与 verify 断言) - **User Stories (Phase 3-5)**: 依赖 Phase 2;三个 story 共享同一对文件(`FlashDeliveryApplicationService.java` 与两个测试类),**必须按 US1 → US2 → US3 顺序串行执行**,不可并行 - **Polish (Phase 6)**: 依赖 Phase 3-5 全部完成 ### Within Each User Story - 测试任务先行并跑红 → 实现任务 → 验证任务跑绿 - 每 story 收尾可独立演示(见各 Checkpoint 对应 quickstart.md 场景) ### Parallel Opportunities - 本特性文件高度集中,仅 T002/T003 与测试内容天然串行;无安全并行窗口(除 Phase 1 与文档工作) --- ## Implementation Strategy ### MVP First (User Story 1 Only) 1. Phase 1 基线 → Phase 2 基础设施(零行为变化) 2. Phase 3 US1:抢单通知(最高价值单条消息) 3. **STOP and VALIDATE**:quickstart.md 场景 3 手动验证 + 单测绿 4. 继续 US2(取件/送达)、US3(取消) ### Incremental Delivery 每个 story 完成即独立可测、可部署的增量;US1 单独上线即可解决"用户不知是否被接单"的核心痛点。 --- ## Notes - [P] 标记:本特性因文件集中,实际无可并行任务 - 提交节奏:每 Phase 完成后提交一次(`/speckit-git-commit`) - 遵守 plan.md 关键设计点 1-5(不可变字段消费、钩子位置、pushType=3、排除清单) - Java 注释不得含 `*/` 序列;保留 CRLF;不格式化整文件