---
schema: "hl-changelog/v2"
ticket: "5575"
title: "用车需求重复提交幂等（FOR UPDATE锁修复+同内容重放不产生新行）"
consumer: "admin"
author: "wx(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "not_required"
frontend_owner: "pi-main-session"
frontend_ref: "N/A：请求字段和成功控制流不变，现有页面不穷举 branchTaken。"
target_release: "v2.1"
verified_at: "2026-08-06"
status_note: "后端完成：PR #5584/#5585 已合并 dev-v3 并部署 TEST；网关验证 3/3（连续同内容重放 2 次均返回 IDEMPOTENT_NOOP 同 requirementId，无新行；内容变化仍走 PENDING_EDIT）；存量 v3 双行已按清理 manifest 清理。响应新增 branchTaken=IDEMPOTENT_NOOP 取值（前端可据此提示'内容未变化'），其余契约不变。"
updated_at: "2026-08-06"
base: "dev-v3"
generated: "2026-08-06T11:07:21+08:00"
---

# 用车需求重复提交幂等（FOR UPDATE锁修复+同内容重放不产生新行）

> 后端完成：PR #5584/#5585 已合并 dev-v3 并部署 TEST，网关验证 3/3 通过。

## 关联 / 联系人

### 链接

- **Issue**: [#5575](https://git.1814.love:8443/wx/HL/issues/5575)
- **PR**: [#5584](https://git.1814.love:8443/wx/HL/pulls/5584)、[#5585](https://git.1814.love:8443/wx/HL/pulls/5585)
- **Merge commit**: [1839b2aa4](https://git.1814.love:8443/wx/HL/commit/1839b2aa4)、[dac3e9347](https://git.1814.love:8443/wx/HL/commit/dac3e9347)

### 联系人

- **后端负责人**: @wx

## 背景

订单 2080600006514872322 需求 V3 两条记录（2085009136168177665 PROCESSING + 2085009096510935041 PENDING，同 version=3）——重复提交双写（双击/重试/重放）。

## 修复

1. **并发双写**：`VehicleRequirementMapper.selectLatestByOrderIdForUpdate` 的 `.last("FOR UPDATE").last("LIMIT 1")`——MyBatis-Plus `last()` 覆盖式，LIMIT 1 覆盖掉 FOR UPDATE → 提交事务退化为快照读，并发提交基于同一快照算出同 version 双行。修复为单段 `.last("LIMIT 1 FOR UPDATE")`（MySQL 语法 FOR UPDATE 须在 LIMIT 后）
2. **顺序重复提交**：PENDING/PENDING_REVIEW 分支先做内容语义比较（fleet/行程日期/接送机/特殊标签/备注，JsonNode 解析比较兼容存量空格/字段序差异），完全一致时返回 `IDEMPOTENT_NOOP` 分支的当前版本——不插新行、不失活旧行、不触发 expand/reconcile/outbox

## 变更接口

| 方法 | 路径 | 来源 |
|---|---|---|
| `PUT` | `/v3/admin/order/{id}/vehicle-requirement` | `VehicleRequirementAdminController`（order-v3） |

**请求/响应字段无变化**。响应 `branchTaken` 新增取值 `IDEMPOTENT_NOOP`（重复提交时返回当前 requirementId/version 不变）。

## 前端/调用方动作

1. 提交按钮**可重复点击**：同内容重复提交返回 `branchTaken=IDEMPOTENT_NOOP`，前端可提示"内容未变化"或直接忽略，**无需**做前端防抖（后端已幂等）
2. 内容变化提交仍走 `PENDING_EDIT`/`DONE_ADJUST`，行为不变

## 验证证据

- 定向测试：RequirementServiceTest 198/198（+2：同内容→IDEMPOTENT_NOOP 且 insert/update/expand 未调用；内容变化→PENDING_EDIT）；MapperTest 7/7（+2 SQL 形态断言）；VehicleRequirementConcurrentVersionMysqlTest 4/4（+1 真实 MySQL LIMIT 1 FOR UPDATE 并发用例）
- 网关验证（TEST）：HL20260805165915481 连续同内容重放 2 次均返回 IDEMPOTENT_NOOP 同 requirementId（2085199671193411585）v1，无新行；存量带空格 fleet JSON 语义比较正确
- 数据清理：2080600006514872322 v3 双行按 manifest 清理（删 2085009096510935041，保留 2085009136168177665），v1-v5 各版本唯一
- 兼容性结论：无契约变化；存量数据语义比较兼容；未传字段的旧调用保持原行为
