Selaa lähdekoodia

记录闪送配送服务规格

qmj 2 tuntia alkaen nyt
vanhempi
sitoutus
33b3f0fb27

+ 1 - 1
.specify/feature.json

@@ -1,3 +1,3 @@
 {
-  "feature_directory": "specs/022-merchant-store-subaccounts"
+  "feature_directory": "specs/024-flash-delivery"
 }

+ 50 - 0
specs/024-flash-delivery/checklists/requirements.md

@@ -0,0 +1,50 @@
+# 规格质量检查清单:闪送配送服务
+
+**目的**:在进入计划阶段前验证规格完整性、清晰度与可验收性。
+
+**创建日期**:2026-08-31
+
+**规格**:[spec.md](../spec.md)
+
+## 内容质量
+
+- [x] 需求范围基于蓝湖“闪送”分组全部 7 个设计页面
+- [x] 已区分设计稿明确内容与后端闭环补充规则
+- [x] 用户、骑手和平台三类角色边界清晰
+- [x] 所有必填章节均已完成
+- [x] 不包含 `TBD`、`TODO` 或未解决的 `[NEEDS CLARIFICATION]`
+
+## 需求完整性
+
+- [x] 三种服务类型和帮取非代购语义已明确
+- [x] 计价公式、配置版本和距离降级规则已明确
+- [x] 用户创建幂等和服务端重新报价已明确
+- [x] 骑手主动抢单与并发唯一接单规则已明确
+- [x] 取件、送达图片必填规则已明确
+- [x] 用户取消、平台介入、签收和自动完成规则已明确
+- [x] 地址簿共用与所有权校验规则已明确
+- [x] 待抢订单隐私字段展示边界已明确
+- [x] 平台配置、订单查询和介入能力已明确
+- [x] 支付、物品信息和自动派单等非目标已明确
+
+## 可测试性
+
+- [x] 每个用户故事都有独立测试方式
+- [x] 正常流程、失败流程、越权流程和并发流程均有验收场景
+- [x] 成功标准可通过自动化测试或数据断言验证
+- [x] 终态不可逆和状态日志审计要求可验证
+- [x] 地图降级、自动完成和配置版本变化均可验证
+
+## 项目约束
+
+- [x] 独立闪送模块不复用废弃或语义不符的订单模型
+- [x] 保持 `ruoyi-admin -> ruoyi-system` 依赖方向
+- [x] Controller token、DTO 和参数注解规则已记录
+- [x] 数据库迁移只写入 `updatesql/sql.md`
+- [x] 国际化错误消息和敏感信息保护要求已记录
+
+## 结论
+
+- [x] 规格具备进入用户书面审核的条件
+- [x] 用户已授权不等待书面规格审核并连续推进
+- [ ] 已进入 `plan.md` 和 `tasks.md` 阶段

+ 166 - 0
specs/024-flash-delivery/design.md

