hl-api-changelog/changelogs-v2/2026-08/08_5674_核单导游摄影费用保存接口字段白名单-修改接口-管理后台.md
Mimingguang da23030b62
一些检查失败了
changelog-filename-gate / validate (push) Failing after 1s
chore(changelog): #5674 白名单 implemented (hl-admin@9694bb68) 含字段对照实证
2026-08-08 17:10:49 +08:00

11 KiB

schema, ticket, title, consumer, change_type, author, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer change_type author backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 5674 核单导游/摄影费用保存接口入参字段白名单(响应只读派生字段不得回传) admin 修改接口 yaosutu(GIT) deployed verified implemented mmg 9694bb68 契约零变更的澄清说明:#5674 后保存请求 VO 对未声明字段显式拒绝(@JsonAnySetter,前端若把 GET 响应 item 原样回传会带出只读派生字段(如 sourceType被 400 拦截。本文档给出保存入参字段白名单与必须剥掉的只读字段清单。 2026-08-08 dev-v3

⚠️ 修改接口·管理后台】核单导游/摄影费用保存接口入参字段白名单(#5674 澄清)

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 Stringyyyy-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

PUT /v3/admin/order/2084000000000002978/settlement/guide-fees
Authorization: Bearer <token>
Content-Type: application/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": []
}
{"code": 200, "message": "success", "data": {"saved": true}, "success": true}

8.2 典型成功 —— 摄影(不含 expectedSourceFingerprint

PUT /v3/admin/order/2084000000000002978/settlement/photographer-fees
Authorization: Bearer <token>
Content-Type: application/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": []
}
{"code": 200, "message": "success", "data": {"saved": true}, "success": true}

8.3 业务失败 —— 多传只读字段 sourceType400

{
  "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": []
}
{"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 白名单字段:

// 伪代码示意(仅说明映射思路,非前端代码)
toSaveItem(respItem) => 只保留白名单 12 个字段其余丢弃

适用 / 不适用

  • 适用:核单页「导游费用」「摄影费用」页签的保存按钮。
  • 不适用:确认接口(/confirm)与 GET 查询接口不在本文档范围。

10. 修改前后对比

本次为澄清说明,接口签名与字段零变化,无修改前后对比。关键行为澄清:

场景 行为
保存请求只含白名单字段 正常保存200
保存请求夹带响应只读字段sourceType 等) 400,584128 指出不支持字段名

11. 影响评估 / 回滚

  • 是否破坏向后兼容:否。契约零变化。
  • 前端是否必须同步上线:建议尽快。若当前前端存在原样回传响应对象的路径,必然触发 400,需按白名单重建 payload。
  • 回滚方案:无需回滚(无代码变更)。

12. 注意事项

  • 导游接口必传 expectedSourceFingerprint64 位 hex,摄影接口不收该字段,传了会被当未知字段拒绝。
  • candidateKey 是候选行标识,手工新增行必须为 null。
  • 失败 toast 直接展示后端 message,已含具体字段原因。

13. 关联 / 联系人

13.1 链接

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 全绿。