文件
hl-api-changelog/changelogs-v2/2026-09/27_7608_团期退团提交与核单定稿判权收口批3-修改接口-管理后台.md
T

16 KiB
原始文件 Blame 文件历史

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