PR #7085 合入 dev-v3(合并提交 d1b724d59),2026-09-04 部署测试环境 并经真实网关鉴权实测 7 条用例。 核心证据:15:30:33 往 group_batch_staff_slot_guide 插一行 GUIDE_ASSISTANT, 15:35:44(301 秒后)导游位候选由 7 人变 8 人——未改代码、未重启、未重新部署。 随后连打 8 次全部返回 8 人,两个实例都已回源。演示行验证后已撤除。 2 个接口的候选池来源与落库取值变更,路径/入参/出参结构全部不变: - GET /v3/admin/group-batch/:groupBatchId/staff/candidates 候选池成员由字典决定,582113 文案变更 - PUT /v3/admin/group-batch/:groupBatchId/staff staffRole 落真实 staffType 含字典初始化脚本 sql/dict_group_batch_staff_slot.sql(幂等,需手工执行)。 dict_type_id 取 8071/8072——8021/8022 在本机与测试环境都已被 contract_status / contract_platform 占用。 staff_role 取值域测试环境实测三处一致(字典 = 枚举 = @Pattern,8 项)。 frontend_status=not_required:路径、入参、出参结构均未变,前端无需改动; 但不要再硬编码候选池成员,也不要沿用自己提交的 staffRole 回显。
这个提交包含在:
@@ -0,0 +1,375 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7079"
|
||||
title: "团期人员配置位成员改由字典决定,取值域三处对齐"
|
||||
consumer: "admin"
|
||||
author: "jw(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #7085 已合入 dev-v3(合并提交 d1b724d59);2026-09-04 部署测试环境并经网关实测。含字典初始化脚本 sql/dict_group_batch_staff_slot.sql,需手工执行"
|
||||
updated_at: "2026-09-04"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期人员配置: 配置位收哪些人员类型改由数据字典决定
|
||||
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: #7085 | **Issue**: #7079 | **合并提交**: `d1b724d59`
|
||||
> **影响范围**: 管理后台「团期详情 → 配置导游/摄影」弹窗的候选池
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
**「导游位收哪些人员类型」不再写死在代码里,改由数据字典决定。业务在字典管理页面加一行,最多 5 分钟后生效,不需要发版。**
|
||||
|
||||
```
|
||||
group_batch_staff_slot_guide → GUIDE、LEADER
|
||||
group_batch_staff_slot_photographer → PHOTOGRAPHER
|
||||
```
|
||||
|
||||
另有一处**落库取值变化**:导游位并收导游与领队,改前不论选的是谁,`order_batch_staff.staff_role` 落的都是前端传的配置位名(往往是 `GUIDE`),「这一位上站的到底是导游还是领队」在数据里就丢了。现在落该人员在资源域的真实 `staffType`。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
人员类型 `staff_type` 本来就是「服务人员管理」里维护的字典,实有 7 项。而团期人员配置位只认死了导游位与摄影位两个、成员也写死在 Java 字面量里,要让研学老师也能配进导游位就得改代码发版。本次把这件事交还给业务。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 团期人员候选列表 | GET | `/v3/admin/group-batch/:groupBatchId/staff/candidates` | **候选池来源变更** | 成员由字典决定;`582113` 文案变更 |
|
||||
| 2 | 全量保存团期人员配置 | PUT | `/v3/admin/group-batch/:groupBatchId/staff` | **落库取值变更** | `staffRole` 落真实 `staffType` |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 团期人员候选列表 `GET /v3/admin/group-batch/:groupBatchId/staff/candidates`
|
||||
|
||||
**VO**: `StaffCandidateRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
「配置导游 / 配置摄影」弹窗打开时调用,渲染可选人员列表。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `groupBatchId` | Path | Long | ✅ | — | 产品侧团期 batchId |
|
||||
| `role` | Query | String | ✅ | `GUIDE` / `PHOTOGRAPHER`,大小写敏感 | 配置位标识;未知值返回 `582113` |
|
||||
|
||||
#### 出参 `Result<List<StaffCandidateRespVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `staffId` | Long | 人员 ID,**未变** |
|
||||
| `staffName` | String | 姓名,**未变** |
|
||||
| `staffType` | String | 资源域人员类型,**未变** |
|
||||
| `staffPhone` | String | 脱敏手机号,**未变** |
|
||||
| `assigned` | Boolean | 是否已选,**未变** |
|
||||
|
||||
> **出参结构完全没有变化**,变的是这个列表里会出现哪些人——由字典 `group_batch_staff_slot_*` 决定。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/group-batch/990707900901/staff/candidates?role=GUIDE
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"staffId": 1002,
|
||||
"staffName": "李雪梅",
|
||||
"staffType": "GUIDE",
|
||||
"staffPhone": "138****1002",
|
||||
"assigned": false
|
||||
},
|
||||
{
|
||||
"staffId": 1005,
|
||||
"staffName": "刘大山",
|
||||
"staffType": "LEADER",
|
||||
"staffPhone": "138****1005",
|
||||
"assigned": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
字典读空或字典服务不可用时**不报错**,回落到内置默认值(导游位 `GUIDE`+`LEADER`,摄影位 `PHOTOGRAPHER`)并打 WARN,候选列表照常返回。资源域无可用人员时返回空数组:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": []
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
配置位标识未知(含小写 `guide`、`DRIVER`):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 582113,
|
||||
"message": "人员配置位不合法,请检查配置位标识",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 配置位成员来自字典,改字典后各实例最多 5 分钟内生效(本地缓存有界 TTL)
|
||||
- 字典里写了取值域外的人员类型会被忽略并 WARN,其余行照常生效
|
||||
- 字典整体读不到时回落内置默认值,行为与改前一致
|
||||
- `role` 大小写敏感,小写不认(沿用既有口径)
|
||||
- 司机不设配置位,由车务派车投影产生
|
||||
|
||||
### 2. 全量保存团期人员配置 `PUT /v3/admin/group-batch/:groupBatchId/staff`
|
||||
|
||||
**VO**: `BatchStaffConfigReqVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
「配置导游 / 配置摄影」弹窗点保存时调用,全量覆盖该团期人员配置。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `groupBatchId` | Path | Long | ✅ | — | 产品侧团期 batchId |
|
||||
| `staffList` | Body | Array | ❌ | 传空则清空 | 全量覆盖 |
|
||||
| `staffList[].staffId` | Body | Long | ✅ | — | 资源域人员 ID |
|
||||
| `staffList[].staffRole` | Body | String | ✅ | 取值域本次扩至 8 项 | 见下方取值域说明 |
|
||||
| `staffList[].sortOrder` | Body | Integer | ❌ | 缺省 0 | 展示排序 |
|
||||
|
||||
#### 出参 `Result<BatchStaffConfigRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `staffList` | Array | 落库后的人员快照;其中 `staffRole` **取值口径变更** |
|
||||
| `affectedOrderCount` | Integer | 扇出到的活跃子订单数,**未变** |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"staffList": [
|
||||
{
|
||||
"staffId": 1005,
|
||||
"staffRole": "GUIDE",
|
||||
"sortOrder": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
请求传的是配置位名 `GUIDE`,但 1005 在资源域是领队,出参与落库都归一为 `LEADER`:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"staffList": [
|
||||
{
|
||||
"staffId": 1005,
|
||||
"staffRole": "LEADER",
|
||||
"staffName": "刘大山",
|
||||
"staffPhone": "138****1005",
|
||||
"sortOrder": 0
|
||||
}
|
||||
],
|
||||
"affectedOrderCount": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
`staffList` 传空数组即清空该团期全部人员配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"staffList": [],
|
||||
"affectedOrderCount": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
`staffRole` 不在取值域内:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "员工角色不在取值域内,取值域对齐 SettlementStaffRoleEnum 与 staff_role 字典",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 落库 `staffRole` 只在**配置位内部**归一:请求角色能解析到配置位、且该人员真实类型也在这个配置位成员里时才替换
|
||||
- 跨位不匹配(领队被配到摄影位)沿用入参,本次不做拒绝
|
||||
- `DRIVER` / `OTHER` 不属于任何配置位,沿用入参
|
||||
- 资源域反查不到人员类型时沿用入参
|
||||
- 导游位有人(`GUIDE` 或 `LEADER`)即置 `guide_ready`,与改前一致
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- **候选池成员不再由代码保证**,前端不要硬编码「导游位只会出现导游和领队」;以接口返回为准。
|
||||
- **`staffRole` 落库值可能与提交值不同**:提交配置位名,落库是该人员的真实人员类型。前端若需回显角色,读出参里的 `staffRole`,不要沿用自己提交的值。
|
||||
- **`staffRole` 取值域本次由 5 项扩至 8 项**:新增 `GUIDE_ASSISTANT` / `STUDY_TEACHER` / `LIFE_TEACHER`。
|
||||
- **改字典不是立即全局生效**:各实例本地缓存 5 分钟有界 TTL,期间不同实例口径可能不同,只影响候选池多列/少列一类人员。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
无 DDL、无 Flyway 迁移。**但需手工执行一个字典初始化脚本**:
|
||||
|
||||
```
|
||||
sql/dict_group_batch_staff_slot.sql 新增,幂等,可重复执行
|
||||
```
|
||||
|
||||
| 表 | 变化 |
|
||||
|---|---|
|
||||
| `sys_dict_type` | 新增 2 行:`group_batch_staff_slot_guide`(8071) / `group_batch_staff_slot_photographer`(8072) |
|
||||
| `sys_dict_data` | 新增 3 行配置位成员;`staff_role` 补 `STUDY_TEACHER` / `LIFE_TEACHER` 两行 |
|
||||
| `order_batch_staff.staff_role` | 结构未变;**新写入的取值口径变了**,存量行不动 |
|
||||
|
||||
> `dict_type_id` 取 8071/8072 而非号段顺序的 8021/8022——后者在本机与测试环境**都已被** `contract_status` / `contract_platform` 占用(两处实测确认)。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 字典未初始化 → 回落内置默认值,行为与改前完全一致
|
||||
- 字典含非法人员类型 → 忽略该行 + WARN,其余生效
|
||||
- 字典全部非法 → 视同读空,回落默认值
|
||||
- 字典行 `dictValue` 为空或空白 → 直接跳过,不刷告警
|
||||
- Feign 返回 null / 失败 / 抛异常 → 三种都回落默认值,不把异常透给调用方
|
||||
- `role` 小写 / 未知 → `582113`
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
**新增字典类型 2 个**:`group_batch_staff_slot_guide`、`group_batch_staff_slot_photographer`。取值域 = 资源域 `staff_type`。
|
||||
|
||||
**`staff_role` 字典扩至 8 项**,与 `SettlementStaffRoleEnum` 和接口 `@Pattern` 三处对齐(测试环境实测三处完全一致)。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项 | 变更前 | 变更后 |
|
||||
|---|---|---|
|
||||
| 配置位成员 | 写死在 3 处 Java 字面量 | 由字典决定,加一行即生效 |
|
||||
| 扩一种人员类型 | 改代码 + 发版 | 改字典,最多 5 分钟生效 |
|
||||
| 落库 `staffRole` | 前端传的配置位名 | 该人员真实 `staffType` |
|
||||
| `staffRole` 取值域 | 5 项 | 8 项 |
|
||||
| `582113` 文案 | 「只支持导游位与摄影位」 | 「请检查配置位标识」 |
|
||||
| 字典不可用 | — | 回落内置默认值,不中断 |
|
||||
| 路径 / 入参 / 出参结构 | — | **全部不变** |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
| 维度 | 评估 |
|
||||
|---|---|
|
||||
| 兼容性 | 字典未初始化时回落默认值,与改前**完全一致**,不会坏 |
|
||||
| 前端 | **无需改动**。路径、入参、出参结构均未变;但不要再硬编码候选池成员,也不要沿用自己提交的 `staffRole` 回显 |
|
||||
| 数据 | 无 DDL;需手工执行一个幂等字典脚本;存量 `staff_role` 行不动 |
|
||||
| 性能 | 每个配置位一次字典 Feign,5 分钟缓存;候选池按类型逐类拉取,类型多一种多一次资源域调用 |
|
||||
| 回滚 | `git revert`;字典行留着无害(回滚后代码不读它) |
|
||||
| 风险 | 中低。主要风险是下游若有按 `staff_role == 'GUIDE'` 硬比对处会受落库取值变化影响;order-v3 内已全部改为按配置位归组 |
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **零影响**:两个接口的路径、HTTP 方法、入参字段、出参结构
|
||||
- **零影响**:摄影位口径,字典默认仍只收 `PHOTOGRAPHER`
|
||||
- **零影响**:staff-fees 费用录入分流——只加枚举取值域,未动 `SettlementStaffFeeDetailCodec` 的 `GUIDE`/`PHOTOGRAPHER` 两族判定
|
||||
- **零影响**:存量 `order_batch_staff` / `order_staff_assignment` 数据
|
||||
- **零影响**:`DRIVER` / `OTHER` 角色的处理
|
||||
- **未新建端点**
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
✅ 2026-09-04 于测试环境网关实测,真实鉴权(管理端 `test_admin`,角色定制师)。
|
||||
|
||||
- 网关 `https://api.test.1814.love:9443`,分支 `dev-v3`,合并提交 `d1b724d59`
|
||||
- 部署方式:双实例滚动更新(8186 → 8086),各 10s 就绪,零停机
|
||||
- 部署内容经部署面板 `/api/git/backend` 交叉核对:构建时点 `d1b724d59` 已是 `dev-v3` 顶端
|
||||
- 字典初始化脚本已在测试环境执行,幂等
|
||||
|
||||
| # | 用例 | 期望 | 实测 |
|
||||
|---|---|---|---|
|
||||
| 1 | 导游位候选(字典 GUIDE+LEADER) | 与改前一致 | ✅ 7 人 |
|
||||
| 2 | 摄影位候选 | 口径不变 | ✅ 1 人 |
|
||||
| 3 | `role=DRIVER` | `582113` 新文案 | ✅ 「请检查配置位标识」 |
|
||||
| 4 | 导游位选领队(提交 `GUIDE`) | 落库真实类型 | ✅ 出参与落库均为 `LEADER` |
|
||||
| 5 | 同场景 `guide_ready` | 仍置位 | ✅ 1 |
|
||||
| 6 | `staff_role` 取值域三处对齐 | 字典 = 枚举 = `@Pattern` | ✅ 8 项完全一致 |
|
||||
| 7 | **字典加一行 `GUIDE_ASSISTANT`** | 无需发版即生效 | ✅ **301 秒后候选 7 → 8 人,陈小燕出现;两个实例连打 8 次均为 8** |
|
||||
|
||||
**观察**:双实例持续 running,服务日志最近 200 行 `ERROR` / `Exception` 命中 **0** 条;配置位字典相关 WARN **0** 条,说明字典读取正常、未走降级。
|
||||
|
||||
用例 7 是本单的核心证据:15:30:33 往字典插一行,15:35:44(301 秒后)候选列表由 7 人变 8 人——**没有改代码、没有重启服务、没有重新部署**。延迟符合 5 分钟有界 TTL 的设计;随后连打 8 次全部返回 8 人,两个实例都已回源。演示用的那一行验证后已撤除,出厂默认仍是 `GUIDE` + `LEADER`。
|
||||
|
||||
**本地单测**:**431 例全过**。新增 `GroupBatchStaffSlotResolverTest` 14 例(正常读取、加行即生效、去重、TTL 命中缓存、读空/Feign null/Feign 异常三种降级、降级后判定仍工作、非法值过滤、全非法回落、空白值跳过、未知角色无配置位、枚举大小写敏感);`GroupBatchStaffConfigServiceTest` 新增 3 例覆盖落库取值三个分支。
|
||||
|
||||
**测试数据**:一律 `T7079-` 前缀,验证后物理删除。`test_admin` 口令一次性置换,取到令牌后立即按备份还原,逐字节一致;共享库写入全程持 `test-db` 租约。
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
- #7065(Refs #7063 第 1 波)本 PR 承接它,`StaffSlotRoles` 由事实源退化为兜底默认值
|
||||
- 裁决来源:#7063 的 D-1 / D-3 / D-4(jw 2026-09-04)
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 团期需求文档:`docs/group/`(dev-v3 分支)
|
||||
- 字典初始化脚本:`sql/dict_group_batch_staff_slot.sql`
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- **Issue**: #7079
|
||||
- **PR**: #7085(合并提交 `d1b724d59`)
|
||||
在新工单中引用
屏蔽一个用户