# 实施计划:闪送配送服务 **分支**:`test` | **日期**:2026-08-31 | **规格**:[spec.md](./spec.md) ## 摘要 在既有独立闪送模块上补充包裹类别/重量档、立即或预约配送、可选 PIN 交付、寄件图片、费用明细、页面状态分组、骑手公开摘要和角色安全响应。服务端继续负责路线报价、骑手原子抢单、取件/送达图片、用户签收、24 小时自动完成和平台介入;支付、退款、结算、骑手收入与自动派单仍不在本期范围。 ## 技术上下文 - **语言/版本**:Java 21 - **主要依赖**:Spring Boot、Spring MVC、Spring Security、MyBatis/MyBatis-Plus、Apache HttpClient 4、fastjson2 - **存储**:MySQL;迁移仅记录在 `updatesql/sql.md` - **测试**:JUnit 5、Mockito、Spring MockMvc、MyBatis XML 解析测试 - **目标平台**:现有 RuoYi 多模块 REST API 服务端 - **性能目标**:列表分页;抢单和状态流转单条条件更新;地图超时后立即降级 - **约束**:保持 `ruoyi-admin -> ruoyi-system`;Controller 不接收 Map;App 身份只从 token 解析;业务错误全部 i18n;不执行数据库迁移 - **范围**:24 个闪送/地址端点、4 张新表、1 张地址表增量、五套语言资源、定向自动化测试 ## 规范检查 - [x] 独立 `flash_delivery_*` 模型,不复用 `pos_order`/`taxi_order` - [x] HTTP 集成只放在 `ruoyi-admin` - [x] Controller 使用显式 DTO 和参数注解 - [x] 用户与骑手接口通过 token 取身份,平台接口使用明确权限 - [x] 金额、距离和状态由服务端计算与校验 - [x] 地址、订单和凭证执行所有权/角色校验 - [x] SQL 只写 `updatesql/sql.md` - [x] 支付能力明确延期 ## 源码结构 ```text ruoyi-system/src/main/java/com/ruoyi/system/ ├── domain/flash/ # 订单、计价、图片、日志实体 └── mapper/flash/ # 持久化、原子抢单、条件状态流转 ruoyi-system/src/main/resources/mapper/flash/*.xml ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/ ├── controller/ # 用户、骑手、平台 Controller ├── dto/ # 明确请求 DTO 与响应视图 ├── service/ # 报价、状态机、权限和脱敏 ├── route/ # Google Routes 与直线降级 └── task/ # 送达 24 小时自动完成 ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/ ruoyi-system/src/test/java/com/ruoyi/system/mapper/flash/ ``` 地址簿沿用 `InfoAddress`、`IInfoAddressService` 和 `InfoAddressController`,增加请求 DTO、置顶字段、按用户条件查询/更新及所有权测试。 ## 核心实现决策 1. `FlashDeliveryApplicationService` 是事务边界;报价只读,创建和每个状态操作同时写订单与日志。 2. 抢单执行带 `status='WAITING_ACCEPTANCE' AND rider_id IS NULL` 的单条 UPDATE;受影响行数不是 1 即已被抢走。 3. 其余状态变更携带订单归属、操作者归属和预期状态条件,阻止并发覆盖;`version` 字段随每次更新递增,仅作审计参考,不作为更新条件。 4. `(user_id, client_request_id)` 唯一键保证创建幂等;冲突时返回原订单。 5. Google Routes API 字段掩码仅取距离与时长;异常、非 2xx、空路线或无效距离降级为 Haversine。 6. 待抢单使用专用脱敏视图;抢单后只有订单骑手可见完整电话和详细地址。 7. 寄件图片在创建事务中写入;取件/送达图片在状态更新事务中写入。三类凭证均校验 1 至 9 个 HTTP(S) URL,取件和送达不得为空。 8. 预约订单沿用 `WAITING_ACCEPTANCE`,可抢查询和原子抢单 SQL 同时限制 `scheduled_pickup_start_at <= now`,不增加仅为调度使用的新状态。 9. PIN 默认启用并由服务端生成四位数字;用户详情可见,骑手响应始终剔除,送达事务先校验 PIN 再写凭证和状态。 10. 用户/骑手使用业务视图,平台详情保留完整审计实体;待抢视图只增加类别、重量档、配送时段、预计时长和市/区级路线信息。 11. 自动任务扫描送达满 24 小时订单并逐条条件完成,和用户/平台并发时只允许一次成功。 ## 补充原型增量实施批次 1. 更新规格、API、数据模型和 `updatesql/sql.md`,明确旧表增量字段及索引。 2. 先扩展计价器、Mapper XML、应用服务和 Controller 契约测试并确认红灯。 3. 增加请求/响应 DTO、实体字段、计价明细与角色安全映射。 4. 实现预约可抢条件、PIN 校验、寄件凭证和状态分组分页。 5. 使用 JDK 21 运行闪送定向测试、MyBatis XML 测试、模块构建与差异检查。 ## 验证策略 测试先行:先写测试并确认因实现缺失而失败,再添加最小生产代码。最终统一执行: ```powershell $env:JAVA_HOME='C:\Users\qmj\.jdks\graalvm-jdk-21.0.7' $env:PATH="$env:JAVA_HOME\bin;$env:PATH" mvn -pl ruoyi-system -am -Dtest=FlashDeliveryMapperXmlTest -Dsurefire.failIfNoSpecifiedTests=false test mvn -pl ruoyi-admin -am -Dtest='FlashDelivery*Test,InfoAddressControllerSecurityTest' -Dsurefire.failIfNoSpecifiedTests=false test mvn -pl ruoyi-admin -am -DskipTests package git diff --check ``` ## 复杂度跟踪 无规范违例。四张新表分别承担订单快照、可变计价、图片凭证和审计日志;应用层留在 admin 是外部 HTTP 依赖和模块边界共同决定的。 ## 平台管理前端增量(2026-09-01) ### 目标与架构 **目标**:在 `foodie-admin-vue` 增加可直接运营的闪送价格配置和订单管理入口,完整消费现有六个平台 API,不修改后端接口语义。 **架构**:继续使用若依动态菜单加载两个 Vue 2 + Element UI 页面。API 请求集中在一个模块,状态/服务类型映射、分页归一化、操作可见性和价格校验集中在可直接由 Node 测试的纯函数模块;订单详情拆为独立展示组件,避免列表页面同时承担全部渲染职责。 **技术栈**:Vue 2.6、Element UI 2.15、Vue i18n 8、若依 `request`、动态 `sys_menu`、Node 内置 `node:test`。 ### 全局约束 - 不新建分支或 worktree,后端与平台前端均在当前 `test` 分支工作。 - 不增加 npm 依赖,不引入新的 UI 或状态管理框架。 - 所有新增用户可见文本使用 `flashDelivery.*`,并同步简中、繁中、英文、越南文。 - 前端文件保留 CRLF;只修改本功能文件,不格式化既有大文件。 - 菜单和权限 SQL 只追加到 `foodie_server/updatesql/sql.md`,不直接执行数据库变更。 - 后端分页响应读取 `data.records` 和 `data.total`,不得套用若依传统 `rows/total` 响应。 - 操作成功后重新读取服务端数据;操作失败不提前修改本地订单状态或价格。 ### 文件结构 ```text foodie-admin-vue/ ├── package.json # 增加闪送定向契约测试命令 ├── tests/flash-delivery.test.cjs # 请求配置、页面规则和四语言真实行为 └── src/ ├── api/flashDelivery/contracts.js # 可执行的六个平台请求配置构造器 ├── api/flashDelivery/index.js # 调用若依 request 的六个平台接口 ├── views/flashDelivery/shared.js # 枚举、分页、价格校验、操作可见性 ├── views/flashDelivery/pricing/index.vue # 三种服务价格列表与编辑弹窗 ├── views/flashDelivery/orders/index.vue # 筛选、分页、取消和完成 ├── views/flashDelivery/orders/OrderDetail.vue # 完整详情、图片和日志 └── api/language/language.{zh_CN,zh_TW,en_US,vi}.js foodie_server/ ├── specs/024-flash-delivery/{spec,design,plan,tasks}.md └── updatesql/sql.md # 父菜单、两个页面及六项权限归位 ``` ### 接口和页面数据流 1. `GET /system/flashDelivery/admin/pricing` 返回数组;价格页只显示服务端实际存在的配置,不创建前端默认价格。 2. `PUT /system/flashDelivery/admin/pricing/{serviceType}` 提交七个完整字段。弹窗先执行纯函数校验,成功响应覆盖列表对应行。 3. `GET /system/flashDelivery/admin/orders` 原样提交 `pageNum,pageSize,status,serviceType,orderNo,userId,riderId`;`normalizePage` 从 `response.data` 提取 `records,total,current,size`。 4. `GET /system/flashDelivery/admin/orders/{id}` 返回 `{order,images,logs}`;打开详情前先清空旧数据,失败时保持弹窗关闭。 5. `POST .../{id}/cancel` 提交 `{reason}`,只对非 `COMPLETED/CANCELLED` 状态显示并要求二次确认。 6. `POST .../{id}/complete` 无请求体,只对 `DELIVERED` 状态显示并要求二次确认。 ### 交互设计 - 价格页使用紧凑表格展示三种服务和版本信息;“编辑”打开宽度 640px 的表单弹窗,金额两位小数、距离单位为米、倍率最小为 1。 - 订单页顶部使用可折叠行内筛选,表格展示订单号、服务、状态、用户、骑手、距离、金额和创建时间。 - 详情使用 960px 弹窗,按“订单概况、取送地址、费用与路线、履约与 PIN、图片凭证、状态日志”分区;图片支持预览,空集合显示空状态。 - 状态使用 Element UI 标签,同时显示本地化文字,避免只靠颜色传达。 - 表格设置固定操作列和横向滚动容器;页面自身不产生横向溢出。 ### 错误处理 - 列表和价格请求使用 `finally` 关闭 loading;后端统一错误拦截负责消息提示。 - 价格校验失败由表单显示本地化字段错误,不发送请求。 - 详情请求开始时清空旧详情,请求失败不打开弹窗。 - 取消原因去除首尾空白后必须非空;取消/完成成功关闭操作层并刷新列表,若详情仍打开则重新读取详情。 ### 测试驱动批次 1. 先创建 `tests/flash-delivery.test.cjs` 和 npm 命令,直接执行请求配置构造器、分页、价格校验、状态操作与四语言对象;运行后必须因目标模块不存在而失败。 2. 实现 API 请求配置、API 封装和 `shared.js`,使请求、分页、价格校验和状态规则测试转绿。 3. 实现价格页面与四语言资源,使价格字段和语言一致性测试转绿。 4. 实现订单页面、详情组件和介入操作;页面调用已经通过测试的状态操作纯函数,权限指令通过 ESLint、构建和页面检查验证。 5. 追加幂等菜单 SQL,再统一执行定向测试、ESLint、生产构建和两个仓库差异检查。 ### 验证命令 ```powershell Set-Location E:\QtwCode\foodie\foodie-admin-vue npm run test:flash-delivery npx eslint src/api/flashDelivery/index.js src/views/flashDelivery tests/flash-delivery.test.cjs npm run build:prod git diff --check Set-Location E:\QtwCode\foodie\foodie_server git diff --check ``` ## 外卖同规则时段运价增量(2026-09-01) 本节替代上方“平台管理前端增量”中的单行价格配置接口、七字段表单和两位小数计价描述;订单管理与平台介入设计继续有效。 ### 目标与架构 **目标**:将闪送计价改为与现有外卖订单一致的“当前时段 + 起送距离/价格 + 计价距离/金额”规则,移除最低价、时长费、尖峰倍率和启停状态,并让平台在独立闪送入口维护三种服务的多个运价时段。 **架构**:`flash_delivery_pricing` 继续作为闪送独立配置表,但从每服务单行改为每服务多时段;`flash_delivery_pricing_lock` 为三种服务各保存一行事务锁。`FlashDeliveryPricingMapper` 在数据库当前时间下返回全部匹配时段、提供重叠计数并用 `SELECT ... FOR UPDATE` 串行化同服务写入;应用服务要求当前时段唯一,异常多行时显式失败。`FlashDeliveryPricingCalculator` 只负责距离边界和整数 TWD 计算;应用服务负责 CRUD、版本、订单快照和业务错误。平台前端复用现有价格页与权限,改为时段新增、修改和删除。 **技术栈**:JDK 21、Spring Boot、MyBatis-Plus/MyBatis、MySQL、JUnit 5/Mockito;Vue 2.6、Element UI 2.15、Vue i18n 8、Node `node:test`。 ### 全局约束 - 不新建分支或 worktree,后端与平台前端都在当前 `test` 分支工作。 - 不直接执行数据库迁移;删除测试阶段旧计价数据和字段的 SQL 只追加到 `updatesql/sql.md`。 - 路线预计时长继续保存和展示,但不得参与计价。 - 起送价格、计价金额、里程费用和最终金额均为整数 TWD;计算结果使用 `RoundingMode.HALF_UP` 取整到 1 元,不做千位特殊取整。 - 同一服务类型的时段使用开始包含、结束不包含语义且不得重叠;写操作先取得服务类型数据库行锁再查重,保存即生效,删除即失效,不提供 `enabled`。 - Controller 使用明确 DTO 和显式注解,平台配置接口继续使用 `flash:pricing:list/edit`。 - 前端新增可见文本同步简中、繁中、英文和越南文,文件保留 CRLF,不增加 npm 依赖。 - 只修改闪送功能所需文件,不触碰工作区原有脏文件。 ### 文件职责 ```text foodie_server/ ├── ruoyi-system/src/main/java/com/ruoyi/system/domain/flash/ │ ├── FlashDeliveryPricing.java # 时段运价实体 │ └── FlashDeliveryOrder.java # 整数金额与运价快照 ├── ruoyi-system/src/main/java/com/ruoyi/system/mapper/flash/ │ └── FlashDeliveryPricingMapper.java # 当前时段与重叠查询 ├── ruoyi-system/src/main/resources/mapper/flash/ │ └── FlashDeliveryOrderMapper.xml # 新订单快照字段映射 ├── ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/ │ ├── controller/FlashDeliveryAdminController.java │ ├── dto/FlashDeliveryPricingRequest.java │ ├── dto/FlashDeliveryPriceBreakdown.java │ ├── dto/FlashDeliveryQuoteView.java │ ├── dto/FlashDeliveryServiceView.java │ └── service/{FlashDeliveryApplicationService,FlashDeliveryPricingCalculator}.java ├── ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/ │ ├── controller/FlashDeliveryControllerContractTest.java │ └── service/{FlashDeliveryApplicationServiceTest,FlashDeliveryPricingCalculatorTest}.java ├── ruoyi-admin/src/main/resources/i18n/messages_{zh_CN,zh_TW,en_US,vi}.properties └── updatesql/sql.md foodie-admin-vue/ ├── src/api/flashDelivery/{contracts,index}.js ├── src/views/flashDelivery/shared.js ├── src/views/flashDelivery/pricing/index.vue ├── src/views/flashDelivery/orders/OrderDetail.vue ├── src/api/language/language.{zh_CN,zh_TW,en_US,vi}.js └── tests/flash-delivery.test.cjs ``` ### 后端接口与数据流 1. `FlashDeliveryPricing` 使用 `startTime,endTime,startingDistance,startingFare,distance,freight`;距离为正十进制公里,金额为正 `Long`。 2. `selectCurrent(serviceType)` 使用数据库 `CURTIME()` 和 `start_time <= CURTIME() AND end_time > CURTIME()` 返回全部匹配时段;找不到时返回无可用运价错误,超过一条时返回配置重叠错误,不用 `LIMIT 1` 掩盖异常。 3. `countOverlaps(serviceType,startTime,endTime,excludedId)` 使用 `start_time < endTime AND end_time > startTime` 检测重叠。新增、修改和删除在事务中先锁定 `flash_delivery_pricing_lock` 对应服务行;跨服务修改按字典序锁定旧、新服务,再校验并写入。 4. 计价器签名改为 `calculateBreakdown(FlashDeliveryPricing pricing, int distanceMeters)`。超出公里小于 `0.5` 时计费公里为 `0`,小于 `1` 时为 `1`,否则使用实际超出公里;`distanceFee` 四舍五入为 `Long`,`amount=startingFare+distanceFee`。 5. `GET /admin/pricing` 返回全部时段;`POST /admin/pricing` 新增;`PUT /admin/pricing/{id}` 按版本更新;`DELETE /admin/pricing/{id}` 删除。新增、修改和删除都使用 `flash:pricing:edit`。 6. `home` 对三种服务分别读取当前时段,只返回有配置的服务;`quote/create` 每次重新读取当前时段并计算。订单固化 `pricingId,pricingVersion,pricingStartTime,pricingEndTime,startingDistance,startingFare,distance,freight,distanceFee`。 7. 报价与公开服务 DTO 移除旧计价字段,保留 `estimatedDurationSeconds`;金额 DTO 和订单实体改为整数 `Long`。 ### 平台前端交互 - 价格页表格按服务类型、开始时间排序,列出起止时间、起送距离/价格、计价距离/金额、版本和更新时间。 - “新增时段”打开空表单;“编辑”回填完整配置;“删除”二次确认后请求服务端并刷新列表。 - 表单校验开始时间早于结束时间、距离为正数、金额为正整数,并用当前列表预检同服务时段重叠;服务端仍执行最终重叠校验。 - 页面不显示启停、最低价、每分钟价格或尖峰倍率。配置保存后立即生效,无匹配时段时由后端阻止报价。 - 订单详情展示匹配时段、起送距离/价格、计价距离/金额、里程费用和整数总额;预计时长仍显示在路线信息中。 ### TDD 批次 1. 先改 `FlashDeliveryPricingCalculatorTest`,覆盖起送内、超出 `0.49/0.50/0.99/1.20` 公里、非 1 公里计价距离、`.5` 金额四舍五入及非法配置;确认旧实现编译或断言失败。 2. 再改应用服务和 Controller 契约测试,覆盖当前时段读取、无时段、重叠新增/修改、版本冲突、删除、整数快照和新路由;确认旧接口失败。 3. 实现实体、并发锁表、Mapper、DTO、计算器、应用服务、Controller 和四语言后端错误,使定向后端测试转绿。 4. 先改 Node 测试,要求新增/修改/删除请求、六字段载荷、时段/整数校验、页面权限和四语言新 key,并确认旧前端失败。 5. 实现前端请求、纯函数、价格页、订单详情与四语言,使 Node 测试转绿。 6. 追加开发阶段重建 SQL,删除旧闪送测试表并创建运价锁、时段配置及新订单快照最终结构;不执行 SQL。 ### 验证命令 ```powershell Set-Location E:\QtwCode\foodie\foodie_server $env:JAVA_HOME='C:\Users\qmj\.jdks\graalvm-jdk-21.0.7' $env:PATH="$env:JAVA_HOME\bin;$env:PATH" mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryPricingCalculatorTest,FlashDeliveryApplicationServiceTest,FlashDeliveryControllerContractTest,FlashDeliveryOrderViewTest,FlashDeliveryRiderOrderListViewTest,FlashDeliveryI18nContractTest' -Dsurefire.failIfNoSpecifiedTests=false test mvn -pl ruoyi-admin -am -DskipTests package git diff --check Set-Location E:\QtwCode\foodie\foodie-admin-vue npm run test:flash-delivery npx eslint --no-ignore src/api/flashDelivery/index.js src/api/flashDelivery/contracts.js src/views/flashDelivery/shared.js src/views/flashDelivery/pricing/index.vue src/views/flashDelivery/orders/index.vue src/views/flashDelivery/orders/OrderDetail.vue tests/flash-delivery.test.cjs npm run build:prod git diff --check ``` # 闪送 App 订单列表与详情精简 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 将用户端和骑手端闪送订单接口收敛为与页面一致的轻量列表与直接详情响应,并让骑手列表复用外卖订单的分页、页签和位置参数。 **Architecture:** Controller 只暴露新的显式查询参数;`FlashDeliveryApplicationService` 在数据库分页前完成用户归属、骑手页签、预约可抢和附近距离条件。列表分别使用用户/骑手卡片 DTO,App 详情使用继承订单业务字段的直接 DTO;平台审计详情继续独立保留实体、图片和日志。 **Tech Stack:** JDK 21、Spring Boot、MyBatis-Plus、MySQL、JUnit 5、Mockito、Lombok。 ## Global Constraints - 不新建分支或 worktree,在当前 `test` 分支连续实施。 - 不保留开发测试阶段的 `/orders/available`、`/orders/mine`、`scene`、`status`、`serviceType` 兼容入口。 - 用户和骑手身份只从 `@RequestHeader String token` 解析;查询参数全部显式使用 `@RequestParam`。 - App 列表和详情禁止返回实体、幂等号、内部用户 ID、计价审计字段或原始状态日志。 - 骑手接单前显示完整取送文字地址,但不返回联系人、电话、实际 PIN、精确坐标或履约图片。 - 后端业务错误同步 `messages.properties`、`messages_zh_CN.properties`、`messages_zh_TW.properties`、`messages_en_US.properties` 和 `messages_vi.properties`。 - 不执行数据库迁移;本次接口精简不需要表结构变更。 --- ### Task 1: 固定 Controller 与 DTO 契约 **Files:** - Modify: `ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryControllerContractTest.java` - Delete: `ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryAvailableOrderViewTest.java` - Create: `ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderListViewTest.java` - Modify: `ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryOrderViewTest.java` - Create: `ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryUserOrderListView.java` - Create: `ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderListView.java` - Create: `ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderPageView.java` - Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryOrderDetailView.java` - Delete: `ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryAvailableOrderView.java` - Delete: `ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryImageView.java` - Delete: `ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryLogView.java` **Interfaces:** - Produces: `FlashDeliveryUserOrderListView` for user cards. - Produces: `FlashDeliveryRiderOrderListView` for all rider tabs. - Produces: `FlashDeliveryRiderOrderPageView(records,total,current,size,nearbyTaskCount,highestOrderAmount)`. - Produces: direct `FlashDeliveryOrderDetailView extends FlashDeliveryOrderView` with `senderImageUrls,pickupImageUrls,deliveryImageUrls,rider,deliveryPinCode`. - [ ] **Step 1: Write failing reflection and whitelist tests** ```java Method userOrders = FlashDeliveryUserController.class.getMethod( "orders", String.class, int.class, int.class); Method riderOrders = FlashDeliveryRiderController.class.getMethod( "orders", String.class, int.class, int.class, String.class, BigDecimal.class, BigDecimal.class); assertThrows(NoSuchFieldException.class, () -> FlashDeliveryOrderDetailView.class.getDeclaredField("order")); assertTrue(riderFields.containsAll(Set.of("pickupAddress", "deliveryAddress", "pickupDistanceMeters", "pinRequired"))); ``` - [ ] **Step 2: Run tests and verify the old contract fails** ```powershell $env:JAVA_HOME='C:\Users\qmj\.jdks\graalvm-jdk-21.0.7' $env:PATH="$env:JAVA_HOME\bin;$env:PATH" mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryControllerContractTest,FlashDeliveryOrderViewTest,FlashDeliveryRiderOrderListViewTest' -Dsurefire.failIfNoSpecifiedTests=false test ``` Expected: compilation or reflection failure because the old controllers and wrapper DTO still exist. - [ ] **Step 3: Add the minimal DTO types** ```java @Data public class FlashDeliveryOrderDetailView extends FlashDeliveryOrderView { private List senderImageUrls = List.of(); private List pickupImageUrls = List.of(); private List deliveryImageUrls = List.of(); private FlashDeliveryRiderSummaryView rider; private String deliveryPinCode; } ``` `FlashDeliveryUserOrderListView` 固定卡片字段为订单标识、服务/包裹、状态、配送方式/预约时段、取送文字地址、预计时长、金额、送达时间和创建时间。`FlashDeliveryRiderOrderListView` 固定增加重量档、`pinRequired`、`pickupDistanceMeters` 和路线距离。分页 DTO 只使用上述六个分页/摘要属性。 - [ ] **Step 4: Run DTO tests** ```powershell mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryControllerContractTest,FlashDeliveryOrderViewTest,FlashDeliveryRiderOrderListViewTest' -Dsurefire.failIfNoSpecifiedTests=false test ``` Expected: DTO whitelist tests pass; controller reflection test remains red until Task 2. ### Task 2: 实现轻量列表、统一骑手页签和直接详情 **Files:** - Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryUserController.java` - Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryRiderController.java` - Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationService.java` - Modify: `ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationServiceTest.java` - Modify: `ruoyi-admin/src/main/resources/i18n/messages.properties` - Modify: `ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties` - Modify: `ruoyi-admin/src/main/resources/i18n/messages_zh_TW.properties` - Modify: `ruoyi-admin/src/main/resources/i18n/messages_en_US.properties` - Modify: `ruoyi-admin/src/main/resources/i18n/messages_vi.properties` **Interfaces:** - Consumes: Task 1 DTOs. - Produces: `userOrders(Long userId,int page,int size)`. - Produces: `riderOrders(Long riderId,int page,int size,String tab,BigDecimal longitude,BigDecimal latitude)`. - Produces: `riderDetail(Long riderId,Long orderId)` returning `FlashDeliveryOrderDetailView`, never `Object`. - [ ] **Step 1: Write failing service tests** ```java var userPage = fixture.service.userOrders(7L, 1, 10); var newTasks = fixture.service.riderOrders(8L, 1, 10, "newTask", new BigDecimal("121.5"), new BigDecimal("25.0")); assertEquals("台北市中山區南京東路二段 100 號", newTasks.getRecords().get(0).getPickupAddress()); assertNull(fixture.service.riderDetail(8L, 10L).getPickup().getPhone()); verify(fixture.logMapper, never()).selectList(any()); ``` Tests must also capture SQL segments for all five tab mappings, reject `refund`/unknown tabs, reject half/malformed coordinates, keep scheduled orders hidden before their start, restrict non-new tabs to `rider_id`, and verify direct photo arrays grouped by `SENDER/PICKUP/DELIVERY`. - [ ] **Step 2: Run service and controller tests and verify failure** ```powershell mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryApplicationServiceTest,FlashDeliveryControllerContractTest,FlashDeliveryOrderViewTest,FlashDeliveryRiderOrderListViewTest' -Dsurefire.failIfNoSpecifiedTests=false test ``` Expected: failure on old method signatures, routes, scene filtering and wrapped detail response. - [ ] **Step 3: Implement controllers and query mapping** ```java @GetMapping("/orders") public AjaxResult orders(@RequestHeader String token, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(defaultValue = "newTask") String tab, @RequestParam(required = false) BigDecimal longitude, @RequestParam(required = false) BigDecimal latitude) { return success(service.riderOrders(userId(token), page, size, tab, longitude, latitude)); } ``` User Controller uses only `page,size`. Service maps `newTask/toPickup/delivering/completed/cancelled` before `selectPage`; `newTask` applies `WAITING_ACCEPTANCE`、`rider_id IS NULL`、预约开始时间条件 and, when coordinates exist, the same `sys_qs_newtask_distance` radius and distance sorting used by `PosOrderQsOprateController.orderList`. - [ ] **Step 4: Implement list mapping and direct detail** ```java private FlashDeliveryOrderDetailView participantDetail(FlashDeliveryOrder order, boolean userView, boolean protectedRiderView) { FlashDeliveryOrderDetailView view = detailOrderView(order, !protectedRiderView); if (!protectedRiderView) groupImageUrls(view, selectImages(order.getId())); if (userView) { view.setDeliveryPinCode(Boolean.TRUE.equals(order.getPinRequired()) ? order.getDeliveryPinCode() : null); view.setRider(riderSummary(order)); } return view; } ``` Do not query `FlashDeliveryOrderLogMapper` while building App details. Pre-accept address views copy `city,area,handoffMethod,address,addressDetail` only; assigned rider/user detail additionally copies contact and exact coordinates. `newTask` page summary uses filtered page total as `nearbyTaskCount` and a same-condition maximum amount query as `highestOrderAmount`. - [ ] **Step 5: Add localized parameter errors and run targeted tests** Add `flash.delivery.tab.invalid` and `flash.delivery.coordinates.invalid` to all five message bundles, then run: ```powershell mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryApplicationServiceTest,FlashDeliveryControllerContractTest,FlashDeliveryOrderViewTest,FlashDeliveryRiderOrderListViewTest' -Dsurefire.failIfNoSpecifiedTests=false test ``` Expected: PASS. ### Task 3: 更新契约文档并统一验证 **Files:** - Modify: `docs/flash-delivery-app-api.md` - Modify: `specs/024-flash-delivery/quickstart.md` - Modify: `specs/024-flash-delivery/tasks.md` **Interfaces:** - Consumes: the implemented Controller routes and DTO field names from Tasks 1–2. - Produces: App integration examples that exactly match production code. - [ ] **Step 1: Replace obsolete request and response examples** Document only: ```text GET /system/flashDelivery/orders?page=1&size=10 GET /system/flashDelivery/rider/orders?page=1&size=10&tab=newTask&longitude=121.5&latitude=25.0 ``` Remove `/available`、`/mine`、`scene`、`serviceType` list filters, approximate coordinates, `{order,images,logs}` App wrappers and App raw log examples. Keep platform audit response unchanged. - [ ] **Step 2: Run all affected tests and package** ```powershell $env:JAVA_HOME='C:\Users\qmj\.jdks\graalvm-jdk-21.0.7' $env:PATH="$env:JAVA_HOME\bin;$env:PATH" mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryApplicationServiceTest,FlashDeliveryControllerContractTest,FlashDeliveryOrderViewTest,FlashDeliveryRiderOrderListViewTest,FlashDeliveryI18nContractTest' -Dsurefire.failIfNoSpecifiedTests=false test mvn -pl ruoyi-admin -am -DskipTests package git diff --check ``` Expected: all targeted tests pass, package succeeds and diff check is clean. - [ ] **Step 3: Audit scope and commit** ```powershell git diff --name-only $flashFiles = @( 'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryUserController.java', 'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryRiderController.java', 'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationService.java', 'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryUserOrderListView.java', 'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderListView.java', 'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderPageView.java', 'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryOrderDetailView.java', 'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryAvailableOrderView.java', 'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryImageView.java', 'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryLogView.java', 'ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryControllerContractTest.java', 'ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryAvailableOrderViewTest.java', 'ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderListViewTest.java', 'ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryOrderViewTest.java', 'ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationServiceTest.java', 'ruoyi-admin/src/main/resources/i18n/messages.properties', 'ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties', 'ruoyi-admin/src/main/resources/i18n/messages_zh_TW.properties', 'ruoyi-admin/src/main/resources/i18n/messages_en_US.properties', 'ruoyi-admin/src/main/resources/i18n/messages_vi.properties', 'docs/flash-delivery-app-api.md', 'specs/024-flash-delivery/quickstart.md', 'specs/024-flash-delivery/tasks.md' ) git add -- $flashFiles git diff --cached --check git diff --cached --name-only git commit -m "精简闪送 App 订单接口" git push origin test ``` The staged list must not contain `.claude/homunculus/observations.jsonl`、`.tmp/` or `docs/merchant-subaccount-api.md`.