新增 PR-C2 那份(受控重开窗口 + 计划刷新状态收口),并给三份都补了此前缺的 「部署清单」一节 —— AC-19 ③ 逐字要求部署约束写进 PR 正文与 changelog, 实测 PR 正文有、三份 changelog 全文零命中。 滚动顺序逐 PR 分别分析,没有照抄同一份危险描述: - PR-C2:🔴 逆序会静默退化 —— order-v3 先滚,旧 fleet 收到 dispatchable=true 直接放行重配,而它不读 reconfigureWindow、不校验令牌与范围,两端日志都正常、 没有任何报错。所以 fleet 必须先于 order-v3。 - PR-A / PR-C1:两个方向都不静默出错(fleet 先滚会整体报 602009,是明确错误码 不是静默放行),如实写清与 PR-C2 的区别。 同时补记 GET /v3/internal/group-batch/{id}/dispatch-baseline —— 对 origin/dev-v3 查证,该端点响应体被 #7442 改过两次(8eb8e13cd 加四字段、c6aa1224f 加 reconfigureWindow),三份 changelog 此前都漏记。 三份都带上了「别按直接 pom 依赖查消费方」的警告:grep -rl 'hl-common-core' */pom.xml 只命中 gateway 与 hl-finance,其余六个服务经 hl-common-web / hl-starter-* 传递引入, 照那份清单部署漏掉的恰恰是本单真正改了的 order-v3 与 fleet。 Refs #7442 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
21 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 7442 | 团期配车就绪回写带身份 + 两级判定(PR-C1 补登) | admin | wx(GIT) | 修改接口 | deployed | verified | not_required | 本文档是补登。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 交付,本文档不重复。 | 2026-09-19 | dev-v3 |
fleet/order-v3: 团期配车就绪回写带身份 + 两级判定(PR-C1 补登)
存放目录: changelogs-v2/{YYYY-MM}/
服务: hl-fleet-service (端口 8082)、hl-order-service-v3 (端口 8083) PR: #7923 Issue: #7442(AC-22) 日期: 2026-09-19(补登;改动实际上线于 2026-09-18) 影响范围: fleet↔order-v3 内部 Feign 回调——团期配车就绪回填/重置两个端点新增身份判定,杜绝乱序/旧版回调污染就绪状态;不面向 admin 前端,mmg 无需改动
⚠️ 关键变化
- 这是补登,不是新功能上线通知:本改动已部署 1 天以上,mmg 不需要做任何事——两个端点都是内部
Feign 接口,从未面向前端开放。补登原因同
19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md: #7442 AC-22 要求的交接件缺口排查时顺带发现的。 - 根因:改前,
vehicle-ready/vehicle-ready-reset两个内部回调端点只有路径参数,没有任何版本信息。 这条回调经 fleet 侧 Outbox 异步投递,到达 order-v3 时活跃需求可能已经换了一版、fleet 的计划也可能已经 又重配了几轮——旧需求产出的就绪意图可能覆盖新需求的未就绪状态,旧计划版本产出的重置意图可能把刚配好车的 团打回未就绪。这两种烂法都不报错,只在数据上悄悄错。 - 两级判定是本次改动的核心:请求体新增
requirementId/requirementVersion/planVersion三个身份字段, 提供方(order-v3)先比对需求身份(第一级,逐字相等),再比对同一需求版本内的计划版本大小(第二级), 两级都通过才落库;不通过一律返回 HTTP 200 +applied=false+discardReason,不抛错误码——本端点 由 fleet 侧 Outbox 重试链路驱动,抛错等于让一条已经该丢弃的意图无限重投。 - legacy 兼容窗口:请求体声明为
required=false。PR-C1 上线前 fleet 已投出、尚未消费完的在途旧意图 没有 body,提供方对它们走 legacy 路径(行为与改动前逐字一致)——这是滚动上线的兼容窗口,不是校验豁免, body 一旦非空,DTO 上的逐字段约束全部生效。 - 顺带交付了一条内部可靠性保证("快照顺序不变量"),不影响外部契约:
AssignmentInsuranceOutboxWriter与相关监听器调整了保险快照/Outbox 事件的写入顺序,确保就绪回调触发的下游动作按稳定顺序执行;这部分 纯内部实现细节,不产生任何可观察的接口字段变化,本文档不展开。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 回填配车就绪 | POST | /v3/internal/group-batch/{groupBatchId}/vehicle-ready |
修改(请求体由无到有,新增两级判定) | fleet 整团配车完成后回填 vehicle_ready=true,补登 |
| 2 | 重置配车就绪 | POST | /v3/internal/group-batch/{groupBatchId}/vehicle-ready-reset |
修改(请求体由无到有,新增两级判定) | fleet 清零/释放后重置 vehicle_ready=false,补登 |
三、接口详情
1. 回填配车就绪 POST /v3/internal/group-batch/{groupBatchId}/vehicle-ready
⚠️ [内部接口,不对前端开放] 仅限 fleet-service 内部 Feign 调用。
VO: GroupBatchVehicleReadyReqDTO → GroupBatchVehicleReadyRespDTO
使用场景
fleet-service 整团配车完成后调用本端点回填 vehicle_ready=true。回填成功后内部自动检测四 ready 闸门,
满足则推进团期状态 RESOURCE_PREPARING → MATERIAL_PREPARING。本次改动前该端点只接受路径参数,无法辨别
一条回调到底产自哪个需求版本、哪个计划版本;本次改动后必须携带身份,经两级判定才落库。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | 是 | - | 团期主订单 ID |
| requirementId | Body | Long | 条件必填(body 整体 required=false) |
- | 产出本次就绪意图的正式团级用车需求 ID(两级判定第一级) |
| requirementVersion | Body | Integer | 同上 | ≥1 | 产出本次就绪意图的需求版本 |
| planVersion | Body | Long | 同上 | ≥1 | 产出本次就绪意图的 fleet 团期级计划版本(两级判定第二级,同一需求内部单调递增) |
| sourceRefNo | Body | String | 否 | ≤64 | 幂等追溯号(fleet Outbox 记录 ID),仅用于日志对账,不参与判定 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| applied | Boolean | 本次是否真的把团期 vehicle_ready 改成了本意图的方向 |
| discardReason | String | applied=false 时的原因常量:IDENTITY_MISMATCH/REQUIREMENT_NOT_CONFIRMED/ALREADY_APPLIED/PLAN_VERSION_STALE |
| discardCode | Integer | applied=false 且该原因有对应错误码时为 809205(身份不一致)/809206(计划版本落后),否则为 null |
| currentRequirementId | String(雪花 ID) | 提供方当前活跃需求 ID;无活跃需求为 null |
| currentRequirementVersion | Integer | 提供方当前活跃需求版本 |
| currentPlanVersion | Long | 提供方已应用的最高计划版本 |
| batchStatus | String | 回填后的团期状态(可能已被四 ready 闸门推进) |
请求示例
POST /v3/internal/group-batch/1934567890123456800/vehicle-ready
{
"requirementId": 1934567890123456789,
"requirementVersion": 3,
"planVersion": 7,
"sourceRefNo": "880123"
}
响应示例
{
"code": 200,
"message": "成功",
"data": {
"applied": true,
"discardReason": null,
"discardCode": null,
"currentRequirementId": "1934567890123456789",
"currentRequirementVersion": 3,
"currentPlanVersion": 7,
"batchStatus": "MATERIAL_PREPARING"
},
"success": true
}
空数据 / 降级响应
身份不一致时一律返 200,不是错误响应:
{
"code": 200,
"message": "成功",
"data": {
"applied": false,
"discardReason": "IDENTITY_MISMATCH",
"discardCode": 809205,
"currentRequirementId": "1934567890123456789",
"currentRequirementVersion": 4,
"currentPlanVersion": 8,
"batchStatus": "MATERIAL_PREPARING"
},
"success": true
}
请求体缺省(required=false,legacy 兼容路径)时行为与改动前逐字一致,不做两级判定:
POST /v3/internal/group-batch/1934567890123456800/vehicle-ready
错误响应
真正可重试的故障(DB 不可用、CAS 被并发抢跑)仍以异常形式返回失败 Result,由 Outbox 退避重试:
{
"code": 500,
"message": "数据库异常",
"data": null,
"success": false
}
业务边界
- 一律返 200:本端点由 fleet 侧 Outbox 重试链路驱动,「需求已换版」「计划版本落后」这些结论再投多少次
都一样,用错误码表达会让这条意图无限重投;是否真的落库看
applied,没落库的原因看discardReason - 幂等性:重投同一条
sourceRefNo,结果保持一致 - legacy 兼容窗口是过渡态,不是长期行为:请求体缺省时的 legacy 路径服务的是 PR-C1 上线前已投出的 在途旧意图,不建议新代码依赖这条路径
discardReason=REQUIREMENT_NOT_CONFIRMED/ALREADY_APPLIED没有对应错误码:前者是受控重开窗口期间 fleet 重配发出的意图正常会落在的分支(预期路径不是异常);后者是 Outbox 重试的正常幂等形态
2. 重置配车就绪 POST /v3/internal/group-batch/{groupBatchId}/vehicle-ready-reset
⚠️ [内部接口,不对前端开放] 仅限 fleet-service 内部 Feign 调用。
VO: GroupBatchVehicleReadyReqDTO → GroupBatchVehicleReadyRespDTO
使用场景
fleet-service 整团清零或取消成团/流团释放占用后调用本端点重置 vehicle_ready=false。判定与「1. 回填配车就绪」
完全相同、无任何例外:先判 requirementId + requirementVersion 与当前活跃需求完全一致(不一致丢弃,
discardReason=IDENTITY_MISMATCH),再判该需求版本下的 planVersion 不落后(落后即丢弃,
discardReason=PLAN_VERSION_STALE)。本次改动前该端点同样没有版本信息,旧的 reset 晚到会无条件把
vehicle_ready 打回 false(例如:"清零 plan10 → 重配出 plan11 → plan11 已就绪 → 重放 plan10 的 reset"
会错误地把就绪状态打回 false)。
入参字段表
字段结构与「1. 回填配车就绪」完全一致(唯一区别是本端点的置位方向固定为 ready=false,体现在服务端内部
处理逻辑上,不是一个显式的请求字段):
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | 是 | - | 团期主订单 ID |
| requirementId | Body | Long | 条件必填(body 整体 required=false) |
- | 产出本次重置意图的正式团级用车需求 ID(两级判定第一级) |
| requirementVersion | Body | Integer | 同上 | ≥1 | 产出本次重置意图的需求版本 |
| planVersion | Body | Long | 同上 | ≥1 | 产出本次重置意图的 fleet 团期级计划版本(两级判定第二级) |
| sourceRefNo | Body | String | 否 | ≤64 | 幂等追溯号(fleet Outbox 记录 ID),仅用于日志对账 |
出参字段表
字段结构与「1. 回填配车就绪」完全一致:
| 字段 | 类型 | 说明 |
|---|---|---|
| applied | Boolean | 本次是否真的把团期 vehicle_ready 改成了 false |
| discardReason | String | applied=false 时的原因常量:IDENTITY_MISMATCH/REQUIREMENT_NOT_CONFIRMED/ALREADY_APPLIED/PLAN_VERSION_STALE |
| discardCode | Integer | applied=false 且该原因有对应错误码时为 809205/809206,否则为 null |
| currentRequirementId | String(雪花 ID) | 提供方当前活跃需求 ID;无活跃需求为 null |
| currentRequirementVersion | Integer | 提供方当前活跃需求版本 |
| currentPlanVersion | Long | 提供方已应用的最高计划版本 |
| batchStatus | String | 重置后的团期状态 |
请求示例
POST /v3/internal/group-batch/1934567890123456800/vehicle-ready-reset
{
"requirementId": 1934567890123456789,
"requirementVersion": 3,
"planVersion": 10,
"sourceRefNo": "880130"
}
响应示例
{
"code": 200,
"message": "成功",
"data": {
"applied": true,
"discardReason": null,
"discardCode": null,
"currentRequirementId": "1934567890123456789",
"currentRequirementVersion": 3,
"currentPlanVersion": 10,
"batchStatus": "RESOURCE_PREPARING"
},
"success": true
}
空数据 / 降级响应
重放一条落后的 plan10 reset(当前已推进到 plan11 且已就绪)会被丢弃,不会把就绪状态打回 false:
{
"code": 200,
"message": "成功",
"data": {
"applied": false,
"discardReason": "PLAN_VERSION_STALE",
"discardCode": 809206,
"currentRequirementId": "1934567890123456789",
"currentRequirementVersion": 3,
"currentPlanVersion": 11,
"batchStatus": "MATERIAL_PREPARING"
},
"success": true
}
错误响应
同「1. 回填配车就绪」:
{
"code": 500,
"message": "数据库异常",
"data": null,
"success": false
}
业务边界
- 判定与置位方向完全一致、无任何例外——起草阶段曾给本端点开过"
planVersion落后仍执行"的例外,最终 版本删除了这条例外:新增释放/清零本来就会产生一个更高的计划版本,合法的释放意图永远带着新版本到达, 不需要豁免;反过来,带着落后版本到达的 reset 只可能是旧事件重放 - 该团已无活跃需求时清零仍照常应用:这是本方向唯一的口子——流团已经把需求失活,而释放回调仍必须能把 标志清掉
- 其余边界(一律返 200、幂等性、legacy 兼容窗口)同「1. 回填配车就绪」
四、契约约束与正确调用方式
本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议(本单两个端点均为内部接口,无 UI 直接对接)。
| 场景 | 做法 |
|---|---|
| fleet 侧发起就绪回调 | 必须带上产生这条意图那一刻的 requirementId/requirementVersion/planVersion,不能只传 groupBatchId |
| 判断回调是否真的生效 | 看响应 applied,不要用 HTTP 状态码判断——不通过的判定同样返回 200 |
| 需要排查一条回调为什么没生效 | 看 discardReason,四种原因分别指向不同的处置方向(换版 / 窗口期正常分支 / 幂等重放 / 乱序过期),不要合并处理 |
五、数据库行为
| 操作 | 数据库影响 |
|---|---|
两级判定通过、ready 方向 |
order_group_batch.vehicle_ready → true,可能连带推进 batch_status |
两级判定通过、reset 方向 |
order_group_batch.vehicle_ready → false |
| 两级判定未通过(任一方向) | 零写入 |
GroupVehicleRequirementDO 新增列 |
新增 plan_version/相关身份列,供两级判定读取当前已应用的最高计划版本(Flyway V20260918_*__add_group_vehicle_requirement_add_vehicle_plan_version.sql) |
六、边界行为
- legacy 无身份回调:
req == null或req.requirementId == null时走改动前的行为,不做两级判定 - 同需求版本内计划版本必须单调不落后:落后判定只在同一需求版本内部比较,换了需求版本后 fleet 的计划 版本并不重置,不会拿跨需求的两个版本比大小
六.5 枚举
discardReason(GroupBatchVehicleReadyRespDTO.discardReason)
所属字段: discardReason | 类型: String
| 值 | 中文 | 对应错误码 | 说明 |
|---|---|---|---|
IDENTITY_MISMATCH |
身份不一致 | 809205 | 回调携带的需求身份与当前活跃需求不一致 |
REQUIREMENT_NOT_CONFIRMED |
需求未在已确认档 | 无 | 受控重开窗口期间的正常路径,不是异常 |
ALREADY_APPLIED |
已应用过 | 无 | Outbox 重试的正常幂等形态 |
PLAN_VERSION_STALE |
计划版本落后 | 809206 | 同一需求版本内计划版本比已应用的旧,乱序到达的过期意图 |
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
vehicle-ready/vehicle-ready-reset 请求体 |
无(仅路径参数) | 新增可选请求体 requirementId/requirementVersion/planVersion/sourceRefNo(legacy 兼容,非必填) |
vehicle-ready/vehicle-ready-reset 响应体 |
仅 Result<Void> |
新增 GroupBatchVehicleReadyRespDTO:applied/discardReason/discardCode/currentRequirementId/currentRequirementVersion/currentPlanVersion/batchStatus |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 旧需求晚到的就绪回调 | 会把新需求的未就绪状态覆盖成就绪(污染) | 身份不一致直接丢弃,applied=false |
| 旧计划版本晚到的重置回调 | 会把刚配好车的团打回未就绪(污染) | 计划版本落后直接丢弃,applied=false |
| 调用方判断回调是否生效 | 只能靠 HTTP 200 推断(不可靠) | 必须读 applied 字段 |
六.7、影响评估
- 是否破坏向后兼容: 否。请求体新增字段声明
required=false,legacy 无身份回调仍走改动前的行为路径;响应体从Result<Void>扩展为携带结构化结果,属于向后兼容的加字段 - 前端是否必须同步上线: 否。两个端点均为内部 Feign 接口,不面向 hl-ui
- 前端 workaround 清理点: 无
部署清单(本单改了 hl-common-core,CODE_RULES §16.6)
本单在 hl-common-core 新增两个全新 DTO:GroupBatchVehicleReadyReqDTO(就绪回调两级判定身份)、
GroupBatchVehicleReadyRespDTO(两级判定结果)。order-v3 是这两个端点(vehicle-ready/vehicle-ready-reset)
的提供方,fleet 是发起方(调用方)。
滚动顺序分析:本单两个方向都不静默出错,但"order-v3 先滚"能让保护提前生效
- order-v3 先滚:order-v3 的端点方法签名新增了一个
@RequestBody(required = false)参数,此时 fleet 还是旧版、调用时不带任何请求体(旧代码逻辑本就没有这个概念)——req == null命中 legacy 路径, 行为与改动前逐字一致,安全;等 fleet 也滚上去开始携带身份后,两级判定立刻对新产生的回调生效。 - fleet 先滚:fleet 开始在请求体里携带
requirementId/requirementVersion/planVersion,但旧版 order-v3 的端点方法签名根本没有声明接收请求体的参数——Spring 对声明之外的请求体内容视而不见, 不会报错也不会解析失败,等价于"这个请求体被忽略了",order-v3 仍按改动前的无身份逻辑处理。净效果: 两级判定这项保护在这段过渡期内还没有生效(旧漏洞——乱序/旧版回调污染就绪状态——仍然存在), 但不会比改动前更糟,也不会报错或崩溃。 - 结论:两个方向都不会比"改动前"更差,区别只是"保护何时开始生效"。仍建议同批滚,让两级判定 尽快对新回调生效,避免过渡期内继续吃"旧漏洞"的亏。
消费方清单不能按 pom.xml 直接依赖关系查
理由与判据同 19_7442_团期配车受控重开窗口计划刷新状态收口-新增接口-管理后台.md「部署清单」节——
grep -rl 'hl-common-core' */pom.xml 只命中 hl-gateway/hl-finance,order-v3 与 fleet 都经
hl-common-web 传递引入,不在直接依赖清单里,但正是本单真正改了代码的两个服务。
⇒ 实际部署单位共 7 个:gateway / user / resource / product-v2 / order-v3 / mp / fleet;本单不强制 要求特定先后顺序,但同批滚仍是最稳妥的做法。
七、不影响范围
- 仅影响: fleet→order-v3 的就绪回调链路(两个内部端点)
- 零影响:
- admin/mp 前端可见的任何端点
- #7442 PR-A 的
reconfigure写口(见19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md) - #7442 PR-B 的确认/回写链路(见
17_7442_团级确认态-需求已发车务回写-新增接口-管理后台.md) - #7442 PR-C2 的受控重开窗口流程(见
19_7442_团期配车受控重开窗口计划刷新状态收口-新增接口-管理后台.md) - #7444 PR-1 在本端点追加的 DISPATCHED→DONE 推进(见
19_7444_团期配车就绪门禁与同团车辆共用关系-新增接口-管理后台.md,本文档不重复该部分)
八、测试环境已验证
⚠️ 本节暂无网关实测数据(两个端点为内部接口,不走网关,网关实测本就不适用);内部调用链的验证归属 fleet↔order-v3 集成测试,本文档撰写时未另行发起手工调用,gateway_status 记 pending 供 #7442 取证车道处理。
十、相关文档
- 关联 Issue: wx/HL#7442
- 关联 PR: wx/HL#7923
- 相关文档:
changelogs-v2/2026-09/19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.mdchangelogs-v2/2026-09/19_7444_团期配车就绪门禁与同团车辆共用关系-新增接口-管理后台.md(本端点后续被追加 DISPATCHED→DONE 推进的部分)
关联 / 联系人
链接
- Issue: #7442
- PR: #7923
- Merge commit: 6f5b1b679f6b534081ca7b136e338cd3fffc1155
联系人
- 后端负责人: @wx