docs(changelog): 撤回含错误指纹说法的 #5674 白名单,只读字段清单并入 #5704
一些检查失败了
changelog-filename-gate / validate (push) Failing after 1s
一些检查失败了
changelog-filename-gate / validate (push) Failing after 1s
这个提交包含在:
父节点
a0b43444db
当前提交
3928b35212
@ -1,280 +0,0 @@
|
|||||||
---
|
|
||||||
schema: "hl-changelog/v2"
|
|
||||||
ticket: "5674"
|
|
||||||
title: "核单导游/摄影费用保存接口入参字段白名单(响应只读派生字段不得回传)"
|
|
||||||
consumer: "admin"
|
|
||||||
change_type: "修改接口"
|
|
||||||
author: "yaosutu(GIT)"
|
|
||||||
backend_status: "deployed"
|
|
||||||
gateway_status: "verified"
|
|
||||||
frontend_status: "implemented"
|
|
||||||
frontend_owner: "mmg"
|
|
||||||
frontend_ref: "9694bb68"
|
|
||||||
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)
|
|
||||||
|
|
||||||
## 前端实证确认(2026-08-08 mmg,hl-admin@9694bb68)
|
|
||||||
|
|
||||||
前端保存**非原样回传响应对象**,而是 `returnDetailAdapter.js buildStaffFeeTabSaveRequest` 逐项显式构造——但与 §4.2 白名单有真实出入,已按白名单重建修复:
|
|
||||||
|
|
||||||
| 字段 | 前端改动前 | 改动后 |
|
|
||||||
|---|---|---|
|
|
||||||
| `sourceType` | 传 `source.sourceType \|\| 'MANUAL'` | **删除**(只读派生,传了 400) |
|
|
||||||
| `settlementConfirmStatus` | 传 UNCONFIRMED/CONFIRMED | **删除**(确认状态后端算) |
|
|
||||||
| `expectedSourceFingerprint` | 导游+摄影**都传** | **仅导游传**,摄影省略(摄影不收,传了被当未知字段拒) |
|
|
||||||
| 手工/候选来源区分 | 靠 sourceType=MANUAL | 改由 `candidateKey`/`staffAssignmentId` 是否为 null 区分(changelog §4.3 口径) |
|
|
||||||
| 姓名/类型字段 | name+serviceType(导游)/photographerName+feeType(摄影) | 不变(本就不混用) |
|
|
||||||
|
|
||||||
修复前导游保存必带 sourceType+settlementConfirmStatus → 必触发 584128 400;摄影另多传 expectedSourceFingerprint → 同样 400。修复后严格白名单。改动文件:`returnDetailAdapter.js` + 两个 spec 同步断言(新增 `not.toHaveProperty('sourceType'/'settlementConfirmStatus')`、摄影 `not.objectContaining expectedSourceFingerprint` 守卫)。step1/step2/meal/vehicle/otherIncome/otherExpense 的 settlementConfirmStatus 不在本白名单范围、契约未变,保持不动。验证:settlement 全量定向 vitest 92/92 通过;checkpoint 全绿。
|
|
||||||
@ -249,6 +249,24 @@ Content-Type: application/json
|
|||||||
- ❌ 导游/摄影 4 个端点与车辆保存端点对请求体做字段白名单校验,**任何未知字段都会被拒**(含本次删除的指纹/版本号字段),不要把查询响应整个 echo 回请求体。
|
- ❌ 导游/摄影 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. 修改前后对比
|
||||||
|
|
||||||
### 10.1 字段级对比
|
### 10.1 字段级对比
|
||||||
|
|||||||
正在加载...
x
在新工单中引用
屏蔽一个用户