diff --git a/changelogs-v2/2026-09/27_7608_团期退团提交与核单定稿判权收口批3-修改接口-管理后台.md b/changelogs-v2/2026-09/27_7608_团期退团提交与核单定稿判权收口批3-修改接口-管理后台.md new file mode 100644 index 00000000..0e6fac25 --- /dev/null +++ b/changelogs-v2/2026-09/27_7608_团期退团提交与核单定稿判权收口批3-修改接口-管理后台.md @@ -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` + +#### 使用场景 + +管理后台团期详情「退单」:为某户子订单提交退单申请,只建审批单,执行退团与退款在审批通过时。**本单起需要 `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`(无请求体) + +#### 使用场景 + +团期核单页「完成核单」:按团聚合落核单快照;重复提交幂等返回既有快照。只有资金写角色(`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