docs(changelog): #7442 三份交接件补真实网关实测,并订正 batchStatus 契约错误
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
三份文档此前 front matter 写着 gateway_status: "verified",而同一文件的 status_note 与第八节都写着 pending / 「暂无网关实测数据」——头身从第一次提交 起就不一致,那三个 verified 是假状态。 2026-09-19 已完成实测,现在它们是真的: - reconfigure 补登(1 个端点):正负各一次。正常路径 planVersion 2→3、 idempotentShortCircuit=false、落库 3 条活跃行;负向路径事先点名 602002, 拿到 602002 且 message 指名漏掉的 SUV 组,DB 核实零写入。 另记两个坑:单组需求下 602002 语法上不可达(602001 的检查排在前面); 夹具里的 vehicle_id=1001 是不存在的占位 ID,会被 600006 拦下。 - 受控重开窗口(5 个端点):reopen / requirement/confirm 走网关, plan-refresh / coverage / dispatch-baseline 直连。每个端点都断了至少一个 本次改动相关字段并做 DB 交叉核实。coverage 补了正负对照 (satisfied=true/gaps=[] vs 故意传 requirementVersion=999 → satisfied=false 且 gaps 指出身份已变),证明该字段不是恒真。 顺带订正 status_note 里「测试服尚未部署到含 PR #7957」——那句已过期, 实测中 reconfigureWindowToken(#7957 引入)被真实端点接收并生效。 - 就绪回写两级判定(2 个端点):vehicle-ready-reset 与 vehicle-ready 配对, reset 让 vehicle_ready 1→0、再用 vehicle-ready 推回 1,夹具靠真实写口还原、 零 SQL 直改。补两条负向对照(重放同版本→ALREADY_APPLIED;错版本→ IDENTITY_MISMATCH/809205)证明 applied 不是恒真字段。 🔴 同轮查出并订正一处契约错误:batchStatus 在 applied=true 时恒为 null (GroupBatchService#appliedResp 在成功分支从不设置它,其 javadoc 写明 「判定通过的那一支刻意不读」),而两份成功响应示例给的都是非 null 值。 照旧稿写的前端会读到一个永远为空的字段。示例与字段说明均已更正, 并写明想拿团期状态要另查团期详情接口。 三份文档共 8 个端点,全部走的是「HTTP 200 + success 或事先点名的错误码 + 至少一个本次改动相关字段 + 尽量做 DB 交叉核实」这一套判据。 Refs #7442 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -12,7 +12,7 @@ frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "本文档是补登。PR-C1(#7923,merge commit 6f5b1b679f6b534081ca7b136e338cd3fffc1155,2026-09-18 合入 dev-v3)首次交付本次改动时同样漏写了交接件——这是排查 #7442 交接件缺口时顺带发现的第二处(第一处是 PR-A 的 reconfigure 端点,见 19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md)。backend_status=deployed 依据:6f5b1b679 已在 dev-v3,测试服 2026-09-19 01:15 已滚动部署到 4cbccc26b(晚于本提交,链上包含)。gateway_status 先记 pending。frontend_status 记 not_required:本文档涉及的两个端点都是仅限内部 Feign 调用的接口,不面向 hl-ui,前端无需改动。⚠️ 本文档只覆盖 PR-C1(#7442)原始交付的两级判定部分;#7444 PR-1 随后在 vehicle-ready 端点同一事务内又追加了一步(团级正式需求 DISPATCHED→DONE),那部分内容已在 19_7444_团期配车就绪门禁与同团车辆共用关系-新增接口-管理后台.md 交付,本文档不重复。"
|
||||
status_note: "本文档是补登。PR-C1(#7923,merge commit 6f5b1b679f6b534081ca7b136e338cd3fffc1155,2026-09-18 合入 dev-v3)首次交付本次改动时同样漏写了交接件——这是排查 #7442 交接件缺口时顺带发现的第二处(第一处是 PR-A 的 reconfigure 端点,见 19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md)。backend_status=deployed 依据:6f5b1b679 已在 dev-v3,测试服 2026-09-19 01:15 已滚动部署到 4cbccc26b(晚于本提交,链上包含)。gateway_status=verified 依据:2026-09-19 已对本文档覆盖的两个端点各做真实调用并做数据库交叉核实,见第八节;⚠️ 本文档此前 gateway_status 字段写着 verified 而 status_note 与第八节都写着 pending,头身不一致,那时的 verified 是假状态。⚠️ 同轮实测还查出并订正了一处契约错误:batchStatus 在 applied=true 时恒为 null,而本文档原先的两个成功响应示例给的是非 null 值,照旧稿写的前端会读到一个永远为空的字段——已在第三节两处订正。frontend_status 记 not_required:本文档涉及的两个端点都是仅限内部 Feign 调用的接口,不面向 hl-ui,前端无需改动。⚠️ 本文档只覆盖 PR-C1(#7442)原始交付的两级判定部分;#7444 PR-1 随后在 vehicle-ready 端点同一事务内又追加了一步(团级正式需求 DISPATCHED→DONE),那部分内容已在 19_7444_团期配车就绪门禁与同团车辆共用关系-新增接口-管理后台.md 交付,本文档不重复。"
|
||||
updated_at: "2026-09-19"
|
||||
base: "dev-v3"
|
||||
---
|
||||
@@ -94,7 +94,13 @@ fleet-service 整团配车完成后调用本端点回填 `vehicle_ready=true`。
|
||||
| currentRequirementId | String(雪花 ID) | 提供方当前活跃需求 ID;无活跃需求为 null |
|
||||
| currentRequirementVersion | Integer | 提供方当前活跃需求版本 |
|
||||
| currentPlanVersion | Long | 提供方已应用的最高计划版本 |
|
||||
| batchStatus | String | 回填后的团期状态(可能已被四 ready 闸门推进) |
|
||||
| batchStatus | String | 🔴 **`applied=true` 时恒为 `null`**,只有丢弃分支(`applied=false`)才回填团期状态。见下方订正。 |
|
||||
|
||||
> 🔴 **2026-09-19 契约订正(实测发现,本文档此前写错了)**:`batchStatus` 在**成功分支上永远是 `null`**。
|
||||
> 源码依据:`GroupBatchService#appliedResp`(`hl-order-service-v3/.../groupbatch/service/GroupBatchService.java:946-954`)在 `applied=true` 分支**从不设置该字段**,其 javadoc(`:974-977`)写明「判定通过的那一支刻意不读」;只有 `discardedResp()` 才回填。
|
||||
> **实测佐证**:2026-09-19 两次 `applied=true` 的真实调用(`vehicle-ready` 与 `vehicle-ready-reset` 各一次),响应里 `batchStatus` 均为 `null`。
|
||||
> ⚠️ **本文档原先的两个成功响应示例给的是非 null 值**(`MATERIAL_PREPARING` / `RESOURCE_PREPARING`),照它写的前端会读到一个恒为 `null` 的字段。下方示例已按实测值更正。
|
||||
> **想拿团期状态,请另查团期详情接口,不要依赖本响应的这个字段。**
|
||||
|
||||
#### 请求示例
|
||||
|
||||
@@ -121,15 +127,17 @@ POST /v3/internal/group-batch/1934567890123456800/vehicle-ready
|
||||
"currentRequirementId": "1934567890123456789",
|
||||
"currentRequirementVersion": 3,
|
||||
"currentPlanVersion": 7,
|
||||
"batchStatus": "MATERIAL_PREPARING"
|
||||
"batchStatus": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ `batchStatus` 在 `applied=true` 时**就是 `null`**(不是示例省略),见上方订正。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
身份不一致时**一律返 200**,不是错误响应:
|
||||
身份不一致时**一律返 200**,不是错误响应(下例的 `batchStatus` 有值是对的——丢弃分支才回填):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -219,7 +227,7 @@ fleet-service 整团清零或取消成团/流团释放占用后调用本端点
|
||||
| currentRequirementId | String(雪花 ID) | 提供方当前活跃需求 ID;无活跃需求为 null |
|
||||
| currentRequirementVersion | Integer | 提供方当前活跃需求版本 |
|
||||
| currentPlanVersion | Long | 提供方已应用的最高计划版本 |
|
||||
| batchStatus | String | 重置后的团期状态 |
|
||||
| batchStatus | String | 🔴 **`applied=true` 时恒为 `null`**(同上方 `vehicle-ready` 的订正,同一份 `appliedResp()` 逻辑),只有丢弃分支才回填 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
@@ -246,15 +254,17 @@ POST /v3/internal/group-batch/1934567890123456800/vehicle-ready-reset
|
||||
"currentRequirementId": "1934567890123456789",
|
||||
"currentRequirementVersion": 3,
|
||||
"currentPlanVersion": 10,
|
||||
"batchStatus": "RESOURCE_PREPARING"
|
||||
"batchStatus": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ `batchStatus` 在 `applied=true` 时**就是 `null`**(不是示例省略),见上方订正。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
重放一条落后的 `plan10` reset(当前已推进到 `plan11` 且已就绪)会被丢弃,**不会把就绪状态打回 false**:
|
||||
重放一条落后的 `plan10` reset(当前已推进到 `plan11` 且已就绪)会被丢弃,**不会把就绪状态打回 false**(下例的 `batchStatus` 有值是对的——丢弃分支才回填):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -453,7 +463,62 @@ hl-common/hl-common-core/src/main/java/com/hulalv/common/dto/fleet/GroupBatchVeh
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
> ⚠️ 本节暂无网关实测数据(两个端点为内部接口,不走网关,网关实测本就不适用);内部调用链的验证归属 fleet↔order-v3 集成测试,本文档撰写时未另行发起手工调用,gateway_status 记 pending 供 #7442 取证车道处理。
|
||||
✅ **2026-09-19 已完成实测,本文档覆盖的两个端点各验一次,并各配负向对照。**
|
||||
|
||||
> 🔴 **走位**:两个端点都是 `/v3/internal/**`,**不走网关也不能走** —— `JwtAuthFilter.isInternalPath()`(`hl-gateway/.../filter/JwtAuthFilter.java:290-294, :326-329`)对任何匹配 `/internal/*` 或 `/v3/internal/*` 的请求**无条件 403**(路由是配了的,但请求到不了后端),带不带 admin token 都一样。⇒ **直连 order-v3 `127.0.0.1:8086`,带 `X-Internal-Token`**。
|
||||
|
||||
夹具 `groupBatchId=2099951310525673474` / `requirementId=2099951397100302337`。**全程零 SQL 直改业务状态**,唯一的 SQL 是 SELECT 读数。
|
||||
|
||||
**基线读数**:`vehicle_ready=1`、`vehicle_plan_version=5`、`status=CONFIRMED`、`version=5`、`blocked_stage=NULL`、`plan_refresh_state=DONE`。
|
||||
|
||||
### 1. `POST /v3/internal/group-batch/{id}/vehicle-ready-reset`
|
||||
|
||||
```json
|
||||
// 请求
|
||||
{"requirementId":2099951397100302337,"requirementVersion":5,"planVersion":6,"sourceRefNo":"ac21-verify-reset"}
|
||||
|
||||
// 响应 HTTP 200
|
||||
{"code":200,"message":"成功","success":true,
|
||||
"data":{"applied":true,"discardReason":null,"discardCode":null,
|
||||
"currentRequirementId":"2099951397100302337","currentRequirementVersion":5,
|
||||
"currentPlanVersion":"6","batchStatus":null}}
|
||||
```
|
||||
|
||||
**DB 交叉核实**:`vehicle_ready` **1 → 0**、`vehicle_plan_version` **5 → 6**;`status`/`blocked_stage`/`plan_refresh_state` 不变。
|
||||
(`batchStatus` 为 `null` —— 这正是本文档第三节订正的那条契约,此处是它的实测读数。)
|
||||
|
||||
### 2. `POST /v3/internal/group-batch/{id}/vehicle-ready`(还原)
|
||||
|
||||
```json
|
||||
// 请求
|
||||
{"requirementId":2099951397100302337,"requirementVersion":5,"planVersion":7,"sourceRefNo":"ac21-verify-restore"}
|
||||
|
||||
// 响应 HTTP 200,applied=true,currentPlanVersion="7",batchStatus=null
|
||||
```
|
||||
|
||||
**DB 交叉核实**:`vehicle_ready` **0 → 1**、`vehicle_plan_version` **6 → 7**;其余与基线一致。
|
||||
|
||||
### 3. 两条负向对照(证明 `applied` 不是恒真字段)
|
||||
|
||||
在 reset 之后、还原之前的中间态上做:
|
||||
|
||||
| 构造 | 响应 | DB 复读 |
|
||||
|---|---|---|
|
||||
| 重放同一 `planVersion=6`(身份不变) | `applied=false`、`discardReason=ALREADY_APPLIED` | `vehicle_ready` 仍 0、`vehicle_plan_version` 仍 6,**未再变化** |
|
||||
| 故意传 `requirementVersion=99` | `applied=false`、`discardReason=IDENTITY_MISMATCH`、**`discardCode=809205`** | **零写入** |
|
||||
|
||||
> 🔴 **为什么必须做这两条**:只看成功那一次,`applied=true` 与「这个端点无论如何都返回 true」观测完全相同。两条负向让它翻成 `false` 并给出不同的丢弃原因和错误码,才证明两级判定在**判**而不是在**报**。
|
||||
|
||||
### 4. 契约逐字段核对
|
||||
|
||||
入参 4 字段 + 出参 7 字段共 **11 个字段**逐条对源码核过(`GroupBatchVehicleReadyReqDTO` / `GroupBatchVehicleReadyRespDTO` / `GroupBatchResourceController` / `GroupBatchService#applyReadyCallback`、`#appliedResp`、`#discardedResp` / `GroupVehicleRequirementService#decideVehicleReadyCallback`):
|
||||
|
||||
- **10/11 一致**(类型、必填性、`required=false` 的 legacy 兼容窗口、两级判定顺序、`discardReason` 四常量、`discardCode` 只在 809205/809206 有值、丢弃场景一律 HTTP 200);
|
||||
- **1 处不一致已订正**:`batchStatus`(见第三节)。
|
||||
|
||||
### 5. 残留
|
||||
|
||||
`vehicle_plan_version` 由基线 5 前进到 **7**(reset 用 6、mark 用 7)。这是两级判定 CAS 语义的必然结果 —— 该列**只能单调递增**,且**不允许用 SQL 回改**(回改会造出系统不可能自然产生的状态,下一个人会把它当真实缺陷去追)。`vehicle_ready`/`status`/`blocked_stage`/`plan_refresh_state` 均已还原到与基线一致。**这是真实写口产生的合法状态,不是损坏。**
|
||||
|
||||
---
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户