1. 字典服务(hl-user-service)不可用时,靠字典才能纳入配置位的角色 (GUIDE_ASSISTANT / STUDY_TEACHER / LIFE_TEACHER)**同样**返回 582117—— 原文「字典读挂时不会变成一律拒绝」只对内置兜底角色成立。582117 现在有两种成因, 排查顺序改为「先确认字典服务可用性,再看字典内容」。 2. 请求示例原用了修复后必被 582117 拒的 payload,却紧跟 200 成功响应示例,已换成 GUIDE。 3. 补记两条边界:5 分钟实例缓存窗口;存量脏行会被前端回显提交并逐行校验, 可能让整次保存(含不传 scopeRoles 的整期覆盖)被 582117 拦下。 同步订正 errorcode javadoc、resolver javadoc、单测 javadoc,并新增一条把降级态 行为钉住的用例(GroupBatchStaffRoleSlotGuardTest#dictUnavailable_dictRoleIsRejectedKnownBoundary)。
这个提交包含在:
@@ -14,6 +14,7 @@ target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "工单 #8194 补的是 #8148 留下的残留缺口。角色↔人员类型的守卫 582117 判的是「该角色在配置位字典里查不到归属位」,而它的分流判据(GroupBatchStaffSlotResolver#participatesInSlotsByFallback)读的是编译期常量兜底名单,只认 GUIDE / LEADER / PHOTOGRAPHER。问题是 #7079 / #8148 立下的能力恰恰是「业务在字典页面加一行就能把某个人员类型配进配置位、不发版」——靠字典新增出来的角色永远不在这张常量名单里,于是「字典给某位加一行 → 有人按它配了人 → 那行又被删掉」这一形态仍走静默放行分支:接口 200、脏行落库、并异步扇出到团内全部活跃订单,该角色的 582114 当场失效,要到结算对账才发现人岗对不上。关键一点是撞上它不需要有人手搓请求——resolveStoredStaffRole 会把落库的 staff_role 改写成人员的真实类型(字典角色),前端回显后原样提交回来,系统自己就会产出那个落进缺口的 staffRole 值。修法(2026-09-23 定案 B,判据翻面):把「本该有位」的判据从「在不在兜底名单里」换成「不在结构性豁免闭集 STRUCTURALLY_SLOT_EXEMPT_ROLES(= DRIVER / OTHER)里」,豁免闭集落成显式常量集合并从 SettlementStaffRoleEnum 取值,方法名同步改为 requiresSlotMembership(改完它不再读 fallbackTypes(),名字与语义必须同批改)。行为变化:GUIDE_ASSISTANT / STUDY_TEACHER / LIFE_TEACHER 三个「只可能靠字典纳入配置位」的角色,在「字典里查不到归属位」时由静默 200 落库变为返回 582117;DRIVER / OTHER 照旧放行(OTHER 的常态就是不属于任何配置位,保护它会拒掉每一次正常的杂项人员配置,那是删功能不是补洞)。已知边界(按 #8194 决策点 4 有意保留、不在本单修):OTHER 在 VALID_STAFF_TYPES 里,业务能把 OTHER 写进某个位的字典再删掉,那一形态仍走静默放行。生产代码由 PR #8259 合入 dev-v3(squash 提交 f6d21648b)。2026-09-23 测试服已实测:PUT /v3/admin/group-batch/2102640646949105666/staff 提交 staffRole=GUIDE_ASSISTANT 返回 code=582117(HTTP 200,文案完整),读回确认零写入;同端点 staffRole=DRIVER 与 staffRole=OTHER 均返回 code=200 成功;探测插入的两行已用 scopeRoles=[DRIVER,OTHER] + staffList=[] 还原,读回与探测前逐字段一致。契约(路径 / 方法 / 权限 / 入参字段与取值域 / 成功响应结构)零变更——BatchStaffConfigReqVO 只加了 javadoc(含该表)。前端结论(2026-09-23):5821xx 全族在 hl-ui v2.1 无专门分支,统一由拦截器按 code 非成功 toast 后端 message;582117 文案已点名角色并指向配置位字典页,完整直出即可。命中后正确动作是去字典页补行,重试仍被拒不会误操作——与 #8123/#8148 既定拍板一致,维持通用 toast,不做专门引导,零改动闭环。"
|
||||
updated_at: "2026-09-23"
|
||||
# ⚠️ 本条目于 2026-09-23 由独立对抗评审复核后订正过三处:①降级态(字典服务不可用)对字典角色同样返回 582117,原文「不会变成一律拒绝」只对内置角色成立;②请求示例原用了修复后必被拒的 payload;③补记缓存窗口与存量脏行两条边界。
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
@@ -116,9 +117,11 @@ Host: api.test.1814.love:9443
|
||||
Authorization: Bearer <admin token>
|
||||
Content-Type: application/json
|
||||
|
||||
{"staffList":[{"staffId":1002,"staffRole":"GUIDE_ASSISTANT","sortOrder":0,"remark":"hl8194-gateway-ac1"}]}
|
||||
{"staffList":[{"staffId":1002,"staffRole":"GUIDE","sortOrder":0,"remark":"hl8194-ok"}]}
|
||||
```
|
||||
|
||||
> ⚠️ 把上面的 `staffRole` 换成 `GUIDE_ASSISTANT`(或其他字典角色)在**修复后**会返回 582117,不再是 200——见「错误响应」。这正是本单的行为变化。
|
||||
|
||||
#### 响应示例
|
||||
|
||||
成功时:
|
||||
@@ -143,7 +146,9 @@ Content-Type: application/json
|
||||
|
||||
`staffList` 传空数组即清空覆盖范围内的配置,返回 200,`staffList` 为空数组、`affectedOrderCount` 为实际扇出订单数。
|
||||
|
||||
配置位字典读挂 / 读空时**不会**变成一律拒绝:`typesOf` 回落内置默认值(导游位 `GUIDE+LEADER`、摄影位 `PHOTOGRAPHER`),内置角色的保存照常成功——这是 #7079 写下的降级保证,本单未动。
|
||||
配置位字典读挂 / 读空时,`typesOf` 回落内置默认值(导游位 `GUIDE+LEADER`、摄影位 `PHOTOGRAPHER`):**内置角色**(`GUIDE` / `LEADER` / `PHOTOGRAPHER`)的保存照常成功,这条降级保证本单未动。
|
||||
|
||||
⚠️ **但内置名单之外的角色不是这样**:`GUIDE_ASSISTANT` / `STUDY_TEACHER` / `LIFE_TEACHER` 不在兜底名单里,字典服务不可用期间它们依然「查不到归属位」⇒ **同样返回 582117**。也就是说 582117 现在有两种成因:①字典可读、但该角色不在任何配置位里(修复动作是补回那一行字典);②字典服务不可用、且该角色不是内置兜底成员(修复动作是恢复字典服务)。**文案当前不区分两者**,排查时请先确认字典服务可用性。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
@@ -166,7 +171,7 @@ Content-Type: application/json
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **582117 不是「字典读挂」的兜底**:Feign 失败或字典整表为空时 `typesOf` 回落内置默认值,归属位照样查得到,走不到这个码。能触发它的只有「**字典非空、但这个角色被从里面删掉了**」(或该角色从没在任何位的字典里出现过)。
|
||||
- **582117 有两种成因,排查顺序是先服务后内容**:①字典可读、但该角色不在任何配置位里(被删掉,或从来没配过);②**字典服务(hl-user-service)不可用**而该角色又不是内置兜底成员(`GUIDE` / `LEADER` / `PHOTOGRAPHER`)。此时文案说的「字典是否被误删」会指错方向——先去字典页面看会发现那行可能压根没删过。
|
||||
- **被判定的不是「你这个角色对不对」,而是「这个角色的归属位是不是丢了」**。角色本身合法(在 `@Pattern` 取值域内)但字典里没有它的位 → 582117;位找得到但人岗不符 → 582114。两者互补。
|
||||
- **修复动作在配置位字典页面,不在这个弹窗里**,所以文案点名角色并指向字典,适合原样透出给运营。
|
||||
- **豁免闭集是 `DRIVER` 与 `OTHER`**,两者在任何字典状态下都不做角色↔人员类型校验,582117 与 582114 都不触发。
|
||||
@@ -221,7 +226,9 @@ Content-Type: application/json
|
||||
- **字典读挂 / 读空不降级成拒绝**:`typesOf` 回落内置默认值,内置角色(`GUIDE` / `LEADER` / `PHOTOGRAPHER`)照常保存。
|
||||
- **将来往 `@Pattern` 里加第 9 个取值**而忘了判断它属不属于豁免闭集时,新角色默认落进「本该有位」侧 ⇒ 那一侧是**拒绝**(响亮失败、当场被发现),不是静默放行。失败方向正确。
|
||||
- **已知边界,不在本单修**:`OTHER` 在 `VALID_STAFF_TYPES` 里,业务**能**把 `OTHER` 写进某个位的字典再删掉,那一形态仍走静默放行。判据是:`OTHER` 的常态化就是不属于任何配置位,把它移出豁免闭集会拒掉每一次正常的杂项人员配置;而「把『其他』纳入『导游位』」本身是配置位语义的误用(等于宣告「其他类型的人算导游」),错的是那行字典。
|
||||
- **存量脏行不在本单范围**:字典行被删之后库里已有的 `staff_role='GUIDE_ASSISTANT'` 行不再属于任何配置位,整位守卫 582116 也判不到它,这些行不会被 `scopeRoles` 的删除窗口覆盖或重建。治理另立工单。
|
||||
- **存量脏行的连带影响(本单不修,但必须知道)**:字典行被删之后库里已有的 `staff_role='GUIDE_ASSISTANT'` 行不再属于任何配置位,整位守卫 582116 也判不到它,这些行不会被 `scopeRoles` 的删除窗口覆盖或重建。⚠️ 更要紧的是:**这类存量行会被前端原样回显提交**,而校验是**逐行**做的——只要该团期的保存请求里带上这样一行且 `staffRole` 仍在缺口集合内,**整次保存(含不传 `scopeRoles` 的整期覆盖)都会返回 582117**。是否已有这类存量行未在本次取证中统计(生产库只读盘点属独立动作)。治理另立工单。
|
||||
- **缓存窗口**:`typesOf` 是固定 5 分钟 TTL 的实例本地缓存(`@VettedLocalCache`)。字典改完到全部实例生效之间,同一请求可能一台放行、一台返回 582117。所以「删回后不再静默放行」是**有界**结论:最长 5 分钟、按实例收敛。
|
||||
- **判据不认识「历史」**:`requiresSlotMembership` 只看字典**当前**内容。因此「从没在任何配置位字典里出现过」与「加过又被删回」走同一条拒绝分支,而文案一律写成「是否被误删」。首次配置某类人员(业务还没往字典加那一行)时会看到这句——正确动作是**去对应配置位字典加一行**,不是去查删除记录。
|
||||
|
||||
---
|
||||
|
||||
@@ -256,7 +263,8 @@ Content-Type: application/json
|
||||
| `staffRole=STUDY_TEACHER` / `LIFE_TEACHER`(同上) | 同上 | 582117,零写入 |
|
||||
| `staffRole=LEADER` 被从非空字典删掉 | 582117(#8148 已建立) | **不变** |
|
||||
| `staffRole=DRIVER` / `OTHER` | 放行 | **不变** |
|
||||
| 字典读挂 / 读空 | 回落内置默认值,内置角色放行 | **不变** |
|
||||
| 字典读挂 / 读空(内置角色) | 回落内置默认值,放行 | **不变** |
|
||||
| 字典读挂 / 读空(字典角色 `GUIDE_ASSISTANT` 等) | **静默 200** | **582117,零写入**(降级态无法区分「被删掉」与「读不到」,见「六、边界行为」) |
|
||||
| 判据实现 | `participatesInSlotsByFallback` 读编译期常量 `fallbackTypes()` | `requiresSlotMembership` 判「不在结构性豁免闭集 `{DRIVER, OTHER}` 里」 |
|
||||
| 路径 / 入参 / 权限 / 成功响应结构 | — | **零变化** |
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户