@@ -0,0 +1,166 @@
+# 闪送配送服务设计记录
+
+本文件记录 2026-08-31 已经逐项确认的设计决策,作为后续 `plan.md`、`tasks.md` 和接口契约的输入。
+
+## 1. 设计来源
+
+蓝湖“闪送”分组共 7 个页面,均已通过蓝湖 MCP 读取图片、设计标注、颜色与图层:
+
+1. 地址薄
+2. 闪送 取
+3. 首页202608
+4. 填写收件信息
+5. 闪送 加急
+6. 闪送
+7. 闪送 送
+
+设计稿明确展示三种服务、取件与收件地址、联系人和电话、地址簿、起步价格及下单入口;未展示重量、类别、件数、支付或代购信息。
+
+## 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/available`
+- `GET /system/flashDelivery/rider/orders/mine`
+- `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`
+- `PUT /system/flashDelivery/admin/pricing/{serviceType}`
+- `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`
+
+### 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`
+
+保存订单号、客户端请求号、用户、骑手、服务类型、取件与收件信息快照、路线距离、距离来源、预计时长、报价金额、币种、计价配置版本、备注、状态、并发版本号、关键操作时间与取消信息。
+
+### 4.2 `flash_delivery_pricing`
+
+按服务类型保存起步价、起步距离、每公里价格、启用状态、配置版本、修改人和修改时间。
+
+### 4.3 `flash_delivery_order_image`
+
+保存订单、凭证类型、图片 URL、排序和创建时间。凭证类型仅包括 `PICKUP` 与 `DELIVERY`。
+
+### 4.4 `flash_delivery_order_log`
+
+保存变更前状态、变更后状态、操作人类型、操作人 ID、原因和时间。
+
+### 4.5 `info_address`
+
+增加置顶标记和置顶时间;列表按置顶时间与既有排序规则返回。
+
+## 5. 计价与距离
+
+```text
+距离 <= 起步距离:
+  价格 = 起步价
+
+距离 > 起步距离:
+  价格 = 起步价
+       + ceil((距离 - 起步距离) / 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` 校验。
+- 骑手抢单使用数据库条件更新,避免双抢。
+- 待抢订单隐藏完整电话与详细门牌;抢单后才向订单骑手展示。
+- 地址操作按 `address_id + user_id` 校验,客户端 `userId` 不参与归属判断。
+- Controller 使用明确 DTO、`@RequestBody`、`@RequestParam`、`@PathVariable` 和 `@RequestHeader`,不接收 Map。
+- 地图密钥不返回客户端、不写日志;业务错误使用国际化消息。
+
+## 8. 测试方向
+
+- 计价边界和配置版本测试。
+- 地图路线成功、失败及直线降级测试。
+- 地址和订单跨用户访问测试。
+- 多骑手并发抢单测试。
+- 取件与送达图片必填测试。
+- 全部允许和禁止状态流转测试。
+- 24 小时自动完成与并发幂等测试。
+- 平台权限和配置校验测试。
+- MyBatis XML 字段、条件更新与索引契约测试。
+
+## 9. 明确延期内容
+
+支付、退款、分账、代购、垫付、商品信息、重量、自动派单、动态附近骑手数和预计接单时间均不在本期范围。

+ 195 - 0
specs/024-flash-delivery/spec.md

