From 3928b35212f2ea1ae6167b342a97fac6473614dc Mon Sep 17 00:00:00 2001 From: yaosutu <770858045@qq.com> Date: Sat, 8 Aug 2026 22:44:38 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20=E6=92=A4=E5=9B=9E=E5=90=AB?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=8C=87=E7=BA=B9=E8=AF=B4=E6=B3=95=E7=9A=84?= =?UTF-8?q?=20#5674=20=E7=99=BD=E5=90=8D=E5=8D=95=EF=BC=8C=E5=8F=AA?= =?UTF-8?q?=E8=AF=BB=E5=AD=97=E6=AE=B5=E6=B8=85=E5=8D=95=E5=B9=B6=E5=85=A5?= =?UTF-8?q?=20#5704?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...影费用保存接口字段白名单-修改接口-管理后台.md | 280 ------------------ ...04_核单域下线数据指纹-修改接口-管理后台.md | 18 ++ 2 files changed, 18 insertions(+), 280 deletions(-) delete mode 100644 changelogs-v2/2026-08/08_5674_核单导游摄影费用保存接口字段白名单-修改接口-管理后台.md diff --git a/changelogs-v2/2026-08/08_5674_核单导游摄影费用保存接口字段白名单-修改接口-管理后台.md b/changelogs-v2/2026-08/08_5674_核单导游摄影费用保存接口字段白名单-修改接口-管理后台.md deleted file mode 100644 index 8c2fae6..0000000 --- a/changelogs-v2/2026-08/08_5674_核单导游摄影费用保存接口字段白名单-修改接口-管理后台.md +++ /dev/null @@ -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 -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 -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 全绿。 diff --git a/changelogs-v2/2026-08/08_5704_核单域下线数据指纹-修改接口-管理后台.md b/changelogs-v2/2026-08/08_5704_核单域下线数据指纹-修改接口-管理后台.md index 971e108..b54594c 100644 --- a/changelogs-v2/2026-08/08_5704_核单域下线数据指纹-修改接口-管理后台.md +++ b/changelogs-v2/2026-08/08_5704_核单域下线数据指纹-修改接口-管理后台.md @@ -249,6 +249,24 @@ Content-Type: application/json - ❌ 导游/摄影 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 字段级对比