文件
hl-api-changelog/changelogs-v2/2026-09/24_8329_团期配车确认重复调用不再返602005-修改接口-管理后台.md
T

293 行
15 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
schema: "hl-changelog/v2"
ticket: "8329"
title: "团期配车确认重复调用不再返 602005:需求版本校验下沉到幂等分支之后,回写推进版本不再让重复确认变成错误"
consumer: "admin"
author: "wx(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "pending"
frontend_owner: "mmg"
frontend_ref: ""
target_release: "v2.1"
verified_at: ""
status_note: "PR #8332 squash 合并 dev-v3(8675e1ff0)。部署:hl-fleet-service dev-v3 @ 2b4656424(2026-09-24 14:03 滚动部署完成;同批 order-v3 亦为 2b4656424)。测试服网关从零实测(自造团期 groupBatchId=2103003304906936322,正式需求 2103003334258675714 v2,3 条配车行):① reconfigure(requirementVersion=2)→ 200;② confirm 第一次(requirementVersion=2)→ 200,confirmedCount=3、alreadyConfirmedCount=0、requirementAdvanceIntent=CONFIRMED_TO_DISPATCHED,回写把正式需求推到 status=DONE、version=3;③ 用提交时的旧版本再 confirm 一次(requirementVersion=2)→ http 200、code=200、message=成功,confirmedCount=0、alreadyConfirmedCount=3。改前同一形态返 602005「用车需求已更新, 请刷新后重新配车(提交 …/v2, 当前 …/v3)」。定向单测 81 绿(GroupDispatchConfirmTest 23 / GroupDispatchServiceTest 58),fleet spotless:check 901 files clean。闸门按「基线需求状态已是 DISPATCHED/DONE(即本端点回写已落地)」放行,状态停在 CONFIRMED/PENDING_RECONFIRM 时仍 fail-closed 抛 602005。"
updated_at: "2026-09-24"
base: "dev-v3"
---
# fleet 团期配车: 确认重复调用不再返 602005(版本校验下沉)
> **存放目录**: 二期(v3) → `changelogs-v2/2026-09/`
>
> **服务**: hl-fleet-service(dispatch 域)
> **PR**: https://git.1814.love/wx/HL/pulls/8332
> **Issue**: #8329
> **日期**: 2026-09-24
> **影响范围**: 车务「团期配车 → 确认配车」按钮的重复点击 / 刷新后重试
---
## ⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)
- **重复确认不再返 602005**:整团配车确认成功后,本次确认会**推进正式用车需求版本**(回写);此前这个自己造成的版本前进会让「再点一次确认」被判成「需求已更新」,返回 602005 要求刷新重配——而刷新后拿到的还是同一个已完成的团。现在这一档返回 **200**,并以 `confirmedCount=0` / `alreadyConfirmedCount=N` 表达「本来就是已确认状态」。
- **602005 仍然存在,且判据更精确**:只有当「本次端点的回写**尚未落地**」而版本/状态又对不上时才抛(fail-closed),即真正的「业务侧改了需求,你拿的是旧版本」。
- **602007 的触发面变化**:本团一行配车行都没有(`assignedCount=0` 且 `alreadyConfirmedCount=0`)时抛 602007「本团没有可确认的配车行」,**不再**被版本校验抢先报成 602005。
- **602005 报文的两个占位符统一成 `id=…/v…` 形态**:前端**只应按错误码 602005 处置,不要解析 {0} / {1}**。
---
## 一、背景(选填)
确认配车是定稿动作,它认下的是「这一版需求对应的这套车」,因此入参带了 `requirementId` + `requirementVersion` 作为需求身份。问题是这个端点自己有副作用:确认成功会把正式需求从 `CONFIRMED` 回写成 `DISPATCHED`,版本因此前进。改前版本校验排在幂等分支**之前**、且在事务外先跑,于是「重复确认」这条正常操作被自己造成的版本前进判成了错误。实测:`POST .../confirm {requirementId:2102982327141556225, requirementVersion:2}` → `code=602005`「用车需求已更新, 请刷新后重新配车(提交 2102982327141556225/v2, 当前 2102982327141556225/v3)」,而同刻回读需求已是 `v3`——前进正是首次确认自己的回写。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 确认整团配车 | POST | `/admin/fleet/group-dispatch/batches/{groupBatchId}/confirm` | 行为变更(校验时机 + 报文占位符口径) | 重复确认返 200;602005 只在「本端点回写未落地」时抛 |
---
## 三、接口详情
### 1. 确认整团配车 `POST /admin/fleet/group-dispatch/batches/{groupBatchId}/confirm`
**VO**: `GroupDispatchConfirmReqVO → GroupDispatchConfirmRespVO`
#### 使用场景
车务在团期配车页点「确认配车」,把本团已排的车一次定稿。**没有防重提交时间窗**:连点多少次都是同一个形态,不会出现「请稍后重试」。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | - | 团期聚合主键 |
| requirementId | Body | Long | ✅ | 须等于本团当前正式需求 | 不等于抛 602005(不受本次改动放宽) |
| requirementVersion | Body | Integer | ✅ | 本次确认所依据的版本 | **语义细化(本单)**:版本不一致时——本团仍有「已派车」行待确认,或基线需求状态还停在 CONFIRMED / PENDING_RECONFIRM ⇒ 602005;基线需求状态已是 DISPATCHED / DONE(本端点回写已落地)⇒ 不抛,按 confirmedCount=0 返 200 |
| remark | Body | String | ❌ | ≤200 | 仅日志留痕,**不写进配车行**(配车行 remark 参与 planDigest,刷进去会让下次重配误判「计划有变更」) |
#### 出参 `Result<GroupDispatchConfirmRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| confirmedCount | Integer | 本次真正由 ASSIGNED 转 CONFIRMED 的行数;重复确认时为 0 |
| alreadyConfirmedCount | Integer | 本次之前就已是 CONFIRMED 的行数 |
| requirementId / requirementVersion | String / Integer | 本次提交的需求身份(照原样回显) |
| planVersion | Integer | 配车计划版本 |
| requirementAdvanceIntent | String | 回写意图,如 `CONFIRMED_TO_DISPATCHED` |
| coverage | Object | 按组覆盖读数(groups / missingGroupCodes / satisfied) |
#### 请求示例
```json
{
"requirementId": "2103003334258675714",
"requirementVersion": 2,
"remark": "与地接确认车辆无误"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2103003304906936322",
"confirmedCount": 0,
"alreadyConfirmedCount": 3,
"requirementId": "2103003334258675714",
"requirementVersion": 2,
"planVersion": 2,
"requirementAdvanceIntent": "CONFIRMED_TO_DISPATCHED",
"coverage": { "satisfied": true, "missingGroupCodes": [] }
},
"success": true
}
```
#### 空数据 / 降级响应
- 本团没有任何配车行时**不是**空成功,而是 602007(见「错误响应」)。
- 本团有配车行但已全部 CONFIRMED:200 + `confirmedCount=0`、`alreadyConfirmedCount=N`。
```json
{ "code": 200, "message": "成功", "data": { "confirmedCount": 0, "alreadyConfirmedCount": 3 }, "success": true }
```
#### 错误响应
```json
{
"code": 602005,
"message": "用车需求已更新, 请刷新后重新配车(提交 id=2103003334258675714/v2, 当前 id=2103003334258675714/v4)",
"success": false,
"data": null
}
```
```json
{
"code": 602007,
"message": "本团没有可确认的配车行, 请先提交配车计划",
"success": false,
"data": null
}
```
#### 业务边界
- **真实的判定顺序(改后)**:事务外只做需求 **ID** 校验(602005 的一种形态);进事务、取团锁、锁内重读配车行后依次判 —— 行数都为 0 ⇒ 602007;**本端点回写是否已落地**(基线需求状态 ∈ DISPATCHED / DONE)⇒ 落地则跳过版本/状态校验(幂等重放),未落地则照旧 fail-closed 抛 602005 / 602006;随后做按组覆盖校验(602008),再 CAS 确认。
- **跳过的条件不是「没有待确认行」,而是「本端点的回写已经落地」**:状态停在 CONFIRMED / PENDING_RECONFIRM 而版本不等 ⇒ 推进版本的不是我们,是业务侧改了需求,必须照旧 602005。**不要按 assignedCount==0 一刀切。**
- **需求被整份换过**(`requirementId` 都变了)时拿不到「提交版本」,报文只有 ID 那一半;两支统一拼 `id=…` 前缀。
- 重配端点(`reconfigure`)**未变**:仍整份调用身份校验。
---
## 四、契约约束与正确调用方式(接口类必写)
- **前端按码处置,不解析报文**:602005 = 需求被业务侧改过,刷新重配;200 且 `confirmedCount=0` = 本来就是已确认,无需任何动作。
- **不要把「重复确认」当成需要防抖的操作**:本端点没有防重提交时间窗,按钮可以做防抖,但服务端保证重复调用不报错。
- 确认成功后若要改车,走重配端点(`reconfigure`)而不是再点确认。
### ✅ 正确 / ❌ 错误处置对照
```text
✅ 200 + confirmedCount=0 → 已是确认态,直接展示「已确认」,不要提示失败
✅ 602005 → 提示「需求已更新,请刷新后重新配车」
❌ 把 200 + confirmedCount=0 渲染成错误或重复提交警告
❌ 发现 602005 后原地重试同一个 requirementVersion(版本不会自己回来)
```
### 切换状态时的必要动作
- 重配(`reconfigure`)成功后配车行回到 ASSIGNED/待确认,需要再点一次确认;本单未改这条链路。
---
## 五、数据库行为(涉及写操作时必写)
- 无表结构变更、无 Flyway。
- 确认成功的写入是「把本团存活配车行由 ASSIGNED 置 CONFIRMED」+「事务内意图记录回写需求状态」;重复确认时 CAS 命中 0 行,**零写入**。
- 602005 / 602006 / 602007 / 602008 都在写库之前抛出,可回读需求版本与配车行状态确认未变。
---
## 六、边界行为
- 并发下的保护未变:确认前取团级变更锁并在锁内重读配车行,命中行数与 assignedCount 不符即判并发。
- 受控重开窗口(602012 / 602013)语义未变。
- 需求被整份换过仍抛 602005,不受本次放宽影响。
---
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
| 码 | 触发(改后) | 报文 |
|---|---|---|
| 602005 | requirementId 不等于基线;或本端点回写未落地(需求状态非 DISPATCHED/DONE)而 requirementVersion 不等于基线当前版本 | 用车需求已更新, 请刷新后重新配车(提交 {0}, 当前 {1}),两个占位符均为 id=需求ID/v版本 |
| 602006(不变) | 需求状态不允许配车(回写未落地时的状态判据) | 正式用车需求当前状态不允许配车: {0} |
| 602007(触发面变更) | 本团 assignedCount=0 且 alreadyConfirmedCount=0 | 本团没有可确认的配车行, 请先提交配车计划 |
| 602008(不变) | 库里现存计划仍不满足按组覆盖 | 配车尚未覆盖完整, 不能确认: {0} |
---
## 六.6、修改前后对比(修改/删除类接口必写,新增跳过)
### 字段级对比
| 字段 | 改前 | 改后 |
|------|------|------|
| 请求 / 响应字段 | — | 无新增、无删除,结构不变 |
| 602005 的 {0} | 「换版」支不带前缀词 | 两支统一为 id=…/v… |
### 行为级对比
| 行为 | 改前 | 改后 |
|------|------|------|
| 首次确认成功 | 200,confirmedCount=N | 200,confirmedCount=N(不变) |
| 需求版本已被本端点回写推进后再确认(旧版本) | **602005**(要求刷新重配) | **200**,confirmedCount=0、alreadyConfirmedCount=N |
| 业务侧改了需求、本团仍有待确认行 | 602005 | 602005(不变,fail-closed) |
| 业务侧改了需求、本团早已确认完(回写已落地) | 602005 | 200(幂等重放) |
| 本团一行配车行都没有 | 可能先被版本校验报成 602005 | 602007 |
---
## 六.7、影响评估(修改/删除类必写)
- **是否破坏向后兼容**:不破坏——只把「自己造成的版本前进」这一档从错误放宽成幂等成功;报文占位符口径变化不影响按码处置的前端。
- **前端是否必须同步上线**:不必须。建议把 confirmedCount=0 渲染成「已确认」而不是失败。
- **存量数据影响**:无迁移、无重算。
- **上线风险**:放宽的闸门以「基线需求状态已是 DISPATCHED / DONE」为唯一理由,状态未落地时判据与改前完全一致,因此不会掩盖「业务侧改需求」这一真实冲突。
---
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
- **仅影响**:`POST /admin/fleet/group-dispatch/batches/{groupBatchId}/confirm` 的版本/状态校验时机与 602005 报文占位符形态。
- **零影响**:
- 重配端点 `POST .../reconfigure`(仍整份校验身份)。
- 602006 / 602008 / 602012 / 602013 的判据与报文。
- 按组覆盖校验、并发锁、受控重开窗口。
- order-v3 侧对回写意图的幂等消费口径。
- readiness / overview / 配车行数据结构、数据库、网关路由。
---
## 八、测试环境已验证
**部署读数**:`hl-fleet-service` dev-v3 @ `2b4656424`,2026-09-24 14:03 滚动部署完成。
**网关真实请求与响应**(`https://api.test.1814.love`,自造团期 `groupBatchId=2103003304906936322`,正式需求 `2103003334258675714`,3 条配车行):
```text
① POST /admin/fleet/group-dispatch/batches/2103003304906936322/reconfigure
{requirementId:2103003334258675714, requirementVersion:2, demands:[3 天 × BUS × 1 辆]}
→ http 200, code=200;addedCount=3、aliveCount=3 ✓
② POST .../confirm {requirementId:2103003334258675714, requirementVersion:2}
→ http 200, code=200, confirmedCount=3, alreadyConfirmedCount=0,
requirementAdvanceIntent=CONFIRMED_TO_DISPATCHED ✓
回读 GET /v3/admin/order/group-batch/2103003304906936322/vehicle-requirement
→ status=DONE, version=3(版本被本次回写推进)✓
③ POST .../confirm {requirementId:2103003334258675714, requirementVersion:2} ← 用提交时的旧版本重复确认
→ http 200, code=200, message=成功, confirmedCount=0, alreadyConfirmedCount=3 ✓(改前此处为 602005)
```
**定向测试逐类读数**(`mvn -o -pl hl-fleet-service -am test -Dtest='GroupDispatchConfirmTest,GroupDispatchServiceTest' -DfailIfNoTests=false -Dhl.surefire.failIfNoTests=false`,81 绿):
| 测试类 | Tests run |
|---|---|
| GroupDispatchServiceTest | 58 |
| GroupDispatchConfirmTest | 23 |
fleet `spotless:check`:901 files clean、0 needs changes。
---
## 十、相关文档
- 确认配车端点与 `GroupDispatchConfirmReqVO` 原始设计:#7442
- 受控重开窗口(602012 / 602013):#8308 系列 → `changelogs-v2/2026-09/`
- 团期配车就绪检查:#8294 → `changelogs-v2/2026-09/24_8294_团期配车就绪检查座位不足黄牌扣司机座,新增-passengerSeatTotal-修改接口-管理后台.md`
---
## 关联 / 联系人
### 链接
- Issue: https://git.1814.love/wx/HL/issues/8329
- 后端 PR: https://git.1814.love/wx/HL/pulls/8332
### 联系人
- 后端: wx