# Research: 用户端统一订单列表 **Date**: 2026-09-20 | **Status**: 决策已与用户确认,无未解决 NEEDS CLARIFICATION 决策来源:头脑风暴对话(方案对比/滚动加载确认/收件视角确认)+ 代码探索报告(现有列表接口、状态字段全集、写入点清单、索引现状)。 ## D1: 架构——Java 读时聚合(否决中间表/视图/ES) - **Decision**: 读侧新 Service 双路查询 + 内存归并;**不建中间表、不建视图、不引入 ES**;零 DB 结构变更。 - **Rationale**: ① 中间表双写一致性风险大——探索确认两域状态写入点多达 20+ 处(商家四状态、骑手三连、用户取消、平台改状态、LINE/OMG 支付回调、闪送状态机六方法、两个自动任务),每处都要同步,且该区域遍布废弃代码(TestTask/ZaloPay 等),漏改即脏数据;② 视图把 tab 状态映射埋进 SQL DDL(每分支 CASE WHEN 30-60 行),不可单测,每次调口径要人工执行 DDL;③ ES 是亿级订单方案,本项目体量杀鸡用牛刀;④ 个人订单列表数据量小(单用户几百单级),读时聚合性能足够,数据永远实时,写侧零侵入。 - **Alternatives**: ① 中间表统一存储(用户最初设想)——被一致性与维护成本否决;② MySQL UNION 视图——被可测性与变更成本否决;③ ES/搜索服务——体量不符,否决。将来若演进为订单搜索服务,仅替换读实现,接口契约不变。 ## D2: 分页——游标滚动(严格 keyset,含同秒跨源决胜) - **Decision**: cursor = 不透明字符串 `时间毫秒_source_id`;前端滚动加载透传 nextCursor。排序:`createTime DESC → source 字典序(flash0)。闪送侧映射:非终态(WAITING_ACCEPTANCE/ACCEPTED/PICKED_UP/DELIVERED)→active、COMPLETED→completed、CANCELLED→cancelled;unpaid/refund 闪送侧恒空。 - **Rationale**: 前端 tab 参数名不变减少改动;外卖语义经过现网验证;归类规则抽成一份谓词(SQL 条件构造 + 内存判定同源),杜绝两处漂移(FR-007)。 - **Alternatives**: 新造一套 tab 枚举——徒增前端映射,否决。 ## D4: 闪送收件人视角——纳入统一列表 - **Decision**: 闪送路查询条件 `(user_id=? OR receiver_user_id=?)` 单条 OR 查询;role 判定:user_id 命中→sender,否则 receiver;寄件人=收件人只出一条(判 sender)。列表项带 role 字段,前端渲染"我收的"标签。 - **Rationale**: 用户明确选择"统一进列表,前端通过字段区分";OR 单查询天然去重(不会一单两行)。 - **Alternatives**: 收件视角留在闪送独立页面——用户否决。 ## D5: 统一卡片 DTO——公共字段 + 两源渲染块 - **Decision**: 公共字段(sourceType/role/orderId/orderNo/unifiedStatus/createTime毫秒/amount/currency/hasAfterSale)+ 外卖块(type/state/deliveryStatus/payStatus/payType/afterSaleStatus 原值 + storeName)+ 闪送块(对齐 `FlashDeliveryUserOrderListView` 字段子集:status/serviceType/deliveryType/vehicleType/deliveryMode/预约窗/取收地址/packageType/quantity/tipAmount 等)。前端按 sourceType 复用现有两种卡片渲染。 - **Rationale**: 状态族原值透传,前端现有渲染逻辑(数值/枚举→文案映射)零改动;详情跳转键外卖用业务单号 ddId、闪送用订单 ID/单号,直达现有详情接口。 - **Alternatives**: 后端把状态渲染成文案——与现有"前端 i18n 渲染"分工不符,否决。 ## D6: 范围裁剪——不做角标、旧接口不动、只做用户端 - **Decision**: 不做 tab 数量角标(未提出,YAGNI,后续可加 counts 接口);现有外卖/闪送两个列表接口原样保留(其它入口在用);商家/骑手端不涉及。 - **Rationale**: 范围控制;旧接口零回归是 FR-008 的验证项。 ## D7: 索引——上线前核实,缺则补(唯一条件性 SQL) - **Decision**: 探索确认 `flash_delivery_order` 现有索引仅 PK + uk(order_no) + uk(user_id, client_request_id),**缺 (user_id, create_time) 与 (receiver_user_id) 索引**;`pos_order` 建表 DDL 不在 sql.md(历史表),索引需在库上核实。补索引语句写入 updatesql/sql.md 并标注"核实后执行",不直接执行。 - **Rationale**: 读时聚合的每路查询 = `WHERE user/角色 = ? AND 时间游标条件 ORDER BY 时间 DESC LIMIT n`,有索引才是索引扫描;个人列表行数不大,无索引也能跑但属于隐患。备注:`(user_id, client_request_id)` 唯一索引的 user_id 前缀可部分复用,但不含 create_time 排序。 - **Alternatives**: 不核实不补——性能隐患留坑,否决。 ## D8: 每路分页实现——MP `Page<>(1, limit, false)` 固定第一页 - **Decision**: 游标推进等价于"永远查第一页",每路 `new Page<>(1, size+1, false)`(关闭 count 查询),取 size+1 行判定 hasMore。 - **Rationale**: 游标模式下页码恒为 1;关 count 省一半查询;hasMore 由第 size+1 行是否存在判定。 - **Alternatives**: PageHelper.startPage——项目新代码已统一 MP 分页(探索结论 7),且 PageHelper 的 count 对本场景多余,否决。 ## 探索报告关键事实存档(实现时引用) - 外卖列表接口:`GET /system/userOrder/orderList`(`UserOrderController.java:536-601`),tab 条件 555-582 行;返回 MP Page 原始实体。 - 闪送列表接口:`GET /system/flashDelivery/orders`(`FlashDeliveryUserController.java:55-62` → `FlashDeliveryApplicationService.userOrders`),无 tab,role=sender/receiver。 - 状态字段:外卖 `state(0待处理1已接单2已出餐3已完成4已取消)` + `deliveryStatus(0/1/2/3)` + `payStatus(0未付1已付2已退)` + `afterSaleStatus(0-6)`;payType 常量 `OrderLifecycleService.java:33-40`(OFFLINE=1/OMG=2/LINE=3/CASH=4/APPLE_PAY=5/TRANSFER=6)。闪送 `FlashDeliveryStatus` 六态枚举。 - 两表主键均为独立 AUTO_INCREMENT BIGINT,**数值必然重叠**,全局键必须带 source。 - 外卖业务单号 `ddId` 为客户端传入 String;闪送 `orderNo` 服务端生成(FD+UUID24)。 - 时间字段:外卖 `cretim`、闪送 `create_time`,均 Date(秒级)。