# Implementation Plan: 闪送订单状态变更消息推送 **Branch**: `028-flash-delivery-push` | **Date**: 2026-09-17 | **Spec**: [spec.md](spec.md) **Input**: Feature specification from `/specs/028-flash-delivery-push/spec.md` ## Summary 在闪送订单状态变更点接入既有推送通道,并追加新单通知:立即单以 `is_display=true` 创建后通知骑手;预约单以 `is_display=false` 创建,由独立 `FlashDeliveryAvailabilityTask` 每秒检查时间窗,以条件更新原子开放展示并取得唯一通知资格。履约事件推寄件用户、取消事件推接单骑手;骑手候选按在线状态、FLASH 配送类型、车型、距离和接单互斥规则筛选。外卖和闪送分别使用专属新单 i18n key,payload 继续复用 `OrderPushBodyDto`(pushType=3)。 ## Technical Context **Language/Version**: Java 21(Spring Boot + MyBatis-Plus) **Primary Dependencies**: Spring 事务同步(`TransactionSynchronizationManager`)、`AsyncManager`、`PayPush`(uni 云函数推送)、`PushEventService`(push_message 入库)、fastjson(payload 序列化) **Storage**: MySQL(`flash_delivery_order` 新增 `is_display`;原子开放预约单;推送消息经 PushEventService 写入既有 `push_message`) **Testing**: JUnit 5 + Mockito(`spy`+`doNothing` 打断网络发送,验证路由决策;仓库先例 `DeliveryOrderNotificationServiceTest`) **Target Platform**: Windows 开发 / Linux 部署的 Spring Boot 服务 **Project Type**: web-service(多模块 Maven:`ruoyi-admin` → `ruoyi-system`) **Performance Goals**: 推送异步执行,不增加订单接口耗时(SC-004 零传导) **Constraints**: 推送失败不得影响状态流转(FR-009);仅事务提交后触发(FR-011);不新增 Android 通道 **Scale/Scope**: 通知服务和应用服务增量 + 1 个独立任务类 + Entity/Mapper/XML + 后端 i18n + SQL/spec 测试 ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* `.specify/memory/constitution.md` 为未定制的占位模板,无项目级原则可引用;改按仓库 `CLAUDE.md` 约束逐条核对: | 约束 | 结论 | |------|------| | Controller 参数规范 | 不涉及(无新 Controller/接口改动) | | 模块边界:集成代码放 ruoyi-admin | ✅ 通知服务放 `ruoyi-admin` 的 `com.ruoyi.app.flashdelivery.service`,不进 ruoyi-system | | 数据库变更须写 updatesql/sql.md | ✅ `is_display`、索引和 `sys_job` 仅记录于 `updatesql/sql.md`,不直接执行 | | 全栈字段清单 | ✅ 更新 Entity、Mapper XML、查询/条件更新及 SQL;该字段不对前端表单开放 | | Java 注释安全(无 `*/` 序列) | ✅ 实现时遵守 | | 前端 i18n 规范 | 不涉及前端;后端 6 份资源新增外卖/闪送专属新单 key | | CRLF 保留、不格式化整文件 | ✅ 实现时遵守 | 无违规,无需 Complexity Tracking 条目。 ## Project Structure ### Documentation (this feature) ```text specs/028-flash-delivery-push/ ├── plan.md # This file (/speckit-plan command output) ├── research.md # Phase 0 output (/speckit-plan command) ├── data-model.md # Phase 1 output (/speckit-plan command) ├── quickstart.md # Phase 1 output (/speckit-plan command) ├── contracts/ │ └── push.md # 推送 payload 契约(Phase 1 output) └── tasks.md # Phase 2 output (/speckit-tasks command - NOT created by /speckit-plan) ``` ### Source Code (repository root) ```text ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/ ├── service/ │ ├── FlashDeliveryApplicationService.java # 修改:状态通知、新单可见性及原子开放 │ └── FlashDeliveryNotificationService.java # 修改:状态通知与闪送新单骑手路由 └── task/ └── FlashDeliveryAvailabilityTask.java # 新增:每秒开放进入预约窗口的订单 ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/ └── service/ ├── FlashDeliveryApplicationServiceTest.java # 修改:构造器新参 + 推送触发/不触发断言 └── FlashDeliveryNotificationServiceTest.java # 新增:接收人判定与跳过逻辑 ``` **Structure Decision**: 集成和任务仍在 `ruoyi-admin`;订单字段、候选骑手查询和原子更新落在 `ruoyi-system` 的既有 Entity/Mapper/XML;不动 Controller 层。 ## 关键设计点(供 tasks 生成引用) 1. **通知服务 API**:`notifyAccepted(order)` / `notifyPickedUp(order)` / `notifyDelivered(order)` / `notifyCancelled(order)`;入参用事务前读到的 `FlashDeliveryOrder`(只消费 orderNo/userId/riderId 等不可变字段,不依赖其 status 快照)。 2. **钩子位置**: - `accept`:`withLock` 内 `writeLog(...)` 之后、`return` 之前; - `pickup`/`deliver`:共用 `transitionWithProof` 的 `writeLog(...)` 之后,按 `next` 参数分派(PICKED_UP/DELIVERED); - `userCancel`/`adminCancel`:`writeLog(...)` 之后,`notifyCancelled` 内部判 `riderId` 判空。 3. **构造器**:`FlashDeliveryApplicationService` 追加第 10 个依赖 `FlashDeliveryNotificationService`;同步更新既有测试的构造调用。 4. **发送实现**:`runAfterCommit` → `userMapper.selectById` 取 cid → `AsyncManager.me().execute` → `PayPush.userPushHandleLocal / qsPushHandleLocal`(title=`no.message.push.message`,content key 见 research.md D2,body=`OrderPushBodyDto.getJson(orderNo, status, -1, 3)`)。 5. **新单开放门闩**:立即单创建时 `isDisplay=true` 并在提交后通知;预约单创建时为 false,骑手列表、详情和抢单均以该字段为前置条件。独立任务调用 Mapper 条件更新,在有效时间窗内将 `false -> true`;只有影响 1 行的执行发送通知。 6. **独立调度**:`FlashDeliveryAvailabilityTask.openScheduledOrders()` 通过 `sys_job` 使用 `0/1 * * * * ?` 每秒执行,`concurrent='1'` 禁止同一任务并发;数据库条件更新继续提供跨实例幂等保护。 7. **候选骑手**:在线、用户类型为骑手、支持 FLASH、车型匹配、配置半径内;加急单排除所有进行中的外卖与闪送,普通单排除进行中的加急闪送;按距离取最近 20 人。 8. **文案区分**:外卖使用 `no.message.push.new.food.order`,闪送使用 `no.message.push.new.flash.order`。 9. **不触发清单**(对应 FR-010):`tipAdd`、`complete`、`FlashDeliveryAutoCompleteTask.completeDeliveredOrders()`、`cancelExpiredScheduledOrder`、预约单创建不调用新单通知。 ## Complexity Tracking > 无 Constitution Check 违规,本节为空。