文件
hl-api-changelog/changelogs-v2/2026-09/13_7608_团期取消成团接manage判权-修改接口-管理后台.md
T
2026-09-13 13:11:26 +08:00

15 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:manage 判权(589507),带 nacos 灰度开关 admin jw(GIT) 修改接口 deployed verified not_required mmg 2026-09-13 AC-2/AC-3/AC-6 与批 0 探针四项均在 TEST 网关实打通过(构建身份逐批探针确认)。对前端有一处硬影响:CUSTOMIZER / FINANCE / ROOM_MANAGER / VEHICLE_MANAGER 改前可正常取消成团,改后一律 589507,需按 group-batch:manage 隐藏「取消成团」按钮(与「成团」按钮同一个权限码),否则低权限角色会点到必然失败的按钮。响应结构与业务错误码不变,无需改解析逻辑。回滚不需重发服务:改 nacos group-batch.acl.enforce.cancel-group=false,实测约 7s 生效。 前端实证 not_required(2026-09-13, mmg):cancel-group 端点前端尚未接入(cancelGroup/取消成团 全仓零命中),无「取消成团」按钮可隐藏;本域既定口径为阶段门写操作按 batchStatus 白名单显隐+可见即可点+589507 兜底(batch/detail/index.vue:40-42,group-batch 按钮权限本就不接线),589507 由 request.js 拦截器统一透后端 message 天然覆盖。将来若接 cancel-group 按同口径即可,响应结构与业务错误码不变零额外改动。另三端点仅旁路观察对外零变化。 2026-09-13 dev-v3

order-v3: 团期取消成团接入 group-batch:manage 判权

服务: hl-order-service-v3 PR: #7616 Issue: #7608


⚠️ 关键变化

🔴 cancel-group 从「任何后台角色都能调」变为「必须持 group-batch:manage」,无权限返回 589507。 改前零判权:定制师 / 房务 / 车务 / 财务任一持有效后台 token 的角色都能把一个已成团的团期打回招募中。

谁会受影响:目前 group-batch:manage 只授予 ADMIN 与 SUPER_ADMIN(V20260906_002 种子)。CUSTOMIZER / FINANCE / ROOM_MANAGER / VEHICLE_MANAGER 在团期 9 个权限码上零授权,这些角色调用本端点将从改前的正常执行变为 589507。若前端把「取消成团」按钮展示给了这些角色,需要按权限隐藏,否则用户会点到一个必然失败的按钮。

🟡 这是权限不对称的修复,不是新增限制。 同一对按钮的另一半——成团(POST .../group)自 #7158 起就要求 group-batch:manage。改前的状态是:低权限账号可以撤销高权限账号的动作。

🟢 响应结构、成功码、业务错误码全部不变。 有权限的调用方(ADMIN / SUPER_ADMIN)行为与改前逐字节一致。


一、背景

#7455(P0,已完成)自称做了「全端点扫描」并收口了 15 个零判权端点,但漏掉了 GroupBatchActionController 里的 4 个。

根因是审计方法本身的缺陷:该方法「逐个 Controller 数 GroupBatchPermissionGuard 的命中数,命中 0 即无强制」——这是按类计数。GroupBatchActionController 因为 advance 与 recheck-material-gate 两处有 guard,整个类被归入「已判权」,于是同类里裸着的 4 个端点全部漏检。

推论:任何「按类计数」的判权审计,都会在混合类(部分方法有守卫、部分没有)上系统性漏检。

本单是这 4 个端点收口的第一批。cancel-group 之所以能不等观察窗直接收口:同一个团期必须先由持 manage 的人成团才可能被取消,调用方集合可被静态证明是成团调用方的子集,不存在「收口后把真实调用方挡在外面」的风险。另外三个端点本批只加旁路观察日志、不拦截,对外行为零变化。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 取消成团 POST /v3/admin/order/group-batch/:groupBatchId/cancel-group 修改 新增判权:需 group-batch:manage,无权限 589507;响应结构与业务错误码不变

三、接口详情

1. 取消成团 POST /v3/admin/order/group-batch/:groupBatchId/cancel-group

VO: 无请求体(Result<Void>)

使用场景

hl-ui 管理后台团期详情页「取消成团」按钮:把已成团(RESOURCE_PREPARING)的团期打回招募中(RECRUITING),并重置四项资源就绪位与物资确认位。本单起需要 group-batch:manage 权限。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ 运营团期主键 不是产品侧排期 ID productBatchId(行为不变)

无请求体。

出参字段表

字段 类型 说明
data null 结构完全不变,Result<Void>

请求示例

POST /v3/admin/order/group-batch/2098606570236481538/cancel-group HTTP/1.1
X-Admin-Id: 1001
X-Admin-Role: ADMIN

无请求体。

响应示例

{
  "code": 200,
  "message": "成功",
  "data": null,
  "success": true
}

空数据 / 降级响应

无分页与列表语义,不存在空数据形态。

