--- 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>` | 字段 | 类型 | 说明 | |------|------|------| | `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` | 字段 | 类型 | 说明 | |------|------|------| | `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`)