docs(changelog): order-v3 订单取消同事务失活用车需求,已取消户不再进 fleet 待配车池(#7964)
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
这个提交包含在:
@@ -0,0 +1,121 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7964"
|
||||
title: "订单取消同事务失活用车需求,已取消户不再出现在 fleet 待配车池(无字段变更)"
|
||||
consumer: "internal"
|
||||
author: "jw(GIT)"
|
||||
change_type: "修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "订单取消链路此前只给 fleet 发「取消派车」命令、不碰 order 侧需求行,已取消/已退团户的 order_vehicle_requirement 仍是 is_active=1 / status=PENDING,继续出现在待配车池里——车管会给一个已经退团的客人排车。现改为在订单取消事务内同步失活该单全部活跃用车需求(两个 kind、任意 status 一并失活)。没有新增、修改、删除任何端点,请求与响应字段全不变,故 change_type 取「修复」。⚠️ 对 fleet 侧有两处行为影响,需知会:①待配车池 GET /v3/internal/order/vehicle-requirements 的返回集合变小;②fleet 对已取消单的三个回写入口(status / reopen-after-assignment-cancel / vehicle-assignment/callback)行为经本单专门保护,仍是原先的「静默跳过 / ORDER_CANCELLED_IGNORED」,不会因失活而改抛 582084。后端已合并 dev-v3(e179e09bd)并部署 TEST,三个入口与待配车池均实测通过(工单 #7964)。网关无路由与接口变更,故 gateway_status 取 not_required;本单只改内部端点行为,前端无感知。"
|
||||
updated_at: "2026-09-20"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# order-v3: 订单取消同事务失活用车需求,已取消户不再进 fleet 待配车池
|
||||
|
||||
> **服务**: hl-order-service-v3 (端口 8086)
|
||||
> **PR**: [#8038](https://git.1814.love:8443/wx/HL/pulls/8038) / [#8044](https://git.1814.love:8443/wx/HL/pulls/8044)
|
||||
> **Issue**: [#7964](https://git.1814.love:8443/wx/HL/issues/7964)
|
||||
> **日期**: 2026-09-20
|
||||
> **影响范围**: fleet 侧待配车需求池与三个用车需求回写入口(**内部接口,不经网关**)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- **本单没有接口结构变化**:没有新增、修改、删除端点,请求参数与响应字段全部不变。
|
||||
- **待配车池返回集合变小**:`GET /v3/internal/order/vehicle-requirements` 不再返回**已取消 / 已退团**订单的需求。此前这些行一直留在池子里,车管会给已经退团的客人排车。
|
||||
- **两个 kind、任意 status 一并失活**:`TRAVEL` 与 `TRANSFER` 都是逐户需求,订单取消时一起失活;`PROCESSING` / `DONE` 的行同样失活(订单都取消了,处在哪一态都不该再被派车)。
|
||||
- **fleet 的三个回写入口行为不变**:本单专门保护了它们,对已取消单仍是原先的「静默跳过」/ `ORDER_CANCELLED_IGNORED`,**不会**因为需求被失活而改抛 `582084`。
|
||||
- **存量已订正**:TEST 库 15 行历史脏数据已随本单清理。
|
||||
|
||||
---
|
||||
|
||||
## 一、行为说明
|
||||
|
||||
### 改前
|
||||
|
||||
订单取消(含退团审批、管理员取消、C 端取消、流团、超时未支付、状态机直推等**全部九条入口**)只做两件事:把订单流转 `CANCELLED`,并给 fleet 发一条 `CANCEL_ACTIVE_BY_REQUIREMENT` 取消派车命令。
|
||||
|
||||
**order 侧的 `order_vehicle_requirement` 行一个字段都不动**:仍是 `is_active=1`、`active_kind` 非空、`status` 保持原值。而待配车池只按 `status + requirement_kind + is_active=TRUE` 过滤、**不 join 订单状态**,于是这些行继续出现在 fleet 拉取的池子里。
|
||||
|
||||
后果:①车管看到一条本不该存在的待派车;②真派了就会给退团客人占用车辆与司机;③派车行已经 `exception` 了,需求还 `PENDING`,两侧状态不一致。
|
||||
|
||||
### 改后
|
||||
|
||||
在**订单取消事务内**(`OrderCancelledFleetListener#beforeCommit`,全部九条取消入口的唯一漏斗)为每条活跃用车需求:先入队 fleet 取消派车命令,**再把该需求行本地失活** —— `is_active=0` 与 `active_kind=NULL` 在**同一条 UPDATE** 里写齐(分两条会在中间态违反 `ck_vehicle_requirement_active_kind`)。
|
||||
|
||||
修的是**源头**,不是给池子加一层 join 过滤 —— 加过滤只会把不一致藏起来,需求本身仍是活跃态。
|
||||
|
||||
---
|
||||
|
||||
## 二、生效范围
|
||||
|
||||
| 维度 | 范围 |
|
||||
|---|---|
|
||||
| 取消入口 | **全部九条**(管理员出行前取消 / C 端本人取消 / C 端删除订单 / 退团审批通过 / 流团已付款户 / 流团待付款户 / 超时未支付 / 管理员状态机直推 CANCEL / E2E 场景推进) |
|
||||
| 需求类别 | `TRAVEL` 与 `TRANSFER` **都失活** |
|
||||
| 需求状态 | **不按 status 过滤**,`PENDING` / `PROCESSING` / `DONE` / `PENDING_REVIEW` 等一律失活 |
|
||||
| 事务 | 与订单取消**同事务**;失活失败会回滚整个取消 |
|
||||
| 表 | `order_vehicle_requirement`(无表结构变更、无 Flyway) |
|
||||
|
||||
---
|
||||
|
||||
## 三、边界
|
||||
|
||||
- **失活写口不带 CAS / `is_active` 前置**:订单状态机没有从 `CANCELLED` 出去的边,取消是终态,不存在「期望态被并发改掉所以放弃」这回事;不带前置也是幂等的前提。
|
||||
- **返回值只能当留痕**:该 UPDATE 每次都刷 `update_time`,MySQL 的 affected rows 是「逐列比对后确实变化的行数」,所以**重放一条早已失活的行返的是 1 而不是 0**。返 0 只发生在「行不存在」或「所有列含 `update_time` 逐列相同」。**不要把 `rows == 0` 当成「重放」的判据。**
|
||||
- **行中终止(`TERMINATED`)不在范围**:它走 `enqueueTerminationTruncate` 按终止日截断,且发的是 `OrderTransitionedEvent` 而不是 `OrderCancelledEvent`,需求不整条失活。
|
||||
- **转期不在范围**:`GroupBatchTransferService` 自己换版,且明确不发 `OrderCancelledEvent`。
|
||||
- **房务侧 `HotelRequirement` 未查**:本单只查了用车侧,**不要把结论推广过去**;若存在同类问题另起工单。
|
||||
- **反向路径确认不存在**:订单状态机无 `from(CANCELLED)` 出边,全仓无 `restore*Order` / `uncancel` / `revertCancel` / `reopenOrder` 方法,所以不存在「订单恢复了、需求还躺在失活态」的镜像问题。团级配车释放的反向回写(`reopenGroupVehicleRequirementsAfterGroupRelease`)**不会**把失活行拉活:它的取锁读自带 `is_active=TRUE` 守卫,失活行直接跳过,且全程只改 `status`。
|
||||
|
||||
---
|
||||
|
||||
## 四、测试环境已验证
|
||||
|
||||
TEST `dev-v3 @ 5d14bc524`(行为)与 `e179e09bd`(语义订正),构建身份探针 `BEHIND 0/N`。
|
||||
|
||||
| 项 | 实测 |
|
||||
|---|---|
|
||||
| 退团审批后离开池子 | 池子 107 → **105**,恰好减该户两条需求 |
|
||||
| 阳性对照 | 退团**前**两条需求确在池子里;另有存量对照:8 条陈旧行**逐条比对 8/8 都在池子里** |
|
||||
| 不误伤同团其它户 | 对照户四次快照始终 `is_active=1` 且始终在池子里 |
|
||||
| 两 kind | `TRAVEL` + `TRANSFER` 同一次取消中一起失活 |
|
||||
| 非 PENDING 行 | `PROCESSING` ✅ / `DONE` ✅ 同样失活 |
|
||||
| 三条取消路径 | 退团审批 / 管理员 `cancel/pre-trip` / 状态机直推 `CANCEL`(覆盖两个不同 publish 点)全部生效 |
|
||||
| 库侧一致性 | `is_active=0` 与 `active_kind=NULL` 同一条 UPDATE 写齐;全库不变式 `is_active=0 AND active_kind IS NOT NULL` 四次快照**恒为 0** |
|
||||
| 存量订正 | 15 行 → 订正后 **0**;那 8 条陈旧行复查 **0/8 仍在池子**,池子 105 → **97** |
|
||||
| 单元测试 | 新增 17 例,四轮变异(注掉失活调用 / 守卫顺序挪回 / 写口漏清 `active_kind` / 第三处换回无差别抛)分别令 4 / 1 / 2 / 3 例转红,复绿 97/97 |
|
||||
| 全量回归 | A 8265 / B 3992 / C 17 = **12274 例**,新增失败 0;唯一失败是 `MapperBoundaryArchTest` 的既存 `payment→refund.mapper` 9 条违规,已用精确 merge-base 实跑对照证明与本单零交集 |
|
||||
|
||||
### fleet 三个回写入口(本单专门保护,**行为不变**)
|
||||
|
||||
对**已取消且需求已失活**的单实测:
|
||||
|
||||
| 入口 | 结果 |
|
||||
|---|---|
|
||||
| `POST /v3/internal/order/orders/{orderId}/requirement/vehicle/status` | `200 成功`(静默跳过) |
|
||||
| `POST /v3/internal/order/orders/{orderId}/requirement/vehicle/reopen-after-assignment-cancel` | `200 成功` |
|
||||
| `POST /v3/internal/order/vehicle-assignment/callback` | `200` + `result: ORDER_CANCELLED_IGNORED` |
|
||||
|
||||
**安全边界未放宽**(同样已取消的单):传别单的 `requirementId` → `582085`;传不存在的 → `582080`。
|
||||
**反向对照**:订单**未**取消 + 需求已失活 → 三个入口**仍全抛 `582080`**,即放宽的只有「订单确实已取消」这一个组合。
|
||||
|
||||
> ⚠️ 第三个入口(配车快照回调)原先把 `is_active` 判定写在 `ORDER_CANCELLED_IGNORED` 短路**之前**,本单一并修正。
|
||||
> 不修的话 fleet 的 `DailyVehicleSnapshotOutboxSender` 会因 `582084` 不在可重试码表里而**首次投递即 quarantine**(终态,只能人工 requeue),并连带堵住同 requirement 的后续 revision。
|
||||
|
||||
---
|
||||
|
||||
## 五、关联 / 联系人
|
||||
|
||||
- Issue: [#7964](https://git.1814.love:8443/wx/HL/issues/7964)
|
||||
- PR: [#8038](https://git.1814.love:8443/wx/HL/pulls/8038)(主修复)、[#8044](https://git.1814.love:8443/wx/HL/pulls/8044)(返回值语义订正)
|
||||
- 后端: jw
|
||||
- **收件人: fleet 侧维护者** —— 待配车池返回集合变小,且三个回写入口对已取消单的行为已被本单专门保护(不变)
|
||||
在新工单中引用
屏蔽一个用户