降级:灰度开关 group-batch.acl.enforce.cancel-group 置 false 时,判权退化为旁路观察日志(GB_ACL_PROBE)而非静默放行——此时所有角色恢复放行,行为与改前一致,但每次调用仍在服务端留痕。开关默认 true。

错误响应

码 符号 触发 本单
589507 GROUP_BATCH_PERMISSION_DENIED 调用方角色不持有 group-batch:manage 🆕 本端点新增(码本身早已存在)
589500 — 团期不存在 不变
589501 — 团期状态不允许(非 RESOURCE_PREPARING) 不变
589509 — 已有已确认子订单或已派单资源,禁止取消成团 不变
{
  "code": 589507,
  "message": "无操作权限(非团期管理员 / 非本定制师名下)",
  "data": null,
  "traceId": null,
  "success": false
}

业务边界

  • 拒绝时零写入:判权在 Controller 入口,排在 Service 的任何库读写之前。被拒的请求不改 order_group_batch 任何列(含 update_time)、不写状态流水、不登记 Fleet 释放命令。
  • 判权落点显式定在 Controller:本类的 advance、recheck-material-gate 已是这个形状。统一判在入口,让「读 Controller 就能判有没有守卫」重新成立——这正是 #7455 漏检的方法学根因。
  • 超管短路放行:user-service 对 SUPER_ADMIN 短路放行,因此用超管自测无法发现种子缺失。本单的验收用 ADMIN + 低权限角色实打(见第八节)。
  • 本批不收口的三个端点:withdraw 提交、transfer-in、transfer-candidates 本批只观察不拦截,对外行为零变化。它们的收口分别在批 2 / 批 3,需先拿到观察窗数据、且 withdraw 需要 user-service 新增权限码种子。

四、契约约束与正确调用方式

  • 前端应按 group-batch:manage 控制「取消成团」按钮的可见性/可用性,与「成团」按钮用同一个权限码(两者本就是同一对按钮)。
  • 589507 建议直接透出 message 文案,无需前端自造提示。
  • 权限码与角色绑定没有运行期写入口(只能走 Flyway 种子),前端不要尝试动态查询角色权限表来做按钮控制,应使用现有的权限码下发通道。

五、数据库行为

无 DDL、无数据迁移。 本单不含任何 Flyway 脚本。

权限码 group-batch:manage 与其角色授予(ADMIN / SUPER_ADMIN)由既有种子 V20260906_002 提供,user-service 零改动。


六、边界行为

  • 有权限的调用方:与改前逐字节一致。
  • 无权限的调用方:改前执行成功,改后 589507 且零写入。
  • 角色头缺失:GroupBatchPermissionGuard 是 fail-closed,roleKey 为空一律 589507(行为不变,该守卫既有口径)。
  • user-service 不可达:权限查询 catch (RuntimeException) → false,即 fail-closed,返回 589507。

六.6、修改前后对比

角色 改前 改后
SUPER_ADMIN 正常执行 正常执行(不变)
ADMIN 正常执行 正常执行(不变)
CUSTOMIZER 正常执行 589507
FINANCE 正常执行 589507
ROOM_MANAGER / VEHICLE_MANAGER 正常执行 589507

六.7、影响评估

  • 前端:需按权限隐藏按钮,否则低权限角色会点到必然失败的按钮。响应结构无变化,无需改解析逻辑。
  • 后端:无下游契约变化。
  • 回滚:改 nacos group-batch.acl.enforce.cancel-group: false,约 30s 生效,不需要重发服务。

七、不影响范围

  • 成团 / 流团 / 名额调整 / 预支 / 物料门复判五个端点:判权口径不变。
  • withdraw 提交 / transfer-in / transfer-candidates:本批只加观察日志,对外行为零变化。
  • 小程序端(consumer: mp):本单端点均为管理后台端点,小程序无影响。

八、测试环境已验证

构建归属:TEST order-v3 部署 fix/7608-groupbatch-action-acl @ ac4fbf0f7(面板 git-backend 确认)。每批取证前后各打一次构建身份探针:CUSTOMIZER 调 cancel-group 返回 589507 —— 这个码在 dev-v3 上根本产不出来,它本身就是构建指纹,可排除「部署假成功、跑的是陈旧产物」。

判权收口生效(角色矩阵实打)

对不存在的 groupBatchId 发请求(不产生任何写入),看返回码即可判定:

角色 改前(dev-v3) 改后 判定
SUPER_ADMIN 589500 589500 过守卫,进业务校验
ADMIN 589500 589500 过守卫,进业务校验
CUSTOMIZER 589500 589507 被拦
FINANCE 589500 589507 被拦

没有只用超管自测:user-service 对 SUPER_ADMIN 短路放行,只用超管验会掩盖种子缺失。上表用 ADMIN + 两个低权限角色实打。另查 SQL 确认 group-batch:manage 状态 ACTIVE 且只绑 ADMIN / SUPER_ADMIN,无种子缺失。

