Branch: 028-flash-delivery-push | Date: 2026-09-17 | Spec: spec.md
Input: Feature specification from /specs/028-flash-delivery-push/spec.md
在闪送订单状态变更点接入既有推送通道,并追加新单通知:立即单以 is_display=true 创建后通知骑手;预约单以 is_display=false 创建,由独立 FlashDeliveryAvailabilityTask 每秒检查时间窗,以条件更新原子开放展示并取得唯一通知资格。履约事件推寄件用户、取消事件推接单骑手;骑手候选按在线状态、FLASH 配送类型、车型、距离和接单互斥规则筛选。外卖和闪送分别使用专属新单 i18n key,payload 继续复用 OrderPushBodyDto(pushType=3)。
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 测试
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 条目。
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)
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 层。
notifyAccepted(order) / notifyPickedUp(order) / notifyDelivered(order) / notifyCancelled(order);入参用事务前读到的 FlashDeliveryOrder(只消费 orderNo/userId/riderId 等不可变字段,不依赖其 status 快照)。accept:withLock 内 writeLog(...) 之后、return 之前;pickup/deliver:共用 transitionWithProof 的 writeLog(...) 之后,按 next 参数分派(PICKED_UP/DELIVERED);userCancel/adminCancel:writeLog(...) 之后,notifyCancelled 内部判 riderId 判空。FlashDeliveryApplicationService 追加第 10 个依赖 FlashDeliveryNotificationService;同步更新既有测试的构造调用。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))。isDisplay=true 并在提交后通知;预约单创建时为 false,骑手列表、详情和抢单均以该字段为前置条件。独立任务调用 Mapper 条件更新,在有效时间窗内将 false -> true;只有影响 1 行的执行发送通知。FlashDeliveryAvailabilityTask.openScheduledOrders() 通过 sys_job 使用 0/1 * * * * ? 每秒执行,concurrent='1' 禁止同一任务并发;数据库条件更新继续提供跨实例幂等保护。no.message.push.new.food.order,闪送使用 no.message.push.new.flash.order。tipAdd、complete、FlashDeliveryAutoCompleteTask.completeDeliveredOrders()、cancelExpiredScheduledOrder、预约单创建不调用新单通知。无 Constitution Check 违规,本节为空。