docs(changelog): 核单导游/摄影费用保存接口入参字段白名单澄清(#5674)
一些检查失败了
changelog-filename-gate / validate (push) Failing after 2s
一些检查失败了
changelog-filename-gate / validate (push) Failing after 2s
保存请求 VO 对未声明字段显式拒绝,前端原样回传 GET 响应对象会带出 sourceType 等只读派生字段触发 400。本文档给出两接口保存入参白名单、 字段约束、必须剥掉的只读字段清单及示例。
这个提交包含在:
父节点
080fe9528d
当前提交
ffdfd6057a
@ -0,0 +1,266 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5674"
|
||||||
|
title: "核单导游/摄影费用保存接口入参字段白名单(响应只读派生字段不得回传)"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
author: "yaosutu(GIT)"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "required"
|
||||||
|
frontend_owner: ""
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "契约零变更的澄清说明:#5674 后保存请求 VO 对未声明字段显式拒绝(@JsonAnySetter),前端若把 GET 响应 item 原样回传会带出只读派生字段(如 sourceType)被 400 拦截。本文档给出保存入参字段白名单与必须剥掉的只读字段清单。"
|
||||||
|
updated_at: "2026-08-08"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【⚠️ 修改接口·管理后台】核单导游/摄影费用保存接口入参字段白名单(#5674 澄清)
|
||||||
|
|
||||||
|
> **Commit**: [146cc2ae4](https://git.1814.love:8443/wx/HL/commit/146cc2ae4) | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-08
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
核单「导游费用」「摄影费用」两个页签的全量保存接口,因 #5674 引入了严格校验:保存请求 VO 用 `@JsonAnySetter` 对**未声明字段显式拒绝**(不是忽略)。
|
||||||
|
|
||||||
|
**前端风险点**:若把 GET 查询响应里的 item 对象**原样回传**给保存接口,会带出 9 个只读派生字段(如 `sourceType`),后端直接 400 报「不支持字段: sourceType」。
|
||||||
|
|
||||||
|
本文档目的:给前端一份**保存入参字段白名单 + 必须剥掉的只读字段清单**。
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 1 | 保存导游费用 | PUT | `/v3/admin/order/{orderId}/settlement/guide-fees` | 契约澄清(签名零变化) | 保存 payload 按白名单重建,勿原样回传响应对象 |
|
||||||
|
| 2 | 保存摄影费用 | PUT | `/v3/admin/order/{orderId}/settlement/photographer-fees` | 契约澄清(签名零变化) | 同上 |
|
||||||
|
|
||||||
|
> 本次为**澄清说明类 changelog**:接口签名、入参、出参、枚举均未变化;目的是防止前端因原样回传响应对象触发 400。
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
- **使用场景**:核单人员在订单核单页维护导游费用 / 摄影费用明细(全量覆盖式保存)。
|
||||||
|
- **认证**:需要管理后台登录态(Bearer Token)。
|
||||||
|
- **幂等性**:全量覆盖式保存,重复提交相同载荷结果一致;导游接口带 `expectedSourceFingerprint` 乐观校验。
|
||||||
|
- **限流**:未声明接口专属限流。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 顶层字段(两接口有差异,重点提示)
|
||||||
|
|
||||||
|
| 字段 | guide-fees(导游) | photographer-fees(摄影) |
|
||||||
|
|---|---|---|
|
||||||
|
| `expectedSourceFingerprint` | ✅ 必填(64 位 hex 数据指纹) | ❌ **不收,别传** |
|
||||||
|
| `items` | ✅ 必填(全量集合,最多 200 条) | ✅ 必填 |
|
||||||
|
| `excludedCandidateKeys` | ✅ 可选(空数组 = 不新增排除) | ✅ 可选 |
|
||||||
|
|
||||||
|
### 4.2 items[] 允许提交字段白名单
|
||||||
|
|
||||||
|
**导游**(guide-fees):
|
||||||
|
|
||||||
|
```
|
||||||
|
id / candidateKey / staffAssignmentId / serviceDate / name / serviceType
|
||||||
|
paymentMethod / amount / remark / voucherUrls / sourceResolution / completionState
|
||||||
|
```
|
||||||
|
|
||||||
|
**摄影**(photographer-fees):
|
||||||
|
|
||||||
|
```
|
||||||
|
id / candidateKey / staffAssignmentId / serviceDate / photographerName / feeType
|
||||||
|
paymentMethod / amount / remark / voucherUrls / sourceResolution / completionState
|
||||||
|
```
|
||||||
|
|
||||||
|
> 导游用 `name` + `serviceType`;摄影用 `photographerName` + `feeType`,**两接口字段名别混**。
|
||||||
|
|
||||||
|
### 4.3 字段约束(写全)
|
||||||
|
|
||||||
|
| 字段 | 类型 | 约束 |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | Long(字符串形式) | 既有行必传;新增手工行传 null |
|
||||||
|
| `candidateKey` | String | 候选行 key;**手工行必须为 null** |
|
||||||
|
| `staffAssignmentId` | Long(字符串形式) | 关联派单人员;手工行为 null |
|
||||||
|
| `serviceDate` | String(yyyy-MM-dd) | INCLUDED 必填;EXCLUDED 为 null |
|
||||||
|
| `name` / `photographerName` | String | INCLUDED 必填,最长 64 |
|
||||||
|
| `serviceType` | Enum | `FULL_COURSE_GUIDE` / `LOCAL_GUIDE` / `COMMENTARY_SERVICE` / `TEMPORARY_SUPPLEMENT` |
|
||||||
|
| `feeType` | Enum | `FOLLOW_SHOOT` / `PORTRAIT` / `AERIAL_SHOOT` / `EDITING_DELIVERY` / `CAMERA_DRONE` / `OTHER` |
|
||||||
|
| `paymentMethod` | Enum | `COMPANY_PAID` / `CASH_PAID` / `SIGNED` |
|
||||||
|
| `amount` | String | 金额字符串,0 ~ 99999999.99,最多两位小数 |
|
||||||
|
| `remark` | String | 无备注传 null,最长 500 |
|
||||||
|
| `voucherUrls` | String[] | 最多 9 个,单条最长 1024,去重保序 |
|
||||||
|
| `sourceResolution` | Enum | 仅转换手工行时传 `CONVERT_TO_MANUAL`,其余不传 |
|
||||||
|
| `completionState` | Enum | `COMPLETE` / `EXCLUDED`;普通保存可 null |
|
||||||
|
|
||||||
|
## 5. 出参字段
|
||||||
|
|
||||||
|
成功响应为统一 `Result` 结构,`code=200`。出参字段本次无变化,不在本文档范围。
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
枚举值见 §4.3 字段约束表(serviceType / feeType / paymentMethod / sourceResolution / completionState)。本次不涉及枚举新增或改值。
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|---:|---|---|
|
||||||
|
| `584128` | 导游或摄影费用请求字段不合法:{具体原因} | 请求体含未声明字段 / 反序列化失败 / Bean 校验失败 |
|
||||||
|
| `584125` | 导游或摄影费用请求字段不合法 | 请求体为空 / null |
|
||||||
|
|
||||||
|
多传只读字段(如 `sourceType`)时,`584128` message 形如:「...不支持字段: sourceType」,直接展示后端 message 即可。
|
||||||
|
|
||||||
|
## 8. 示例
|
||||||
|
|
||||||
|
### 8.1 典型成功 —— 导游(含 expectedSourceFingerprint)
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/2084000000000002978/settlement/guide-fees
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expectedSourceFingerprint": "9f2c4a1b8d3e6f0a1b2c3d4e5f60718293a4b5c6d7e8f901234567890abcdef1",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": null,
|
||||||
|
"candidateKey": null,
|
||||||
|
"staffAssignmentId": null,
|
||||||
|
"serviceDate": "2026-08-08",
|
||||||
|
"name": "张三",
|
||||||
|
"serviceType": "LOCAL_GUIDE",
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"amount": "300.00",
|
||||||
|
"remark": "半天讲解",
|
||||||
|
"voucherUrls": ["https://oss.example.com/voucher/1.png"],
|
||||||
|
"sourceResolution": null,
|
||||||
|
"completionState": "COMPLETE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"excludedCandidateKeys": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"code": 200, "message": "success", "data": {"saved": true}, "success": true}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 典型成功 —— 摄影(不含 expectedSourceFingerprint)
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/2084000000000002978/settlement/photographer-fees
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": null,
|
||||||
|
"candidateKey": null,
|
||||||
|
"staffAssignmentId": null,
|
||||||
|
"serviceDate": "2026-08-08",
|
||||||
|
"photographerName": "李四",
|
||||||
|
"feeType": "FOLLOW_SHOOT",
|
||||||
|
"paymentMethod": "CASH_PAID",
|
||||||
|
"amount": "800.00",
|
||||||
|
"remark": null,
|
||||||
|
"voucherUrls": [],
|
||||||
|
"sourceResolution": null,
|
||||||
|
"completionState": "COMPLETE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"excludedCandidateKeys": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"code": 200, "message": "success", "data": {"saved": true}, "success": true}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 业务失败 —— 多传只读字段 sourceType(400)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expectedSourceFingerprint": "9f2c4a1b8d3e6f0a1b2c3d4e5f60718293a4b5c6d7e8f901234567890abcdef1",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "2084000000000003001",
|
||||||
|
"candidateKey": "GUIDE#2026-08-08#张三",
|
||||||
|
"serviceDate": "2026-08-08",
|
||||||
|
"name": "张三",
|
||||||
|
"serviceType": "LOCAL_GUIDE",
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"amount": "300.00",
|
||||||
|
"sourceType": "CANDIDATE",
|
||||||
|
"sourceTypeName": "候选带入",
|
||||||
|
"settlementConfirmStatus": "UNCONFIRMED"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"excludedCandidateKeys": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"code": 584128, "message": "导游或摄影费用请求字段不合法:导游费用明细不支持字段: sourceType", "data": null, "success": false}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
### ⚠️ 必须剥掉的只读派生字段(响应里有、保存不收,传了就 400)
|
||||||
|
|
||||||
|
```
|
||||||
|
sourceType / sourceTypeName / sourceActive
|
||||||
|
serviceTypeName / paymentMethodName / feeTypeName(摄影)
|
||||||
|
settlementConfirmStatus / settlementConfirmStatusName
|
||||||
|
candidateResolution
|
||||||
|
```
|
||||||
|
|
||||||
|
**通则**:所有 `*Name` 中文字段、`sourceType`/`sourceActive`、确认状态、候选处理结果,都是后端算的,前端保存时**一律别回传**。
|
||||||
|
|
||||||
|
### 推荐做法
|
||||||
|
|
||||||
|
**不要直接回传 GET 响应对象**。保存前按白名单重建 payload——维护一个 `toSaveItem` 映射函数,只挑 §4.2 白名单字段:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 伪代码示意(仅说明映射思路,非前端代码)
|
||||||
|
toSaveItem(respItem) => 只保留白名单 12 个字段,其余丢弃
|
||||||
|
```
|
||||||
|
|
||||||
|
### 适用 / 不适用
|
||||||
|
|
||||||
|
- ✅ 适用:核单页「导游费用」「摄影费用」页签的保存按钮。
|
||||||
|
- ❌ 不适用:确认接口(`/confirm`)与 GET 查询接口不在本文档范围。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
本次为澄清说明,接口签名与字段零变化,无修改前后对比。关键行为澄清:
|
||||||
|
|
||||||
|
| 场景 | 行为 |
|
||||||
|
|---|---|
|
||||||
|
| 保存请求只含白名单字段 | 正常保存(200) |
|
||||||
|
| 保存请求夹带响应只读字段(sourceType 等) | 400,`584128` 指出不支持字段名 |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否。契约零变化。
|
||||||
|
- **前端是否必须同步上线**:建议尽快。若当前前端存在原样回传响应对象的路径,必然触发 400,需按白名单重建 payload。
|
||||||
|
- **回滚方案**:无需回滚(无代码变更)。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 导游接口必传 `expectedSourceFingerprint`(64 位 hex),摄影接口**不收**该字段,传了会被当未知字段拒绝。
|
||||||
|
- `candidateKey` 是候选行标识,手工新增行必须为 null。
|
||||||
|
- 失败 toast 直接展示后端 `message`,已含具体字段原因。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5674](https://git.1814.love:8443/wx/HL/issues/5674)
|
||||||
|
- **Commit**: [146cc2ae4](https://git.1814.love:8443/wx/HL/commit/146cc2ae4)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yaosutu (yst)
|
||||||
正在加载...
x
在新工单中引用
屏蔽一个用户