零写入(真实团期,改前/改后逐列对拍)

用自建团期 2098979230774657025(RESOURCE_PREPARING,四个 ready 位全 0,零已确认子订单 —— cancelGroup 三条硬前置全满足,即「守卫若不拦,这次调用本会真的写进去」):

项 结果
CUSTOMIZER 返回码 589507
order_group_batch 改前/改后 41 列逐列完全一致,无差异列
update_time 未变(11:37:19 → 11:37:19)
group_batch_status_log 新增 0 行(2 → 2)

这条必须用真能被取消的团期。拿一个取消不了的团期做,守卫不拦它也写不进去,零写入的证明是空的。

有权限仍可用(非超管 ADMIN)

同一团期、同一端点,ADMIN token → 200,RESOURCE_PREPARING → RECRUITING,update_time 11:37:19 → 11:37:52,状态流水 +1 行(BATCH_CANCEL_GROUP / operator 1001)。

四个 ready 位的「重置」在本路径上结构性不可观测:R10 硬前置要求它们进来时就全 0,重置是 0→0,不可能看到 delta。这是接口语义决定的,不是没验。

灰度开关开/关两态(回滚演练)

nacos hl-order-service-v3-test.yml(tenant=test):

时刻 动作 CUSTOMIZER 调用结果
11:51:40 写入 group-batch.acl.enforce.cancel-group: false —
11:51:47 写入后 7s 589500(恢复放行)
11:51:55 写回原文 —
11:52:02 还原后 7s 589507(收口恢复)

配置已逐字节还原(md5 与原文相同,group-batch key 已消失),TEST 未被留在关闭态。实测热刷新 ≈7s,代码注释里的「30s」是保守上界。

三个旁路观察端点:行为零变化(前后对照)

withdraw / transfer-in / transfer-candidates 各用 CUSTOMIZER、FINANCE 打一次,共六发:

改前(dev-v3,无探针) 改后(有探针)
六发结果 全部 589500 全部 589500
400 / 500 / 589507 无 无

逐个对上,零差异 —— 证明新增的观察探针没有把正常请求打挂(探针内吞 RuntimeException,且 has() 的 Feign 跳是 fail-closed 不外抛)。

transfer-in 带齐了必填字段(含 fromGroupBatchId),返回 589500 说明已进到 Controller 之后,不是被 @Valid 的 400 挡在门外 —— 黑盒探测判权时这一步必须做,否则 400 会掩盖守卫、把有守卫的端点误判成裸奔。

观察探针端到端(GB_ACL_PROBE,合并进 dev-v3 后补采)

三个旁路端点实际产出的观察日志(enforced=false 表示只观察不拦截):

端点 roleKey intendedPermission hasPermission
GB-ADM-070 withdraw-submit CUSTOMIZER / FINANCE group-batch:manage false
GB-ADM-070 withdraw-submit SUPER_ADMIN group-batch:manage true
GB-ADM-071 transfer-in ADMIN group-batch:manage true
GB-ADM-071b transfer-candidates CUSTOMIZER / FINANCE / ADMIN (not-evaluated) —

两点被这张表钉死:

  1. 探针的判定与真实权限模型一致 —— 低权限角色 false、管理角色 true,说明观察窗收上来的数据可以直接作为批 2 / 批 3 选码的依据。
  2. transfer-candidates 确实不发 Feign —— intendedPermission=(not-evaluated)。它是转单弹窗的搜索下拉,用户每敲一个字就打一次,走 Feign 判权会同时拖慢下拉并放大 user-service 压力;观察阶段只需要知道「哪些角色在调」。

本地全量单测(四数对照)

检出 Tests / Failures / Errors / Skipped
基线 cb293d2ab(本分支 merge-base) 10220 / 0 / 13 / 51
本分支 ac4fbf0f7 10232 / 0 / 13 / 51

+12 逐条可归因:+6 = 本单新增的 6 条用例;+6 = 合并 dev-v3 带进来的 VehicleRequirementKindErrorCodeMessageTest(实测正好 6 条)。Failures / Errors / Skipped 三项零变化,13 条 Errors 的类名集合完全相同,全部是 Testcontainers 无 Docker 环境所致,与本单无关。

未采集到的证据(如实列出)

  • AC-1 的 roleKey × endpoint × 次数观察表:本单只负责把探针种下去,统计窗 ≥2 周,表本身不在本单产出。

采集说明:部署面板对 order-v3 配了分支白名单(service-ports.conf),检出切到特性分支时 order-v3 会从 /api/services 消失、日志端点 404,所以日志类证据是在合并进 dev-v3 并部署 dev-v3 之后补采的。另:面板日志端点只读 primary 实例,双实例轮询会漏,需多打几轮才能覆盖全。


十、相关文档

  • Issue #7608(含 58 端点判权清点、网关三层排除、FINANCE 三步可达链)
  • PR #7616

关联 / 联系人

  • 后端:jw
  • 前端:mmg