---
schema: "hl-changelog/v2"
ticket: "5704"
title: "核单域下线数据指纹乐观锁：6 个端点删除指纹/版本号入参与出参字段"
consumer: "admin"
change_type: "修改接口"
author: "yaosutu(GIT)"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "implemented"
frontend_owner: "mmg"
frontend_ref: "d32e14b9"
target_release: ""
verified_at: ""
status_note: "PR #5709 已合并 dev-v3；核单域 6 个端点删除 expectedSourceFingerprint/version 入参与 sourceFingerprint/version 出参，错误码 584108/584110/584325 同步下线。字段删除属硬破坏契约，前端必须先停传这些字段再与后端同批发布。"
updated_at: "2026-08-08"
base: "dev-v3"
---

# 【⚠️ 修改接口·管理后台】核单域下线数据指纹乐观锁，6 个端点删除指纹/版本号字段（#5704）

> **PR**: [#5709](https://git.1814.love:8443/wx/HL/pulls/5709) | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-08

## 1. 接口背景

核单为单人负责场景，不存在多人并发同时修改同一订单核单数据的情况。此前引入的数据指纹（sha256）/版本号乐观锁机制（前端 GET 拿到指纹，保存/确认/提交时回传，数据被他人改动则拒存）对单人操作无实际保护价值，且强制前端每次保存前 GET 并回传 64 位十六进制指纹，增加对接负担。本次将核单域内该机制整体下线：**删除 6 个端点的指纹/版本号入参字段与对应出参字段，3 个相关错误码不再触发**。

> ⚠️ 本次为**字段删除类硬破坏契约**：其中 5 个端点的请求体带未知字段白名单校验，前端若继续传已删除的字段会被拒绝（详见 §11）。**前端必须先删除这些字段的传参，再与后端同批发布。**

## 2. 变更清单

| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|---|---|---|---|---|---|
| 1 | 保存导游费用 | PUT | `/v3/admin/order/{orderId}/settlement/guide-fees` | 删除入参 `expectedSourceFingerprint`、删除出参 `sourceFingerprint` | 停止传参/读字段 |
| 2 | 确认导游费用 | POST | `/v3/admin/order/{orderId}/settlement/guide-fees/confirm` | 删除入参 `expectedSourceFingerprint` | 停止传参 |
| 3 | 保存摄影费用 | PUT | `/v3/admin/order/{orderId}/settlement/photographer-fees` | 删除入参 `expectedSourceFingerprint`、删除出参 `sourceFingerprint` | 停止传参/读字段 |
| 4 | 确认摄影费用 | POST | `/v3/admin/order/{orderId}/settlement/photographer-fees/confirm` | 删除入参 `expectedSourceFingerprint` | 停止传参 |
| 5 | 保存车辆核单草稿 | PUT | `/v3/admin/order/{orderId}/settlement/step3/vehicles` | 删除入参 `version`、删除出参 `version` | 停止传参/读字段 |
| 6 | 完成核单 | POST | `/v3/admin/order/{orderId}/settlement/finalize` | 删除入参 `reimbursementExpectedSourceFingerprint`、`groupExpectedSourceFingerprint` | 停止传参 |

同时下线的错误码：`584108`、`584110`、`584325`（详见 §7）。

## 3. 接口详情

- **使用场景**：核单人员在订单核单页维护导游费用、摄影费用、车辆核单草稿，并在全部分类就绪后完成核单提交。
- **认证**：需要管理后台登录态（Bearer Token）。
- **幂等性**：保存类接口按订单维度覆盖式保存，重复提交相同载荷结果一致；确认/提交接口重放安全（不再有指纹前置校验）。
- **限流**：未声明接口专属限流。
- **方法/路径**：见 §2 变更清单（共 6 个端点；对应的 GET 查询端点出参同步删除指纹/版本号字段，见 §5）。

## 4. 接口入参

### 4.1 路径参数

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `orderId` | Long | 是 | 订单 ID，路径参数，6 个端点一致 |

### 4.2 请求体字段（变更后现状）

**PUT /settlement/guide-fees（保存导游费用）**

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `items` | Array | 是 | 导游费用明细全量集合，最多 200 条；每项含 `id`/`candidateKey`/`staffAssignmentId`/`serviceDate`/`name`/`serviceType`/`paymentMethod`/`amount`/`remark`/`voucherUrls`/`sourceResolution`/`candidateStatus` |
| `excludedCandidateKeys` | Array of String | 否 | 明确排除的候选 key；空数组表示本次不新增排除项 |
| ~~`expectedSourceFingerprint`~~ | - | - | **已删除，禁止再传**（传了会被 584128 白名单拒绝） |

**POST /settlement/guide-fees/confirm（确认导游费用）**

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `itemIds` | Array of Long | 是 | 待确认 INCLUDED 费用明细 ID 列表（JSON 中每项为字符串），1~200 条 |
| ~~`expectedSourceFingerprint`~~ | - | - | **已删除，禁止再传** |

**PUT /settlement/photographer-fees、POST /settlement/photographer-fees/confirm**：字段结构同导游两个端点，仅业务对象为摄影费用，删除字段同为 `expectedSourceFingerprint`。

**PUT /settlement/step3/vehicles（保存车辆核单草稿）**

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `items` | Array | 是 | 车辆核单全量明细 |
| ~~`version`~~ | - | - | **已删除，禁止再传**（传了会被白名单拒绝，返回 400） |

**POST /settlement/finalize（完成核单）**

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `remark` | String | 否 | 提交备注 |
| `reimbursementConfirmation` | Object | 条件必填 | 主报账确认凭据；含 `transferDate`（转账日期）、`transferRef`（转账流水号，主报账净额非 0 时必填，trim 后最长 128 字符）、`advanceSettledFlag`（预支是否已处理，必填布尔）、`signedVoucher`（签字凭证，至少 1 个 URL 非空文件：`files[{name,url}]` + `note`） |
| ~~`reimbursementExpectedSourceFingerprint`~~ | - | - | **已删除，禁止再传** |
| ~~`groupExpectedSourceFingerprint`~~ | - | - | **已删除，禁止再传** |

> 注：finalize 请求体未启用未知字段白名单，误传旧指纹字段会被**静默忽略**（不报 400），但前端仍应停止传参，避免依赖「传了也没事」的行为。

## 5. 出参字段

成功路径出参结构不变，仅删除指纹/版本号字段：

| 端点 | 删除的出参字段 | 原作用 |
|---|---|---|
| GET/PUT `/settlement/guide-fees`、POST `/settlement/guide-fees/confirm` 响应 | ~~`sourceFingerprint`~~ | 导游费用来源数据 sha256 指纹 |
| GET/PUT `/settlement/photographer-fees`、POST `/settlement/photographer-fees/confirm` 响应 | ~~`sourceFingerprint`~~ | 摄影费用来源数据指纹 |
| GET/PUT `/settlement/step3/vehicles` 响应 | ~~`version`~~ | 车辆核单草稿版本号 |

其余出参字段（如导游/摄影的 `category`/`totalAmount`/`cashPaidAmount`/`unconfirmedCount`/`pendingCandidateCount`/`settlementReady`/`blockReasonCode`/`items`/`editable`/`readOnlyReasonCode`，车辆的 `orderId`/`totalAmount`/`allConfirmed`/`settlementReady`/`blockReasonCode`/`items`/`frozen` 等）均无变化。前端不要再读取 `sourceFingerprint` / `version`，读取结果恒为 undefined。

## 6. 枚举 / 数据字典

本次不涉及枚举或字典的新增、删除、改值、改语义。`serviceType`（FULL_COURSE_GUIDE/LOCAL_GUIDE/COMMENTARY_SERVICE/TEMPORARY_SUPPLEMENT）、`paymentMethod`（COMPANY_PAID/CASH_PAID/SIGNED）、`blockReasonCode` 等既有取值不变。

## 7. 错误码

| code | 含义 | 本次变化 |
|---:|---|---|
| `584108` | 车辆核单明细已变化，请刷新 | **已删除，不再触发** |
| `584110` | 导游或摄影费用数据已变化，请刷新 | **已删除，不再触发** |
| `584325` | 核单提交指纹缺失（FINALIZE_FINGERPRINT_REQUIRED） | **已删除，不再触发** |
| `584315` | 核单来源数据已变化，请刷新后重新确认 | **仍在用**：车辆保存的来源数据漂移门禁改抛此码（承接原 584108 场景） |
| `584128` | 导游或摄影费用请求字段不合法：{具体原因} | 不变；前端误传已删除字段时由该码拒绝（见 §8.3） |

> 前端如曾对 `584108`/`584110`/`584325` 写过特判（专属提示/自动刷新分支），这些分支不会再命中，应移除；车辆来源漂移场景改判 `584315`。

## 8. 示例

### 8.1 典型成功（保存导游费用，不再回传指纹）

```http
PUT /v3/admin/order/2084000000000002978/settlement/guide-fees
Authorization: Bearer <token>
Content-Type: application/json
```

```json
{
  "items": [
    {
      "id": "9001",
      "candidateKey": null,
      "staffAssignmentId": "11",
      "serviceDate": "2026-08-08",
      "name": "导游甲",
      "serviceType": "FULL_COURSE_GUIDE",
      "paymentMethod": "COMPANY_PAID",
      "amount": "500.00",
      "remark": null,
      "voucherUrls": [],
      "sourceResolution": null,
      "candidateStatus": "COMPLETE"
    }
  ],
  "excludedCandidateKeys": []
}
```

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "category": "GUIDE",
    "totalAmount": "500.00",
    "cashPaidAmount": "0.00",
    "unconfirmedCount": 1,
    "pendingCandidateCount": 0,
    "settlementReady": false,
    "blockReasonCode": "UNCONFIRMED_ITEMS",
    "items": [],
    "editable": true,
    "readOnlyReasonCode": null
  },
  "success": true
}
```

> 注意：响应中已无 `sourceFingerprint` 字段。

### 8.2 边界（完成核单，不再传双指纹）

```http
POST /v3/admin/order/2084000000000002978/settlement/finalize
Authorization: Bearer <token>
Content-Type: application/json
```

```json
{
  "remark": "核单完成",
  "reimbursementConfirmation": {
    "transferDate": "2026-08-08",
    "transferRef": "TX20260808001",
    "advanceSettledFlag": true,
    "signedVoucher": {
      "files": [{"name": "voucher.jpg", "url": "https://oss.example.com/voucher/1.jpg"}],
      "note": null
    }
  }
}
```

```json
{"code": 200, "message": "success", "data": {"submitted": true}, "success": true}
```

### 8.3 业务失败（旧前端仍传已删除字段，被白名单拒绝）

场景 A：保存导游费用仍传 `expectedSourceFingerprint`。

```http
PUT /v3/admin/order/2084000000000002978/settlement/guide-fees
Authorization: Bearer <token>
Content-Type: application/json
```

```json
{
  "expectedSourceFingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "items": []
}
```

```json
{"code": 584128, "message": "导游或摄影费用请求字段不合法：导游费用请求不支持字段: expectedSourceFingerprint", "data": null, "success": false}
```

场景 B：保存车辆核单草稿仍传 `version`。

```http
PUT /v3/admin/order/2084000000000002978/settlement/step3/vehicles
Authorization: Bearer <token>
Content-Type: application/json
```

```json
{
  "version": 3,
  "items": []
}
```

```json
{"code": 400, "message": "请求数据格式错误：车辆核单请求不支持字段: version", "data": null, "success": false}
```

## 9. 业务边界

- ✅ 保存/确认/提交不再要求前端先 GET 取指纹，可直接操作；单人负责场景下后写覆盖先写，与既有使用方式一致。
- ✅ 车辆保存仍保留「来源数据漂移」业务门禁：草稿加载后若派单/用车来源数据已变化，保存时返回 `584315`，提示刷新后重新确认——这不是乐观锁，是业务一致性校验。
- ❌ 导游/摄影 4 个端点与车辆保存端点对请求体做字段白名单校验，**任何未知字段都会被拒**（含本次删除的指纹/版本号字段），不要把查询响应整个 echo 回请求体。
- ❌ 已终态（冻结）的核单数据仍不可编辑，该约束与本次变更无关，保持不变。

### 9.1 保存时必须剥掉的只读派生字段（导游/摄影）

导游/摄影保存接口（PUT guide-fees / photographer-fees）的 GET 响应 items[] 里含有后端计算的只读派生字段，**保存回传时必须剥掉**，否则触发白名单 400（错误码 584128「不支持字段: xxx」）。

必须剥掉的字段：

| 字段 | 含义 |
|---|---|
| `sourceType` / `sourceTypeName` | 来源类型及中文名 |
| `sourceActive` | 来源是否仍有效 |
| `serviceTypeName` | 导游服务类型中文名 |
| `feeTypeName` | 摄影费用类型中文名 |
| `paymentMethodName` | 付款方式中文名 |
| `settlementConfirmStatus` / `settlementConfirmStatusName` | 核算确认状态及中文名 |
| `candidateResolution` | 候选处理结果 |

通则：**所有 `*Name` 中文字段 + `sourceType`/`sourceActive` + 确认状态 + 候选处理结果，都是后端算的，保存一律不回传。** 推荐前端保存前按允许字段重建 payload（维护 toSaveItem 映射），不要把 GET 响应对象整个 echo 回去。

## 10. 修改前后对比

### 10.1 字段级对比

| 端点 | 字段 | 原来 | 现在 |
|---|---|---|---|
| PUT guide-fees / photographer-fees | `expectedSourceFingerprint` | 入参，回传 GET 拿到的指纹 | **已删除** |
| POST guide-fees/confirm、photographer-fees/confirm | `expectedSourceFingerprint` | 入参 | **已删除** |
| GET/PUT guide-fees、photographer-fees 响应 | `sourceFingerprint` | 出参，64 位十六进制 | **已删除** |
| PUT step3/vehicles | `version` | 入参，草稿版本号 | **已删除** |
| GET/PUT step3/vehicles 响应 | `version` | 出参，整数版本号 | **已删除** |
| POST finalize | `reimbursementExpectedSourceFingerprint`、`groupExpectedSourceFingerprint` | 入参，双指纹 | **已删除** |

### 10.2 行为级对比

| 场景 | 原来 | 现在 |
|---|---|---|
| 保存导游/摄影费用 | 必须先 GET 取 `sourceFingerprint` 回传，指纹不匹配返回 584110 | 直接保存，无指纹校验 |
| 保存车辆核单草稿 | 必须回传 `version`，不匹配返回 584108 | 直接保存；来源数据漂移改返回 584315 |
| 完成核单提交 | 必须传主报账+单团核算双指纹，缺失返回 584325 | 直接提交，无指纹校验 |
| 请求体含已删除字段 | 正常受理 | 导游/摄影返回 584128、车辆返回 400；finalize 静默忽略 |

## 11. 影响评估 / 回滚

### 11.1 影响评估

- **是否破坏向后兼容**：**是，硬破坏**。导游/摄影 4 个端点 + 车辆保存端点带请求体字段白名单，旧前端继续传 `expectedSourceFingerprint` / `version` 会被拒绝（584128 / 400），核单保存、确认链路直接不可用。
- **前端是否必须同步上线**：**必须同批**。前端需先删除上述字段的传参与读取，再与后端同批发布；旧前端 + 新后端 = 核单保存/确认全部报错。
- **上线顺序边界**：前后端同批发布；若必须分先后，**先上前端（停传字段），再上后端**。
- **前端特判清理**：移除对 `584108`/`584110`/`584325` 的特判；车辆来源漂移提示改挂 `584315`。

### 11.2 回滚方案

- 后端回滚即恢复原指纹契约；但已改造的新前端（不传指纹）在旧后端上会触发指纹校验失败——**回滚必须前后端同批回滚**。
- 数据侧无迁移：指纹/版本号不持久化在业务表，回滚无数据修复成本。

## 12. 注意事项

- 本次只下线核单域内上述 6 个端点的指纹机制；应收总览/逐条优惠确认的指纹错误码 `584300`/`584302`（OVERVIEW_FINGERPRINT_EXPIRED / DISCOUNT_FINGERPRINT_EXPIRED）**不在本次范围，仍在用**，相关确认接口的指纹传参保持不变。
- 保存类接口幂等语义不变：按订单维度覆盖式全量保存，重复提交相同载荷结果一致。
- 排查用户报错时，`584128` / 400 的 message 已含具体不支持的字段名，可直接据此定位前端是否还在传旧字段。
- 小程序端（/v3/mp/*）不涉及本次变更，无需任何改动。

## 13. 关联 / 联系人

### 13.1 链接

- **Issue**: [#5704](https://git.1814.love:8443/wx/HL/issues/5704)
- **代码 PR**: [#5709](https://git.1814.love:8443/wx/HL/pulls/5709)
- **文档 PR**: [#5711](https://git.1814.love:8443/wx/HL/pulls/5711)

### 13.2 联系人

- **后端负责人**: @yaosutu (yst)

## 前端实证确认（2026-08-08 mmg，hl-admin@d32e14b9）

已按契约停传/停读全部指纹/版本号字段，清理下线错误码特判：

- **入参删除**：保存导游/摄影 `buildStaffFeeTabSaveRequest` 不再传 `expectedSourceFingerprint`（含回退 #5674 给导游强制传指纹的逻辑——该 changelog 已撤回并入本单）；保存车辆 `buildVehicleSaveRequest` 删 `version`；finalize 删 `reimbursementExpectedSourceFingerprint`/`groupExpectedSourceFingerprint`。人员/车辆保存 items 白名单重建（不回传 sourceType/settlementConfirmStatus 等只读字段）保留不动。
- **出参停读**：报告适配不再投影 `sourceFingerprint`；GET 回读不再持久化车辆 version / 人员指纹。
- **错误码特判**：车辆来源漂移恢复分支 584108 改挂 **584315**（恢复语义不变：丢弃过期草稿+GET 回读权威明细+提示重新核对）；人员 584110/584108 指纹冲突分支删除（下线后不再命中）；584325 下线清理。584300/584302（应收总览/优惠确认指纹）不在范围，未触碰；`attachRefreshedDetailForReportError` 报告侧 584312/584314/584315 刷新逻辑保留。
- **UI 门禁**：`detail.vue canComplete` 由「持有双报告指纹」放宽为「双报告已生成」（finalize 前 service 层双报告 GET 的 584311/584313 保护仍在，指纹门禁本是 UI 冗余）；`ReportModal` 凭据表单重置改用报告快照引用触发（语义等价）。
- **前端未接 confirm 端点**：grep 确认无 `guide-fees/confirm`、`photographer-fees/confirm` 调用，6 端点中实际涉及 4 个。
- 验证：settlement + orderV2 定向 vitest 105/105 通过；checkpoint（含 Vitest 全量 + 生产构建）全绿。
