Branch: 030-unified-order-list | Date: 2026-09-20 | Spec: spec.md
Input: Feature specification from /specs/030-unified-order-list/spec.md
用户端外卖(pos_order)与闪送(flash_delivery_order)订单合并为一个统一列表:Java 读时聚合(否决中间表/视图),新包 com.ruoyi.app.unifiedorder 提供独立接口 GET /system/unifiedOrder/list,支持 6 tab(沿用外卖现有枚举 all/unpaid/active/completed/cancelled/refund)+ 来源筛选(all/takeaway/dinein/pickup/flash,2026-09-21 扩堂食/自取:takeaway=type0 仅外送、dinein=type2、pickup=type1)+ 游标滚动分页(严格 keyset,同秒跨源决胜:source 字典序 + id DESC)。两路各查 size+1 条内存归并;闪送路含收件人视角(user_id OR receiver_user_id,role 字段区分);unpaid/refund tab 跳过闪送路。tab 归类做成表驱动谓词(同一份规则生成 SQL 条件与内存判定)。零数据库结构变更(唯一条件项:核实/补两表查询索引)。
Language/Version: Java 21(Maven 多模块:ruoyi-admin / ruoyi-system / ruoyi-common)
Primary Dependencies: Spring Boot、MyBatis-Plus(分页用 Page<> 构造,游标模式下 searchCount=false)、Lombok;复用现有 PosOrderMapper / FlashDeliveryOrderMapper(只读,不新增写路径)
Storage: 无新表无视图。只读两张源表:pos_order(时间字段 cretim,Date)、flash_delivery_order(时间字段 create_time,Date;两表均为秒级 datetime,同秒并列真实存在,决胜规则必须确定性)。条件项:pos_order(user_id, cretim)、flash_delivery_order(user_id, create_time) 与 (receiver_user_id) 索引上线前核实,缺则按流程补(updatesql/sql.md)
Testing: JUnit 5 + Mockito,沿用 029 建立的 SpringUtils 静态桩 + MockHttpServletRequest 模式;测试位于 ruoyi-admin/src/test/java/com/ruoyi/app/unifiedorder/。构建:JAVA_HOME=%USERPROFILE%\.jdks\graalvm-jdk-21.0.12+7.1 + mvn -pl ruoyi-admin -am test -Dtest='Xxx' -Dsurefire.failIfNoSpecifiedTests=false
Target Platform: Linux 服务器 Spring Boot(越南用户端 uni-app 的后端)
Project Type: web-service(单体多模块 Maven)
Performance Goals: 单次滚动请求每源至多一次索引查询(各取 size+1 行,无 count 查询);耗时与现有单域列表同量级
Constraints: Controller 参数规范(GET 全 @RequestParam + @RequestHeader token);不触碰 CLAUDE.md 废弃代码;现有两个列表接口零改动;保留 CRLF
Scale/Scope: 新包约 6 个主文件 + 3 个测试文件 + 1 条条件性索引 SQL;不动任何既有订单写路径
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
.specify/memory/constitution.md 为空模板;以 CLAUDE.md 项目规范替代核对:
| 约束 | 结果 |
|---|---|
| Controller 请求参数规范(GET 显式 @RequestParam、token @RequestHeader、无 Map 入参) | PASS |
| 模块依赖方向 ruoyi-admin → ruoyi-system | PASS — 新包在 ruoyi-admin,仅用 ruoyi-system 实体/Mapper |
| 数据库变更管理(不直接执行,写 sql.md) | PASS — 仅条件性索引补充 |
| 废弃代码清单不触碰 | PASS — 只读现有表,不参考 PayController/TestTask 等 |
| Java 注释安全 | PASS |
| 后端错误消息走 MessageUtils | PASS — 非法 cursor 等错误用消息 key |
Phase 1 设计后复核:全部 PASS,无违规项。
specs/030-unified-order-list/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output(DTO/cursor/谓词规则)
├── quickstart.md # Phase 1 output
├── contracts/
│ └── api-contract.md # 前端契约
└── tasks.md # Phase 2 output (/speckit-tasks)
ruoyi-admin/src/main/java/com/ruoyi/app/unifiedorder/
├── UnifiedOrderQueryController.java # GET /system/unifiedOrder/list
├── UnifiedOrderQueryService.java # 两路查询 + 归并 + 游标推进 + 店铺名批量回填
├── UnifiedOrderTab.java # tab 枚举 + 表驱动谓词(wrapper 条件构造 & 实体内存判定同源)
├── OrderSource.java # takeaway/dinein/pickup/flash 枚举(字典序决胜依据;dinein/pickup 为 pos_order type 子集)
├── UnifiedCursor.java # 游标:生成/解析/校验(时间_source_id)
└── dto/
├── UnifiedOrderListItem.java # 公共字段 + takeaway 渲染块 + flash 渲染块
└── UnifiedOrderPageView.java # 分页信封:list/hasMore/nextCursor
ruoyi-admin/src/test/java/com/ruoyi/app/unifiedorder/
├── UnifiedOrderTabTest.java # 两域×6 tab×边界样本归类 + wrapper SQL 片段断言
├── UnifiedCursorTest.java # 生成/解析/非法输入/同秒并列
├── UnifiedOrderQueryServiceTest.java # 归并排序、翻页完整性、hasMore、tab 跳过闪送路、role 判定
└── UnifiedOrderQueryControllerTest.java # 参数契约 + 组合查询
updatesql/sql.md # 条件性索引补充(标注"核实后执行")
(复用只读)ruoyi-system: PosOrderMapper / FlashDeliveryOrderMapper / InfoUser(店铺名回填)
Structure Decision: 新功能独立成包(不向 700+ 行的 UserOrderController 继续堆叠);两源表的查询直接走各自 MP Mapper(游标模式下 new Page<>(1, limit, false) 固定第一页,天然无 count 开销);tab 谓词集中在一个枚举类里同时服务 SQL 构造与内存判定(唯一事实源,FR-007)。
Fill ONLY if Constitution Check has violations that must be justified
无违规项。