16 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 7608 | 团期退团提交接新码 group-batch:withdraw:submit(589507,带 nacos 灰度开关);团期核单 finalize 判权前移到幂等分支之前 | admin | jw(GIT) | 修改接口 | deployed | verified | not_required | 已部署 dev-v3 到 TEST 并实打验证:退团提交七角色矩阵逐格吻合、审批端点角色门对照、nacos 开关 false→还原往返(md5 逐字还原)、核单 finalize 四个非财务角色对已有快照的团期 589507 且不带数据,全程零写入。前端判 not_required(2026-09-27,与批2 同口径守既定架构「可见即可点+589507 兜底、后端不下发 group-batch 按钮码无码可接」):grep 实证 submitGroupBatchWithdraw 无 silentError、消费方 WithdrawSubOrderModal catch 无按码分支,589507 拦截器自动透 message;团级 settlement/finalize 前端零封装零消费(团期核单页 Epic #8361 ③ defer 中);退单入口唯一显隐门为 #8154 isGroupBatchManager;§四 按码控显隐建议同批2 被否,finalize changelog 自述不要求前端改动。 | 2026-09-27 | dev-v3 |
order-v3: 团期退团提交收口判权(批 3)+ 核单 finalize 判权前移
服务: hl-order-service-v3(种子在 hl-user-service)
PR: #8438(已合入 dev-v3,合并提交 b8c9b463f);种子 PR #8434(b95f0b443,先行上线)
Issue: #7608
⚠️ 关键变化
🔴 withdraw(退团提交,GB-ADM-070)从「团期管理员以外的任何后台角色都能调」变为「必须持 group-batch:withdraw:submit」,无权限返回 589507。 这是一个新权限码,授 ADMIN / CUSTOMIZER(SUPER_ADMIN 短路放行),不授 FINANCE。
🔴 settlement/finalize(完成团期核单)对非财务角色一律 589507,包括该团期已有核单快照的情况。 改前判权只在锁内事务层,而「已有快照 → 幂等返回」的分支排在判权之前:快照一旦存在,任意后台角色调 finalize 都能拿到整份核单数据,绕过读端点 reports/group 的 group-batch:finance:view。
🟢 两个端点的响应结构、成功码、业务错误码全部不变。 有权限的调用方行为与改前一致。
一、背景
#7455 自称做了「全端点扫描」,但漏掉 GroupBatchActionController 里的 4 个端点(按类计数的审计方法缺陷)。#7608 分四批收口:批 0 旁路观察、批 1 cancel-group、批 2 transfer-in / transfer-candidates(均已上线),本单是批 3,至此 4 个端点全部收口。
为什么 withdraw 要单独立码:退团是「建单 → 审批」两段(#7100),审批端点走 WithdrawApprovalGuard 角色门,只放 ADMIN / SUPER_ADMIN。若提交也接 group-batch:manage,提交集合与审批集合完全相同;接 group-batch:view 又会把提交能力给到 FINANCE。故新立 group-batch:withdraw:submit,只授真实提交方(jw 2026-09-27 定案)。
finalize 的缺口是怎么发现的:本单同时上了构建期门禁 GroupBatchAdminWriteEndpointPermissionArchTest(#7608 AC-7),要求团期 admin 控制器的每个写端点在「自身 / 直接委托的方法及其私有汇聚方法」里看得到判权。它首跑就报出 finalize 的判权藏在第二跳。订单级核单的同名编排器 SettlementFinalizeOrchestrator 首句就判权,团期版漏了这一句。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 退单户·提交退单申请(GB-ADM-070) | POST | /v3/admin/order/group-batch/:groupBatchId/sub-order/:orderId/withdraw |
修改 | 新增判权:需 group-batch:withdraw:submit,无权限 589507;响应结构与业务错误码不变 |
| 2 | 完成团期核单 | POST | /v3/admin/order/group-batch/:groupBatchId/settlement/finalize |
修改 | 判权前移:非 SUPER_ADMIN / ADMIN / FINANCE 在幂等分支也 589507;响应结构不变 |
三、接口详情
1. 退单户·提交退单申请 POST /v3/admin/order/group-batch/:groupBatchId/sub-order/:orderId/withdraw
VO: WithdrawSubOrderReqVO → Result<WithdrawSubOrderRespVO>
使用场景
管理后台团期详情「退单」:为某户子订单提交退单申请,只建审批单,执行退团与退款在审批通过时。本单起需要 group-batch:withdraw:submit 权限。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | ✅ | 团期主键 | 行为不变 |
| orderId | Path | Long | ✅ | 子订单 ID | 行为不变 |
| reason | Body | String | ❌ | ≤512 字 | 退团原因;请求体整体可省略。行为不变 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| data | WithdrawSubOrderRespVO |
结构完全不变 |
请求示例
POST /v3/admin/order/group-batch/2104057019403218945/sub-order/2104057019403218999/withdraw HTTP/1.1
X-Admin-Id: 1001
X-Admin-Role: CUSTOMIZER
Content-Type: application/json
{"reason": "客户临时有事"}
响应示例
{
"code": 200,
"message": "成功",
"data": {},
"success": true
}
空数据 / 降级响应
无分页语义。降级:nacos group-batch.acl.enforce.withdraw-submit 置 false 时,判权退化为旁路观察日志(GB_ACL_PROBE,intendedPermission=group-batch:withdraw:submit)而非静默放行——团期管理员以外的角色恢复放行、与改前一致,但每次调用仍在服务端留痕。开关默认 true。团期管理员的 581008 不读本开关。
错误响应
| 码 | 符号 | 触发 | 本单 |
|---|---|---|---|
| 589507 | GROUP_BATCH_PERMISSION_DENIED |
调用方不持 group-batch:withdraw:submit |
🆕 本端点新增(码本身早已存在) |
| 581008 | ORDER_VIEW_FORBIDDEN |
调用方是团期管理员(#8154,排在判权之前) | 不变 |
| 589500 / 581007 / 其它业务码 | — | 团期不存在 / 订单不存在 / 各项业务前置 | 不变 |
589507 实打响应体(TEST,2026-09-27,FINANCE):
{
"code": 589507,
"message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)",
"data": null,
"traceId": null,
"success": false
}
业务边界
- 拒绝时零写入:判权在 Controller 入口,排在 Service 任何库读写之前;对不存在的团期也先返回
589507。 - 提交集合 ≠ 审批集合:提交 {
SUPER_ADMIN,ADMIN,CUSTOMIZER};审批(GB-ADM-072~075,WithdrawApprovalGuard角色门,本单未改){SUPER_ADMIN,ADMIN}。定制师能提交、不能审批;FINANCE两侧都不在。 - 团期管理员:仍被
581008拦在最前(#8154)。「退单提交放开团期管理员」已另开单跟进,本单不含。
2. 完成团期核单 POST /v3/admin/order/group-batch/:groupBatchId/settlement/finalize
VO: Result<GroupSettlementRespVO>(无请求体)
使用场景
团期核单页「完成核单」:按团聚合落核单快照;重复提交幂等返回既有快照。只有资金写角色(SUPER_ADMIN / ADMIN / FINANCE)能调,口径本身不变,本单只把判权挪到幂等分支之前。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | ✅ | ≥1 | 团期主键。行为不变 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| data | GroupSettlementRespVO |
结构完全不变;无权限时为 null |
请求示例
POST /v3/admin/order/group-batch/2104008377741012994/settlement/finalize HTTP/1.1
X-Admin-Id: 1001
X-Admin-Role: FINANCE
响应示例
{
"code": 200,
"message": "成功",
"data": {"finalized": true, "groupSettlementId": "2104009943290146817", "groupBatchId": "2104008377741012994"},
"success": true
}
空数据 / 降级响应
无分页语义,无开关(它本就应判权,不设降级)。已有快照时财务写角色照旧幂等返回同一份快照(TEST 实测 groupSettlementId 不变、快照表行数不变)。
错误响应
| 码 | 符号 | 触发 | 本单 |
|---|---|---|---|
| 589507 | GROUP_BATCH_PERMISSION_DENIED |
非资金写角色,含已有快照的幂等分支 | 🆕 幂等分支新增;无快照分支改前即为 589507 |
| 其它业务码 | — | 团期不存在、核单前置不满足等 | 不变 |
589507 实打响应体(TEST,2026-09-27,CUSTOMIZER 对已有快照的团期):
{
"code": 589507,
"message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)",
"data": null,
"traceId": null,
"success": false
}
业务边界
- 改前的泄露面:定制师 / 房务 / 车务 / 团期管理员对一个已有核单快照的团期调
finalize,改前返回200+ 整份核单(团总成本、分摊、人均、预支、应收已收等);改后589507,data为null,且连快照都不查。 - 锁内事务层那道判权保留作纵深兜底。
四、契约约束与正确调用方式
- 退单入口建议按
group-batch:withdraw:submit控制可见性;589507直接透出message即可。 finalize入口的可见性口径不变(资金写角色);本单不要求前端改动。- 权限码与角色绑定没有运行期写入口(只能走 Flyway 种子)。
五、数据库行为
- hl-user-service 新增种子
V20260927_8404_7608__add_group_batch_withdraw_submit_permission.sql(PR#8434):admin_permission插 1 行(新码),admin_role_permission插 2 行(ADMIN、CUSTOMIZER),INSERT IGNORE幂等,零 DDL。 - 上线顺序:种子 15:28:49 在 TEST 执行(rank 168),过完 10 分钟权限缓存后才部署 order-v3(16:00),避免
#7154那种「非超管全员 589507」。 - order-v3 零 DDL、零数据变更。
六、边界行为
- 有权限的调用方:与改前一致。
- 无权限的调用方:
withdraw改前进入业务(可建审批单),改后589507且零写入;finalize见上。 - 角色头缺失:
withdraw走GroupBatchPermissionGuard,finalize走SettlementWriteGuard,两者对「有请求但角色为空」都 fail-closed(589507)。 - user-service 不可达:权限查询 fail-closed。
六.6、修改前后对比
| 角色 | withdraw 改前 → 改后 |
finalize(已有快照)改前 → 改后 |
|---|---|---|
SUPER_ADMIN |
正常 → 正常 | 正常 → 正常 |
ADMIN |
正常 → 正常 | 正常 → 正常 |
CUSTOMIZER |
正常 → 正常(持新码) | 200 + 快照 → 589507 |
FINANCE |
正常 → 589507 | 正常 → 正常 |
ROOM_MANAGER / VEHICLE_MANAGER |
正常 → 589507 | 200 + 快照 → 589507 |
GROUP_BATCH_MANAGER |
581008 → 不变 | 200 + 快照 → 589507 |
六.7、影响评估
- 前端:
FINANCE、房务、车务会在退单入口拿到589507;若按码控制可见性则看不到入口。finalize对合法调用方无变化。 - 后端:
withdraw每次调用多一跳 Feign 判权。 - 回滚:
withdraw改 nacos key 为false,TEST 实测热刷新约 3 秒,不需重发服务;finalize需回滚代码。
七、不影响范围
- 退单审批四端点(GB-ADM-072~075):角色门不变,本单未改。
cancel-group/transfer-in/transfer-candidates(批 1、批 2)与成团 / 流团 / 名额 / 预支:判权口径不变。- 团期核单
confirm/reports/group、团期归档settle/reopen-settle:判权口径与落点不变(新门禁核过均在可见位置)。 - 小程序端(
consumer: mp):本单端点均为管理后台端点,无影响。
八、测试环境已验证
环境:TEST(https://api.test.1814.love) 验证时间:2026-09-27 16:03~16:10
构建身份:TEST 检出 dev-v3 @ b8c9b463f(本单合并提交),order-v3 16:00 重启。零写入判据:旧字节里 FINANCE 调 withdraw 走旁路观察、会进入业务校验(589500),新字节才会 589507;旧字节里定制师对已有快照的团期调 finalize 返回 200 + 快照,新字节才会 589507。下表两处拦截只可能来自本单字节。
8.1 withdraw 七角色矩阵(强制态)
真实团期 2104057019403218945 + 不存在的子订单;另用不存在的团期再打一轮,排除「判权排在存在性校验之后」。
| 角色 | 真实团期 | 不存在的团期 |
|---|---|---|
| SUPER_ADMIN | 581007 过守卫 |
589500 过守卫 |
| ADMIN | 581007 过守卫 |
589500 过守卫 |
| CUSTOMIZER | 581007 过守卫 |
589500 过守卫 |
| FINANCE | 589507 拦截 |
589507 拦截 |
| ROOM_MANAGER | 589507 拦截 |
589507 拦截 |
| VEHICLE_MANAGER | 589507 拦截 |
589507 拦截 |
| GROUP_BATCH_MANAGER | 581008(#8154) |
581008(#8154) |
审批端点对照(POST .../withdraw/1/approve):CUSTOMIZER / FINANCE / GROUP_BATCH_MANAGER → 589530「仅管理员可处理退单审核」;ADMIN → 589531「退单申请不存在」(过角色门)。定制师能提交、不能审批,两个集合不相等。
group_batch_approval 全程 75 行、最大 id 不变:零写入。未只用超管自测:ADMIN 与 CUSTOMIZER 过守卫,证明种子对非超管角色已生效。
8.2 nacos 灰度开关往返
配置 hl-order-service-v3-test.yml(tenant=test)原本不含 group-batch.* 键,即跑默认值 true。
| 态 | 配置 | FINANCE | ROOM_MANAGER | CUSTOMIZER | GROUP_BATCH_MANAGER |
|---|---|---|---|---|---|
| A | 无键(默认 true) |
589507 |
589507 |
589500 |
581008 |
| B | 追加 withdraw-submit: false |
589500(3.1 秒内生效) |
589500 |
589500 |
581008 |
| C | 逐字节还原 | 589507(2.7 秒内生效) |
— | 589500 |
— |
- 还原校验到字节:发布带
casMd5,还原写在finally里;还原后md5与原值同为c2206934960057f70b7173159046dc54。 - 态 B 下团期管理员仍
581008:#8154 守卫不读本开关,符合设计。 - 本轮未取服务端日志;「关掉退化为旁路观察而非静默放行」由单测
withdraw_toggleOff_fallsBackToObserverWithNewCode钉住(断言观察的是新码)。
8.3 finalize(已有快照的团期 2104008377741012994)
| 角色 | 结果 |
|---|---|
| CUSTOMIZER / ROOM_MANAGER / GROUP_BATCH_MANAGER / VEHICLE_MANAGER | 589507,data 为 null |
| FINANCE | 200,幂等返回既有快照 groupSettlementId=2104009943290146817 |
order_group_settlement_summary 前后均 1 行、最大 id 不变:零写入。
本地证据
| 项 | 读数 |
|---|---|
| 引用改动点的 17 个测试类 | 267/0/0/0;合并前按含 #8437 的新基底重跑关键 13 类 163/0/0/0 |
GroupBatchAdminWriteEndpointPermissionArchTest |
2/0(规则 + 判定分辨力自测:7 阳性 / 5 阴性 / 3 个 @RequestMapping 样本) |
变异:撤掉 finalize 编排器入口判权 |
门禁恰好只报 GroupSettlementController.finalizeSettlement 一处;编排器新增用例 4 例变红;已还原 |
| 既有红 | WebMvcSliceMockBeanInventoryTest 1 条,基底同样红(#8388 的切片测试类未登记),与本单无关 |
十、相关文档
- Issue
#7608;种子 PR#8434;批 2 PR#8416 docs/group/团期模块接口文档-v2.0.html§0A.11(已随本单刷新:GB-ADM-070 行、判定方法第 7 条)
关联 / 联系人
链接
联系人
- 后端负责人: @jw