docs(order-v3): #8150 changelog 按接口类模板骨架补齐入参/响应示例/业务边界
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -8,7 +8,10 @@ change_type: "修改接口"
|
|||||||
backend_status: "deployed"
|
backend_status: "deployed"
|
||||||
gateway_status: "not_required"
|
gateway_status: "not_required"
|
||||||
frontend_status: "not_required"
|
frontend_status: "not_required"
|
||||||
target_release: "v2.1"
|
frontend_owner: ""
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
status_note: "backend_status=deployed:测试服 hl-order-service-v3 双实例(8086 / 8186)当前运行 sha=843de6401。本单修复提交 bfc10966e 不是该 sha 本身而是它的祖先,故用两条互相独立的判据:①git merge-base --is-ancestor bfc10966e 843de6401 → 退出码 0(是祖先),且 deploy-status.sh 登记 dev-v3 / 843de6401 / BEHIND=0/N;②jar mtime 2026-09-22 18:55:47,晚于 bfc10966e 的提交时刻 2026-09-22 15:24:50 +0800。两条之外还有第三条活体判据:本单契约在 18:5x 经网关实测四组请求取证(见第八节),其中三组的返回值在改动前不可能出现。gateway_status=not_required:本单零网关改动。PUT /v3/admin/group-batch/{productBatchId}/staff 是存量端点、路径未变,命中既有通配路由 Path=/v3/admin/**,未引入任何新路径段,也未改 hl-gateway 的路由或 Nacos 配置。frontend_status=not_required:本单收紧的是一个前端目前不会构造的取值。判据——对 mmg/hl-ui origin/v2.1 的调用方 grep,scopeRoles 一律由 roleMeta.memberRoles 这类字面量常量数组提供(GroupBatchStaffConfigModal.vue:153-154 写死 ['GUIDE','LEADER'] 与 ['PHOTOGRAPHER']),无任何一处会产出 null / 空串 / 纯空白元素;对 [\"\"] 与 [\" \"] 本单只改 message 文案长度、不改 code。⚠️ 覆盖边界:这是「现有实现不会命中」,不是「将来也不会」——若前端改为由下拉或接口回填拼 scopeRoles,未选中的空值会直接命中本单的 400,请按第四节的契约处理。"
|
status_note: "backend_status=deployed:测试服 hl-order-service-v3 双实例(8086 / 8186)当前运行 sha=843de6401。本单修复提交 bfc10966e 不是该 sha 本身而是它的祖先,故用两条互相独立的判据:①git merge-base --is-ancestor bfc10966e 843de6401 → 退出码 0(是祖先),且 deploy-status.sh 登记 dev-v3 / 843de6401 / BEHIND=0/N;②jar mtime 2026-09-22 18:55:47,晚于 bfc10966e 的提交时刻 2026-09-22 15:24:50 +0800。两条之外还有第三条活体判据:本单契约在 18:5x 经网关实测四组请求取证(见第八节),其中三组的返回值在改动前不可能出现。gateway_status=not_required:本单零网关改动。PUT /v3/admin/group-batch/{productBatchId}/staff 是存量端点、路径未变,命中既有通配路由 Path=/v3/admin/**,未引入任何新路径段,也未改 hl-gateway 的路由或 Nacos 配置。frontend_status=not_required:本单收紧的是一个前端目前不会构造的取值。判据——对 mmg/hl-ui origin/v2.1 的调用方 grep,scopeRoles 一律由 roleMeta.memberRoles 这类字面量常量数组提供(GroupBatchStaffConfigModal.vue:153-154 写死 ['GUIDE','LEADER'] 与 ['PHOTOGRAPHER']),无任何一处会产出 null / 空串 / 纯空白元素;对 [\"\"] 与 [\" \"] 本单只改 message 文案长度、不改 code。⚠️ 覆盖边界:这是「现有实现不会命中」,不是「将来也不会」——若前端改为由下拉或接口回填拼 scopeRoles,未选中的空值会直接命中本单的 400,请按第四节的契约处理。"
|
||||||
updated_at: "2026-09-22"
|
updated_at: "2026-09-22"
|
||||||
base: "dev-v3"
|
base: "dev-v3"
|
||||||
@@ -79,9 +82,9 @@ base: "dev-v3"
|
|||||||
|
|
||||||
## 二、变更接口清单
|
## 二、变更接口清单
|
||||||
|
|
||||||
| 方法 | 路径 | 变更类型 | 本单改了什么 |
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| `PUT` | `/v3/admin/group-batch/{productBatchId}/staff` | 入参校验收紧 | `scopeRoles` 增加元素级 `@NotBlank`;`code`/响应结构/业务语义均未变 |
|
| 1 | 保存团期 staff 配置(含扇出) | PUT | `/v3/admin/group-batch/{productBatchId}/staff` | 修改接口 | `scopeRoles` 增加元素级 `@NotBlank`(入参校验收紧);`code`、响应结构、业务语义均未变 |
|
||||||
|
|
||||||
**未新增、未删除、未改名任何端点。**
|
**未新增、未删除、未改名任何端点。**
|
||||||
|
|
||||||
@@ -91,32 +94,27 @@ base: "dev-v3"
|
|||||||
|
|
||||||
### 1. 保存团期 staff 配置(含扇出) `PUT /v3/admin/group-batch/{productBatchId}/staff`
|
### 1. 保存团期 staff 配置(含扇出) `PUT /v3/admin/group-batch/{productBatchId}/staff`
|
||||||
|
|
||||||
**请求 VO**:`BatchStaffConfigReqVO`
|
**VO**: `BatchStaffConfigReqVO` → `Result<BatchStaffConfigRespVO>`
|
||||||
**响应 VO**:`Result<BatchStaffConfigRespVO>`
|
|
||||||
**权限**:`GroupBatchPermissionGuard.PERMISSION_MANAGE`
|
**权限**: `GroupBatchPermissionGuard.PERMISSION_MANAGE`(`hl-gateway` 侧命中既有通配路由 `Path=/v3/admin/**`)
|
||||||
|
|
||||||
#### 使用场景
|
#### 使用场景
|
||||||
|
|
||||||
团期详情页「配导游 / 配摄影」弹窗保存。传入列表即为**覆盖范围内**的最终状态,保存成功后异步扇出到团内所有活跃订单的 `order_staff_assignment`(`source=GROUP_BATCH`)。
|
团期详情页「配导游 / 配摄影」弹窗保存。传入列表即为**覆盖范围内**的最终状态,保存成功后异步扇出到团内所有活跃订单的 `order_staff_assignment`(`source=GROUP_BATCH`)。
|
||||||
|
|
||||||
#### 路径参数
|
#### 入参
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| `productBatchId` | Long | 是 | **产品侧排期 ID**(`group_tour_batch.batch_id`),不是运营团期主键 `order_group_batch.group_batch_id`。两者 1:1 但值不同,传错不会报错、只会走不到团期 |
|
| `productBatchId` | path | Long | 是 | — | **产品侧排期 ID**(`group_tour_batch.batch_id`),不是运营团期主键 `order_group_batch.group_batch_id`。两者 1:1 但值不同,传错不会报错、只会走不到团期 |
|
||||||
|
| `scopeRoles` | body | `List<String>` | 否 | 数组 `@Size(min = 1)`;🆕 元素 `@NotBlank` + 元素 `@Pattern` | 本次保存覆盖的角色范围。**不传 = 整期全量覆盖**(历史行为);传了则只在这些角色内覆盖。取值域见第六.5 节。**数组不能是空数组**(清空语义只由 `staffList` 表达);🆕 **元素不能是 `null` / 空串 / 纯空白**——本单唯一的行为变化就在这里 |
|
||||||
|
| `staffList` | body | `List<Item>` | **是** | `@NotNull` + `@Valid` | 覆盖范围内的最终状态。**显式传 `[]` 即清空该范围**。缺字段或字段名拼错一律 400(审计 F-03,Refs #6950 / #7377)。本单未改动 |
|
||||||
|
| `staffList[].staffId` | body | Long | 是 | `@NotNull` | 用户域员工 ID。本单未改动 |
|
||||||
|
| `staffList[].staffRole` | body | String | 是 | `@NotBlank` + `@Pattern` | 员工角色,取值域见第六.5 节。本单未改动 |
|
||||||
|
| `staffList[].sortOrder` | body | Integer | 否 | — | 展示排序,默认 0。本单未改动 |
|
||||||
|
| `staffList[].remark` | body | String | 否 | `@Size(max = 500)` | 备注,≤500 字。本单未改动 |
|
||||||
|
|
||||||
#### 请求参数
|
#### 出参(本单未改动,列出供对照)
|
||||||
|
|
||||||
| 字段 | 类型 | 必填 | 本单是否改动 | 说明 |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| `scopeRoles` | `List<String>` | 否 | **是(元素级校验)** | 本次保存覆盖的角色范围。**不传 = 整期全量覆盖**(历史行为);传了则只在这些角色内覆盖。取值域见第六.5 节。**数组不能为空数组**(`@Size(min = 1)`,清空语义只由 `staffList` 表达);🆕 **元素不能是 `null` / 空串 / 纯空白** |
|
|
||||||
| `staffList` | `List<Item>` | **是** | 否 | 覆盖范围内的最终状态。**显式传 `[]` 即清空该范围**。缺字段或字段名拼错一律 400(审计 F-03,Refs #6950 / #7377) |
|
|
||||||
| `staffList[].staffId` | Long | 是 | 否 | 用户域员工 ID |
|
|
||||||
| `staffList[].staffRole` | String | 是 | 否 | 员工角色,取值域见第六.5 节 |
|
|
||||||
| `staffList[].sortOrder` | Integer | 否 | 否 | 展示排序,默认 0 |
|
|
||||||
| `staffList[].remark` | String | 否 | 否 | 备注,≤500 字 |
|
|
||||||
|
|
||||||
#### 响应参数(本单未改动,列出供对照)
|
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
| 字段 | 类型 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -150,8 +148,61 @@ PUT /v3/admin/group-batch/80001/staff
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
保存成功(`code` 为 `200`,本单未改变响应结构):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"productBatchId": "80001",
|
||||||
|
"groupBatchId": "90211",
|
||||||
|
"staffList": [
|
||||||
|
{
|
||||||
|
"id": 770001,
|
||||||
|
"staffId": 40001,
|
||||||
|
"staffRole": "LEADER",
|
||||||
|
"staffRoleName": "领队",
|
||||||
|
"staffName": "刘领队",
|
||||||
|
"staffPhone": "138****6677",
|
||||||
|
"avatarUrl": null,
|
||||||
|
"sortOrder": 0,
|
||||||
|
"remark": "首席领队",
|
||||||
|
"reporterRank": "NONE",
|
||||||
|
"reporterRankName": "非报账人"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"affectedOrderCount": 3
|
||||||
|
},
|
||||||
|
"traceId": null,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
🔎 本示例按 `BatchStaffConfigRespVO` 的字段逐个列出,用于说明结构;本单未改动响应结构中的任何一个字段。成功路径的活体读数不在本单的取证范围内(本单取证用的是不存在的团期,见第八节),需要成功响应的真实样本请见第十节 #8006 的交接件。
|
||||||
|
|
||||||
|
#### 空数据 / 降级响应
|
||||||
|
|
||||||
|
本单不涉及。`staffList: []` 的「清空该范围」语义未变,该情形下 `data.staffList` 返回空数组(不是 `null`),`affectedOrderCount` 照常返回扇出订单数。
|
||||||
|
|
||||||
#### 错误响应
|
#### 错误响应
|
||||||
|
|
||||||
|
🆕 `scopeRoles` 元素为 `null`(**本单新增的拒绝**):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"code":400,"message":"scopeRoles 的元素不能为空","data":null,"traceId":null,"success":false}
|
||||||
|
```
|
||||||
|
|
||||||
|
`scopeRoles` 元素为空串 / 纯空白(改前已是 400,本单只让 `message` 多带一条):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"code":400,"message":"scopeRoles 取值不在员工角色取值域内; scopeRoles 的元素不能为空","data":null,"traceId":null,"success":false}
|
||||||
|
```
|
||||||
|
|
||||||
|
全部错误码:
|
||||||
|
|
||||||
| code | 触发条件 | 本单是否改动 |
|
| code | 触发条件 | 本单是否改动 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `400` | `scopeRoles` 元素为 `null` | 🆕 **本单新增**(改前是 200 静默空操作) |
|
| `400` | `scopeRoles` 元素为 `null` | 🆕 **本单新增**(改前是 200 静默空操作) |
|
||||||
@@ -165,9 +216,14 @@ PUT /v3/admin/group-batch/80001/staff
|
|||||||
|
|
||||||
🔴 **HTTP 状态行恒为 200,`body.code` 才是真相**。本服务的业务失败与入参校验一律返 HTTP 200(`GlobalExceptionHandler` 带 `@ResponseStatus(HttpStatus.OK)`),成功码是 `200` 不是 `0`。判成败请读 `body.code`,不要读 HTTP 状态码。
|
🔴 **HTTP 状态行恒为 200,`body.code` 才是真相**。本服务的业务失败与入参校验一律返 HTTP 200(`GlobalExceptionHandler` 带 `@ResponseStatus(HttpStatus.OK)`),成功码是 `200` 不是 `0`。判成败请读 `body.code`,不要读 HTTP 状态码。
|
||||||
|
|
||||||
#### 空数据降级
|
#### 业务边界
|
||||||
|
|
||||||
本单不涉及。`staffList: []` 的"清空该范围"语义未变。
|
- **本单唯一的行为变化在 `scopeRoles` 的元素上**:`[null]` 由「HTTP 200 + 静默空操作」改为 `code: 400`。`scopeRoles` 不传、传合法值、以及 `staffList` 侧的一切语义都与改前逐字相同。
|
||||||
|
- **`scopeRoles` 不传 = 整期全量覆盖**,传了 = 只在这些角色内覆盖;`[]` 一直被拒(`@Size(min = 1)`),清空语义只由 `staffList: []` 表达。这三条改前改后一致。
|
||||||
|
- **`[null]` 改前不报错,但它做的事情是「什么都没做」**:`scopeRoles` 含 `null` 时下游范围计算得到空范围,接口返 200、`affectedOrderCount` 为 0、库里零写入。调用方若据 200 判定「已保存」,实际保存从未发生 —— 这正是本单要关掉的静默失败。
|
||||||
|
- **`[""]` / `[" "]` 改前已是 400**,本单只让 `message` 多带一条。**不要对这两个值的 `message` 写全等比较**:它是 `@Pattern` 与 `@NotBlank` 两条 violation 由 `GlobalExceptionHandler` 以 `"; "` 拼接而成,且 **拼接顺序不属于契约**(Bean Validation 不保证多个约束的执行顺序)。要判就判 `code == 400`,或判 `message` **包含**你关心的那个子串。
|
||||||
|
- **校验发生在 Spring `@Valid` 绑定层,在进 Service 之前**:因此 `productBatchId` 是否真实存在不影响这三种 400 的出现。反过来,拿到 `589552` / `589553` 说明请求已经穿过绑定层、是团期状态问题,不是参数格式问题。
|
||||||
|
- **HTTP 状态行恒为 200**,成功码是 `200` 不是 `0`,判成败一律读 `body.code`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户