@@ -0,0 +1,195 @@
+# 功能规格:闪送配送服务
+
+**功能标识**:`024-flash-delivery`
+
+**创建日期**:2026-08-31
+
+**状态**:草稿,等待书面规格审核
+
+**输入**:基于蓝湖“闪送”分组 7 个设计页面,实现帮送、帮取、加急送的完整非支付业务闭环,包括共享地址簿、路线报价、创建订单、骑手主动抢单、取件、送达、用户签收、平台配置与介入。
+
+## 用户场景与测试
+
+### 用户故事 1:用户获取报价并发布闪送订单(优先级:P1)
+
+用户选择帮送、帮取或加急送,填写取件与收件地址后获取服务端报价,并发布一笔等待骑手接单的闪送订单。
+
+**优先级原因**:报价与发布订单是闪送服务成立的基础,没有该能力就无法形成可配送任务。
+
+**独立测试**:为同一组取件和收件坐标分别选择三种服务,验证系统按当前启用配置返回报价,并在创建订单时重新计算价格、保存地址与计价快照且进入待接单状态。
+
+**验收场景**:
+
+1. **假如** 路线距离未超过起步距离,**当** 用户请求报价,**那么** 系统返回对应服务类型的起步价。
+2. **假如** 路线距离超过起步距离但不足下一个完整公里,**当** 用户请求报价,**那么** 超出部分按一个完整续程公里计价。
+3. **假如** 地图路线服务正常,**当** 用户请求报价,**那么** 系统使用路线距离并标记距离来源为路线服务。
+4. **假如** 地图路线服务超时或失败,**当** 用户请求报价,**那么** 系统使用经纬度直线距离降级报价并明确标记距离来源。
+5. **假如** 客户端提交伪造金额或距离,**当** 用户创建订单,**那么** 系统忽略客户端金额与距离并在服务端重新报价。
+6. **假如** 同一用户以相同客户端请求号重复创建订单,**当** 请求被重复处理,**那么** 系统返回同一订单且不产生重复订单。
+
+---
+
+### 用户故事 2:骑手从闪送列表主动抢单并完成配送(优先级:P1)
+
+骑手在闪送待接单列表中查看可抢订单,主动抢单后依次上传取件和送达图片,完成实际配送。
+
+**优先级原因**:第一版明确采用骑手主动抢单,不实现自动派单;抢单与配送状态流转是业务闭环的核心。
+
+**独立测试**:创建一笔待接单订单,由两个骑手并发抢单,验证只有一个骑手成功;成功骑手上传取件和送达图片后,订单依次进入已取件和已送达状态。
+
+**验收场景**:
+
+1. **假如** 同时存在普通和加急待接订单,**当** 骑手查询闪送列表,**那么** 加急订单优先展示,其余订单按发布时间排序。
+2. **假如** 两名骑手同时抢同一订单,**当** 两个请求并发到达,**那么** 只有一名骑手成功,另一名收到订单已被接走的业务结果。
+3. **假如** 骑手尚未抢到订单,**当** 其尝试确认取件或送达,**那么** 系统拒绝操作且不泄露完整联系方式。
+4. **假如** 已接单骑手未提交取件图片,**当** 其确认取件,**那么** 系统拒绝状态变更。
+5. **假如** 已取件骑手未提交送达图片,**当** 其确认送达,**那么** 系统拒绝状态变更。
+6. **假如** 图片要求已满足,**当** 订单骑手按顺序确认取件和送达,**那么** 系统保存凭证、操作人和时间并完成对应状态变更。
+
+---
+
+### 用户故事 3:用户安全管理共享地址簿(优先级:P1)
+
+用户在闪送与现有收货场景中共用同一个地址簿,可以搜索、新增、修改、查看、删除和置顶自己的地址。
+
+**优先级原因**:蓝湖设计中的取件地址、收件地址和地址簿均依赖该能力,同时现有地址接口需要消除跨用户访问风险。
+
+**独立测试**:准备用户 A 与用户 B 的地址,以用户 A 身份执行列表、详情、修改、删除和置顶操作,验证只能访问 A 的地址,所有针对 B 地址的请求均被拒绝。
+
+**验收场景**:
+
+1. **假如** 用户拥有多条地址,**当** 用户按姓名、电话或地址关键词搜索,**那么** 系统只返回该用户自己的匹配地址。
+2. **假如** 用户置顶一条地址,**当** 再次查询地址列表,**那么** 该地址排在当前用户其他地址之前。
+3. **假如** 用户提交其他用户的地址 ID,**当** 其尝试查看、修改、删除或置顶,**那么** 系统拒绝操作且不返回目标地址内容。
+4. **假如** 客户端在新增或修改地址时提交 `userId`,**当** 系统保存地址,**那么** 系统忽略该字段并以登录用户身份确定归属。
+
+---
+
+### 用户故事 4:用户跟踪、取消和签收自己的订单(优先级:P1)
+
+用户查看自己的闪送订单与状态日志,可以在允许阶段取消订单,并在骑手送达后确认签收。
+
+**优先级原因**:用户需要了解履约进度,并对尚未实际取件的订单保留取消能力。
+
+**独立测试**:分别创建处于待接单、已接单、已取件和已送达状态的订单,验证用户取消和签收权限符合状态机规则,并验证送达 24 小时后的自动完成。
+
+**验收场景**:
+
+1. **假如** 订单处于待接单或已接单,**当** 订单用户取消,**那么** 订单进入已取消状态并记录原因。
+2. **假如** 订单已取件,**当** 订单用户尝试取消,**那么** 系统拒绝并要求平台介入。
+3. **假如** 骑手已确认送达,**当** 订单用户确认签收,**那么** 订单进入已完成状态。
+4. **假如** 骑手送达后用户 24 小时未确认,**当** 自动完成任务执行,**那么** 订单进入已完成状态并记录自动操作日志。
+5. **假如** 用户访问其他用户的订单,**当** 其查询详情、取消或签收,**那么** 系统拒绝访问。
+
+---
+
+### 用户故事 5:平台管理计价配置并介入异常订单(优先级:P2)
+
+平台管理员维护三种闪送服务的计价配置,查询全部闪送订单,并在取件后的异常场景中取消或完成订单。
+
+**优先级原因**:价格必须可运营调整,取件后的订单又必须具备受控的人工处置入口。
+
+**独立测试**:管理员修改加急送配置后创建新报价,验证新报价使用新版本而历史订单保持原快照;随后对已取件订单执行平台取消并检查状态日志。
+
+**验收场景**:
+
+1. **假如** 管理员修改某一服务类型的计价配置,**当** 新请求报价,**那么** 新报价使用新配置且已创建订单价格不变。
+2. **假如** 配置值为负数、零或服务类型非法,**当** 管理员提交修改,**那么** 系统拒绝保存。
+3. **假如** 普通用户或骑手调用平台配置或介入接口,**当** 权限校验执行,**那么** 系统拒绝操作。
+4. **假如** 订单已取件但发生异常,**当** 有权限的管理员取消订单,**那么** 系统允许取消并记录管理员、原因和时间。
+5. **假如** 订单已送达但用户无法确认,**当** 有权限的管理员确认完成,**那么** 系统完成订单并记录平台操作日志。
+
+## 边界情况
+
+- 取件地址和收件地址相同或坐标相同,不允许创建订单。
+- 地址缺少联系人、联系电话、完整地址或有效经纬度时,不允许用于报价和创建订单。
+- 经纬度超出合法范围时拒绝请求,不调用地图服务。
+- 对应服务类型没有启用计价配置时,不允许报价或创建订单。
+- 地图路线结果缺少有效距离时按路线失败处理并降级为直线距离。
+- 创建订单时当前配置与先前报价时不同,以创建时服务端重新计算结果为准。
+- 待接单列表仅返回待接单订单;已取消或已被抢走的订单不得继续出现在新查询结果中。
+- 待抢订单列表隐藏完整电话和门牌信息;骑手抢单成功后才能读取配送所需的完整快照。
+- 骑手第一版不能自行放弃已抢订单,需要平台介入处理。
+- 已完成和已取消为终态,不允许再次变更。
+- 自动完成任务与用户确认、平台完成并发时,只允许一个状态变更成功且不得重复写入完成副作用。
+
+## 需求
+
+### 功能需求
+
+- **FR-001**:系统必须提供帮送、帮取和加急送三种服务类型。
+- **FR-002**:帮取只承担取件配送,不包含代购、垫付或商品金额。
+- **FR-003**:三种服务必须使用同一订单履约状态机;加急送仅在排序和计价上体现优先级。
+- **FR-004**:系统必须按服务类型维护起步价、起步距离和每公里价格,并保存配置版本与修改记录。
+- **FR-005**:距离不超过起步距离时必须按起步价计费。
+- **FR-006**:距离超过起步距离时,超出部分必须按每开始一公里向上取整计费,最终金额保留两位小数。
+- **FR-007**:系统必须优先使用地图路线距离,地图失败时使用经纬度直线距离并向客户端返回距离来源。
+- **FR-008**:地图密钥必须从服务端配置读取,不得返回客户端或写入业务日志。
+- **FR-009**:创建订单时必须由服务端重新计算距离和价格,并保存地址、路线、金额和计价配置快照。
+- **FR-010**:系统必须使用客户端请求号保证同一用户的订单创建幂等。
+- **FR-011**:闪送订单不得复用餐饮订单或打车订单数据模型。
+- **FR-012**:第一阶段订单不包含支付、退款、物品重量、物品类别、件数和禁寄品确认。
+- **FR-013**:订单必须支持用户备注,但备注不参与计价。
+- **FR-014**:骑手必须通过独立闪送待接单列表主动抢单,第一阶段不实现自动派单。
+- **FR-015**:待接单列表必须按加急优先、创建时间次优先的顺序返回。
+- **FR-016**:骑手抢单必须使用原子条件更新,确保同一订单最多由一名骑手抢到。
+- **FR-017**:订单状态必须包括待接单、已接单、已取件、已送达、已完成和已取消。
+- **FR-018**:只有抢到订单的骑手能够确认取件和送达。
+- **FR-019**:确认取件必须至少提供一张取件图片;确认送达必须至少提供一张送达图片。
+- **FR-020**:系统必须保存取件和送达图片 URL、凭证类型、操作骑手和操作时间。
+- **FR-021**:用户只能在待接单或已接单状态取消自己的订单。
+- **FR-022**:订单已取件后,用户和骑手均不能自行取消,只有有权限的平台管理员能够介入取消。
+- **FR-023**:骑手确认送达后,用户可以确认签收并完成订单。
+- **FR-024**:送达后 24 小时未确认的订单必须自动完成,并留下可审计日志。
+- **FR-025**:平台管理员能够查询全部闪送订单及日志,并在允许状态执行取消或完成操作。
+- **FR-026**:每次有效状态变更必须记录变更前状态、变更后状态、操作人类型、操作人 ID、原因和时间。
+- **FR-027**:所有状态变更必须校验当前状态、业务归属和操作角色,并防止并发覆盖。
+- **FR-028**:用户只能查询、取消和签收自己的闪送订单。
+- **FR-029**:骑手在抢单前只能看到履约判断所需的脱敏信息,抢单成功后才能查看完整联系方式和门牌信息。
+- **FR-030**:闪送必须与现有收货场景共用 `info_address` 地址数据。
+- **FR-031**:统一地址接口必须要求登录身份,并对详情、修改、删除和置顶执行地址所有权校验。
+- **FR-032**:地址列表必须支持按当前用户的姓名、电话和地址关键词搜索,并支持置顶排序。
+- **FR-033**:地址保存接口必须忽略客户端提供的用户 ID,以当前登录用户确定地址归属。
+- **FR-034**:所有用户端和骑手端接口必须直接读取请求头 token;不得接受客户端提交的用户或骑手身份。
+- **FR-035**:所有平台接口必须使用后台权限校验,并为配置查询、配置修改、订单查询和订单介入设置明确权限。
+- **FR-036**:所有请求必须使用明确 DTO 或显式查询参数,Controller 不得使用 Map 接收请求。
+- **FR-037**:所有业务校验错误必须通过项目国际化消息机制返回,不得硬编码单一语言错误。
+- **FR-038**:系统必须复用现有上传能力;闪送接口仅接收上传完成后的图片 URL。
+- **FR-039**:本功能的数据库变更只能记录在 `updatesql/sql.md`,不得直接执行。
+
+### 关键实体
+
+- **闪送订单**:用户发布的取件配送任务,持有地址、路线、价格、服务类型、履约状态和关键时间快照。
+- **计价配置**:某一服务类型当前启用的起步价、起步距离、每公里价格及版本信息。
+- **配送凭证**:骑手在取件或送达时提交的图片记录。
+- **订单状态日志**:记录订单每次有效状态变化的操作审计信息。
+- **共享地址**:属于单一用户、可供闪送和现有收货业务共同使用的联系人与地点信息。
+
+## 成功标准
+
+- **SC-001**:三种服务的起步距离、超距不足一公里、超距整公里和加急配置报价测试全部通过。
+- **SC-002**:地图路线成功和失败降级场景均能在一次请求内返回可用报价,并准确标记距离来源。
+- **SC-003**:至少 20 个并发抢单请求针对同一订单时,数据库中恰好只有一名骑手成功绑定。
+- **SC-004**:跨用户地址和订单的详情、修改、删除、置顶、取消及签收测试拦截率达到 100%。
+- **SC-005**:缺少取件或送达图片的状态变更请求拦截率达到 100%。
+- **SC-006**:所有允许和禁止的状态流转均有自动化测试覆盖,终态订单不能再次改变状态。
+- **SC-007**:送达超过 24 小时的订单能够自动完成,且并发确认不会产生重复状态日志或重复副作用。
+- **SC-008**:修改计价配置后,新订单使用新版本,历史订单保存的价格与配置快照保持不变。
+- **SC-009**:所有新增用户可见业务错误均能通过项目支持的语言资源解析,不出现硬编码单一语言结果。
+
+## 假设
+
+- 继续使用现有用户、骑手、token、骑手位置、图片上传和站内消息/推送基础设施。
+- 地图路线服务的具体供应商与密钥沿用项目现有配置能力;计划阶段确定现有配置键和 HTTP 调用位置。
+- 币种为新台币,接口使用稳定币种代码 `TWD`。
+- 第一阶段不根据有效骑手数量阻止下单,也不实现自动匹配、自动派单或定向派单。
+- 蓝湖页面中的附近骑手数量与预计接单时间第一阶段不作为动态业务承诺。
+- 骑手第一阶段不能自行放弃已抢订单,由平台管理员介入处理异常订单。
+
+## 非目标
+
+- 不接入支付网关、付款、退款、结算或骑手收入分账。
+- 不实现代购、垫付款、商品金额、物品类别、重量、件数或禁寄品确认。
+- 不实现自动派单、抢单推送筛选、骑手在线状态计算或接单时间预测。
+- 不修改餐饮订单 `pos_order` 或打车订单 `taxi_order` 的业务语义。
+- 不在本功能中实现前端页面;本轮交付后端 API、数据库 SQL、自动化测试和 spec-kit 文档。