docs(changelog): #7608 团期退团提交收口判权(批 3)+ 核单 finalize 判权前移
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
withdraw 提交接新码 group-batch:withdraw:submit(授 ADMIN / CUSTOMIZER,不授 FINANCE), 无权限 589507,带 nacos 灰度开关;团期核单 finalize 在已有快照的幂等分支之前判权, 非资金写角色不再能经它读到整份核单。响应结构、成功码、业务错误码不变。 TEST 已部署(dev-v3 @ b8c9b463f)并实测:退团提交七角色矩阵、审批角色门对照、 开关 false→还原往返(md5 逐字还原)、finalize 四个非财务角色 589507,全程零写入。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
这个提交包含在:
@@ -0,0 +1,327 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7608"
|
||||
title: "团期退团提交接新码 group-batch:withdraw:submit(589507,带 nacos 灰度开关);团期核单 finalize 判权前移到幂等分支之前"
|
||||
consumer: "admin"
|
||||
author: "jw(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-09-27"
|
||||
status_note: "已部署 dev-v3 到 TEST 并实打验证:退团提交七角色矩阵逐格吻合、审批端点角色门对照、nacos 开关 false→还原往返(md5 逐字还原)、核单 finalize 四个非财务角色对已有快照的团期 589507 且不带数据,全程零写入。"
|
||||
updated_at: "2026-09-27"
|
||||
base: "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` | **结构完全不变** |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
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": "客户临时有事"}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"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):
|
||||
|
||||
```json
|
||||
{
|
||||
"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` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/group-batch/2104008377741012994/settlement/finalize HTTP/1.1
|
||||
X-Admin-Id: 1001
|
||||
X-Admin-Role: FINANCE
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"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 对已有快照的团期):
|
||||
|
||||
```json
|
||||
{
|
||||
"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 条)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#7608](https://git.1814.love/wx/HL/issues/7608)
|
||||
- **PR**: [#8438](https://git.1814.love/wx/HL/pulls/8438)(order-v3)、[#8434](https://git.1814.love/wx/HL/pulls/8434)(user-service 种子)
|
||||
- **Merge commit**: [b8c9b463f](https://git.1814.love/wx/HL/commit/b8c9b463fbb02fc711822bcab6f9d769bd7cc23a)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @jw
|
||||
在新工单中引用
屏蔽一个用户