# 闪送配送服务设计记录 本文件记录 2026-08-31 已经逐项确认的设计决策,作为后续 `plan.md`、`tasks.md` 和接口契约的输入。 ## 1. 设计来源 蓝湖“闪送”分组共 7 个页面,均已通过蓝湖 MCP 读取图片、设计标注、颜色与图层: 1. 地址薄 2. 闪送 取 3. 首页202608 4. 填写收件信息 5. 闪送 加急 6. 闪送 7. 闪送 送 最初的蓝湖 7 页明确展示三种服务、取件与收件地址、联系人和电话、地址簿、起步价格及下单入口,未展示重量和类别;2026-08-24 用户端/骑手端补充原型进一步明确包裹类别、重量档、预约配送、交付 PIN、寄件图片和费用明细。两套设计均未明确支付、代购、精确重量或件数规则。 ## 2. 架构决策 采用独立闪送订单模块,不复用 `pos_order` 或 `taxi_order`。 ```text 用户端 / 骑手端 / 平台端 | v ruoyi-admin - 用户、骑手、平台 Controller - 报价编排与地图路线适配器 - 闪送状态流转与自动完成任务 - 通知编排 | v ruoyi-system - 闪送领域实体、Mapper、Service - 计价配置、配送凭证、状态日志 - 共享地址所有权数据访问 ``` 外部地图 HTTP 集成位于 `ruoyi-admin`,保持 `ruoyi-admin -> ruoyi-system` 的模块依赖方向。SQL 只写入 `updatesql/sql.md`,由开发者手动执行。 ## 3. 接口边界 ### 3.1 用户端 - `GET /system/flashDelivery/home` - `POST /system/flashDelivery/quote` - `POST /system/flashDelivery/orders` - `GET /system/flashDelivery/orders` - `GET /system/flashDelivery/orders/{id}` - `POST /system/flashDelivery/orders/{id}/cancel` - `POST /system/flashDelivery/orders/{id}/confirmReceipt` ### 3.2 骑手端 - `GET /system/flashDelivery/rider/orders` - `GET /system/flashDelivery/rider/orders/{id}` - `POST /system/flashDelivery/rider/orders/{id}/accept` - `POST /system/flashDelivery/rider/orders/{id}/pickup` - `POST /system/flashDelivery/rider/orders/{id}/deliver` ### 3.3 平台端 - `GET /system/flashDelivery/admin/pricing` - `POST /system/flashDelivery/admin/pricing` - `PUT /system/flashDelivery/admin/pricing/{id}` - `DELETE /system/flashDelivery/admin/pricing/{id}` - `GET /system/flashDelivery/admin/orders` - `GET /system/flashDelivery/admin/orders/{id}` - `POST /system/flashDelivery/admin/orders/{id}/cancel` - `POST /system/flashDelivery/admin/orders/{id}/complete` 平台管理前端沿用 `foodie-admin-vue` 的 Vue 2、Element UI、动态菜单和权限指令,不额外引入 UI 或状态管理依赖: - “闪送管理”作为父菜单,“价格配置”和“闪送订单”作为两个子页面。 - `src/api/flashDelivery/index.js` 集中封装平台接口,页面不直接拼接请求。 - 价格配置页按帮送、帮取和加急送展示运价时段,新增与编辑弹窗承载时间、距离和整数金额校验,支持删除不再需要的时段;配置保存即生效,不提供启停开关。 - 订单页以服务端分页表格为主体,详情弹窗分区展示概况、地址、费用、凭证和日志;取消原因使用独立输入弹窗,完成操作使用二次确认。 - 操作按钮同时使用状态条件和 `v-hasPermi` 控制可见性,后端权限与状态机仍是最终安全边界。 - 所有新增文本集中在四个前端语言文件的 `flashDelivery` 对象中,key 集合保持一致。 ### 3.4 共享地址簿 - `GET /system/address/getaddress` - `GET /system/address/getaddressxq` - `POST /system/address/address` - `DELETE /system/address/{id}` - `POST /system/address/{id}/top` 地址接口继续由现有收货与闪送场景共用,但所有用户接口都必须携带 token;后端按当前登录用户限制详情、保存、删除和置顶范围。 ## 4. 数据结构方向 ### 4.1 `flash_delivery_order` 保存订单号、客户端请求号、用户、骑手、服务类型、包裹类别、重量档、配送方式、预约时段、交付 PIN、取件与收件信息快照、路线距离、距离来源、预计时长、报价金额、匹配时段的起送与里程计价快照、币种、计价配置版本、备注、状态、并发版本号、关键操作时间与取消信息。 ### 4.2 `flash_delivery_pricing` 按服务类型保存多个不重叠的运价时段;每个时段保存开始时间、结束时间、起送距离、起送价格、计价距离、计价金额、配置版本、修改人和修改时间。保存即生效,删除即失效,不设置启停字段。 ### 4.3 `flash_delivery_order_image` 保存订单、凭证类型、图片 URL、排序、操作人类型、操作人和创建时间。凭证类型包括 `SENDER`、`PICKUP` 与 `DELIVERY`。 ### 4.4 `flash_delivery_order_log` 保存变更前状态、变更后状态、操作人类型、操作人 ID、原因和时间。 ### 4.5 `info_address` 增加置顶标记和置顶时间;列表按置顶时间与既有排序规则返回。 ## 5. 计价与距离 ```text 当前配置 = 按服务类型和当前时间匹配唯一运价时段 超出公里 = max(0, 路线距离公里 - 起送距离) 计费公里 = 超出公里 < 0.5 ? 0 : 超出公里 < 1 ? 1 : 超出公里 里程费用 = 计费公里 * (计价金额 / 计价距离) 价格 = 起送价格 + 四舍五入到整数元的里程费用 ``` 计价规则与现有外卖订单一致,但闪送使用自己的时段配置表。起送价格和计价金额为正整数新台币;距离与除法过程使用十进制定点数,里程费用采用 `HALF_UP` 四舍五入到整数元,最终价格不显示小数,也不执行外卖旧实现中金额达到 1000 后的千位特殊取整。帮送、帮取和加急送分别拥有多个互不重叠的时段;当前时间没有匹配时段时拒绝报价和创建。路线预计时长继续返回和保存,仅用于履约展示,不参与计价。报价优先使用地图路线距离,失败时降级为直线距离;创建订单时服务端必须重新匹配时段、计算价格并固化配置快照。 ## 6. 状态机 ```text WAITING_ACCEPTANCE | | 骑手原子抢单 v ACCEPTED | | 订单骑手 + 取件图片 v PICKED_UP | | 订单骑手 + 送达图片 v DELIVERED | | 用户确认 / 24小时自动完成 / 平台完成 v COMPLETED ``` `WAITING_ACCEPTANCE` 和 `ACCEPTED` 允许订单用户取消。`PICKED_UP` 和 `DELIVERED` 只有平台能够介入取消。任一允许取消状态进入 `CANCELLED`。`COMPLETED` 与 `CANCELLED` 为终态。 ## 7. 安全与隐私 - 用户、骑手身份只从请求头 token 解析,不从请求体读取。 - 用户订单查询和操作按 `order_id + user_id` 校验。 - 骑手履约操作按 `order_id + rider_id + expected_status` 校验。 - 骑手抢单使用数据库条件更新,避免双抢。 - 待抢订单按原型显示完整取送文字地址,但隐藏联系人、电话、实际 PIN、精确坐标和内部字段;抢单后才向订单骑手展示完整履约信息。 - 地址操作按 `address_id + user_id` 校验,客户端 `userId` 不参与归属判断。 - Controller 使用明确 DTO、`@RequestBody`、`@RequestParam`、`@PathVariable` 和 `@RequestHeader`,不接收 Map。 - 地图密钥不返回客户端、不写日志;业务错误使用国际化消息。 ## 8. 测试方向 - 时段匹配、重叠拒绝、0.5 公里边界、整数金额取整和配置版本测试。 - 地图路线成功、失败及直线降级测试。 - 地址和订单跨用户访问测试。 - 多骑手并发抢单测试。 - 取件与送达图片必填测试。 - 全部允许和禁止状态流转测试。 - 24 小时自动完成与并发幂等测试。 - 平台权限和配置校验测试。 - MyBatis XML 字段、条件更新与索引契约测试。 - 平台前端 API 路径、方法、参数和权限字符串契约测试。 - 平台前端价格校验、状态操作可见性和四语言 key 一致性测试。 - 平台前端 ESLint、生产构建和常用后台宽度的页面检查。 ## 9. 明确延期内容 支付、退款、分账、骑手收入、代购、垫付、商品金额、精确重量、件数、自动派单、动态附近骑手数、预计接单时间以及用户端、骑手端和商家端页面均不在本期范围。 ## 10. App 订单列表精简增量(2026-09-01) ### 10.1 设计选择 采用单一骑手列表接口,查询参数与现有骑手外卖订单列表保持一致。该方案让 App 的两类配送列表共享 `page、size、tab、longitude、latitude` 的调用方式,同时由服务端完成页签到状态的映射。原 `/available` 与 `/mine` 拆分接口、`scene` 与 `serviceType` 组合筛选不再保留。 没有采用“保留两个接口只改参数名”,因为 App 仍需维护两套请求和响应;也没有采用“列表继续返回完整详情对象”,因为列表页面只需要卡片字段,完整订单信息已有独立详情接口。 ### 10.2 用户列表 - `GET /system/flashDelivery/orders?page=1&size=10` - 只查询当前用户,按创建时间倒序混合返回各状态订单。 - 列表不接受 `status`、`scene` 或 `serviceType`,页面无需为了简单卡片组装筛选条件。 - 使用用户列表摘要 DTO,只包含卡片展示所需的订单标识、服务/包裹、状态、取送文字地址、配送时段、预计时长、金额和关键展示时间;完整联系方式、精确坐标和照片由详情接口按明确字段返回,原始状态日志不向 App 返回。 ### 10.3 骑手列表 - `GET /system/flashDelivery/rider/orders?page=1&size=10&tab=newTask&longitude=121.5&latitude=25.0` - 参数名、默认分页和坐标用途与骑手外卖订单列表保持一致;经纬度用于 `newTask` 的附近范围、取件点距离和排序,其他页签忽略坐标。 - `newTask -> WAITING_ACCEPTANCE`;`toPickup -> ACCEPTED`;`delivering -> PICKED_UP`;`completed -> DELIVERED + COMPLETED`;`cancelled -> CANCELLED`。除 `newTask` 外均只查询当前骑手本人。 - 闪送没有退款流程,因此不接受外卖列表的 `refund` 页签。 - 列表使用统一卡片摘要 DTO,包含订单标识、服务类型、状态、包裹类别/重量档、配送方式/预约时段、是否需要 PIN、完整取送文字地址、取件点距离、路线距离、预计时长、订单金额和创建时间。 - `newTask` 分页响应额外返回附近任务总数与最高订单金额,用于原型顶部摘要;不返回已移除的尖峰倍率,也不将订单金额表述为尚未实现的骑手收入。 - 接单前不返回联系人、电话、实际 PIN、精确坐标、用户备注、图片凭证、状态日志、用户 ID、幂等号或计价审计字段。接单成功后,详情接口仅向中单骑手返回完整履约信息。 ### 10.4 App 详情响应 - 用户创建订单、用户订单详情、骑手订单详情和骑手接单成功响应的 `data` 直接返回订单详情字段,不再使用 `{order,images,logs}` 包装。 - 寄件、取件和送达照片分别使用 `senderImageUrls`、`pickupImageUrls`、`deliveryImageUrls`,避免 App 再按通用图片记录的类型分组。 - App 详情不返回原始状态日志;页面使用 `status`、`acceptedAt`、`pickedUpAt`、`deliveredAt`、`completedAt` 和 `cancelledAt` 展示进度。平台详情继续保留 `{order,images,logs}` 供审计。 - 骑手接单前后使用同一个详情 DTO:接单前隐藏联系人、电话、实际 PIN、精确坐标和履约凭证,接单后只向中单骑手补齐可见字段,`data` 顶层形状保持不变。 ### 10.5 兼容和错误处理 - App 与后端同步切换到新接口,不保留测试阶段旧 `/available`、`/mine` 路由或旧参数兼容层。 - `page`、`size` 或 `tab` 非法时返回国际化业务错误;坐标必须成对出现并通过经纬度范围校验。 - 列表必须先按页签和角色过滤,再由数据库分页;禁止查出完整集合后在 Java 或客户端过滤。