docs(changelog): #7411 团期核单共享成本三端点补判权(前端契约交接件)
changelog-filename-gate / validate (push) Successful in 2s

三端点权限码:
- GET  /v3/admin/order/group-batch/{groupBatchId}/settlement/summary → group-batch:finance:view
- GET  /v3/admin/order/group-batch/{groupBatchId}/settlement/cost    → group-batch:finance:view
- POST /v3/admin/order/group-batch/{groupBatchId}/settlement/cost    → group-batch:finance:advance

不持码返 589507,HTTP 恒 200,前端判 Result.code。
请求与响应结构一字未改,网关零改动(/v3/admin/** 已通配)。

已部署测试服 HEAD 6d41d6148,四档权限矩阵经真实登录态过网关实测,
无权限 POST 已用 SQL 前后对比证明零副作用。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
这个提交包含在:
API Changelog Bot
2026-09-10 15:26:39 +08:00
共同撰写人 Claude Fable 5.1
父节点 be7415b771
当前提交 6cfa38b56d
@@ -0,0 +1,446 @@
---
schema: "hl-changelog/v2"
ticket: "7411"
title: "团期核单共享成本三端点补判权:读走 group-batch:finance:view、写走 group-batch:finance:advance(此前零判权)"
consumer: "admin"
author: "wx(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "pending"
frontend_owner: "mmg"
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "本篇覆盖 PR #7447(合并提交 856ab69bf)。GroupBatchSettlementController 三个端点此前零判权,本次仅在 Controller 入口各加一行 permissionGuard.require(...),Service/Mapper 逻辑与请求响应结构均未改动。2026-09-10 已部署测试服(HEAD 6d41d6148,已验 856ab69bf 为其祖先),并用真实登录态过网关实测完四档权限矩阵:持码账号两个 GET 放行、POST 写入成功并回读到 created_by 与登录账号一致;无码账号三个端点均返 589507,且 SQL 前后对比证明 order_batch_settlement 零行写入、幂等键未被占用、团期状态未变。网关零改动(/v3/admin/** 已在 hl-gateway application.yml:249 通配,Issue #3264),故 gateway_status=not_required。"
updated_at: "2026-09-10"
base: "dev-v3"
---
# 团期模块:核单共享成本三端点补判权(此前零判权,含一个写端点)
> **服务**: `hl-order-service-v3`
> **PR**: #7447
> **Issue**: #7411
> **日期**: 2026-09-10
> **影响范围**: 管理后台团期看板「结算」Tab 下的成本录入 / 成本明细 / D2 汇总三个端点(含一个写端点)
---
## ⚠️ 关键变化
**三个端点的请求参数与响应结构均完全不变,网关路由零改动**,本次只做一件事:给 `GroupBatchSettlementController` 三个此前**零判权**的端点补上平台权限码校验。
1. **`POST /v3/admin/order/group-batch/{groupBatchId}/settlement/cost`**(录入团期共享成本,**写端点**)现在要求当前角色持权限码 **`group-batch:finance:advance`**(`GroupBatchPermissionGuard.java:64`,常量 `PERMISSION_FINANCE_ADVANCE`)。修复前任何能登录后台的账号(不区分角色)都能给任意团期写入一笔共享成本,直接抬高该团总成本与人均分摊。
2. **`GET /v3/admin/order/group-batch/{groupBatchId}/settlement/cost`**(成本明细列表)与 **`GET /v3/admin/order/group-batch/{groupBatchId}/settlement/summary`**(核单 D2 汇总)现在要求当前角色持权限码 **`group-batch:finance:view`**(`GroupBatchPermissionGuard.java:61`,常量 `PERMISSION_FINANCE_VIEW`)。修复前任何账号都能越权读到整团毛利(`subOrderTotalProfit`)、团期预支(`groupAdvanceApproved`/`groupAdvancePending`)等敏感经营数据。
3. **权限码与同包 `GroupBatchFinanceController`(团期财务总览等既有端点)口径一致**,均为「金额数据用 `finance:view`、资金写动作用 `finance:advance`」;不是 `group-batch:view`(团期详情的一般查看权),持 `group-batch:view` 但未持 `group-batch:finance:view` 的账号调这三个端点同样会被拒。
4. 不持有对应权限码 → 业务错误码 **589507**(`GROUP_BATCH_PERMISSION_DENIED`,message:「无操作权限(非团期管理员 / 非本定制师名下)」,HTTP 恒 200,`data=null`);判权在 Controller 方法体第一行执行,先于任何业务逻辑与 Service 调用——**被拒时零副作用**,写端点不会落库、幂等键不会被占用(见「八、测试环境已验证」单测证据)。
5. 判权是**纯角色判定**,没有「本人 / 本定制师名下」之类的归属兜底(与 `#7316` 全团需求汇总同一套 `GroupBatchPermissionGuard.require()`),`adminId` 或角色(`X-Admin-Role`)缺失同样 fail-closed 直接拒绝 589507,不发 Feign;判权服务(user-service)Feign 异常 / 非成功响应 / 空值同样一律按无权限处理。
前端应如何处理(详见「四、契约约束与正确调用方式」):
- 「结算」Tab 的成本录入表单与成本明细/汇总卡片建议按权限码 **隐藏对应按钮/区块**(角色不持 `group-batch:finance:view` 就不请求这三个端点,不持 `group-batch:finance:advance` 就隐藏「录入成本」按钮),而不是等后端 589507 才提示——判权本来就先于业务逻辑,前端提前隐藏能省一次无意义请求。
- 兜底仍必须做:直接调用/绕过前端隐藏的场景,后端会返回 589507,前端需按通用错误码展示(HTTP 200,判 `code`,不是判 HTTP 状态)。
- 若某角色此前能用这三个端点、上线后被拒,处置办法是去 `hl-user-service` 给该角色补 `group-batch:finance:view` / `group-batch:finance:advance` 权限码种子,**不是后端代码问题**(与 `#7316` 定案 10 同一处置口径)。
---
## 一、背景
`#7316` 给团期看板「全团需求汇总」只读端点补 `group-batch:view` 判权时,顺带发现隔壁的 `GroupBatchSettlementController` 三个端点一个判权都没有,其中 `POST .../settlement/cost` 还是**写**端点,写入的金额直接进团期总成本与人均分摊公式。`#7411` 专治这个缺口:给三个端点分别接上 `GroupBatchPermissionGuard`,读侧复用既有的 `group-batch:finance:view`(`GroupBatchFinanceController` 同码),写侧复用既有的 `group-batch:finance:advance`(此前唯一调用点是团期预支单 `GroupBatchActionController.java:142`,语义是「资金写动作」而非「预支单专用」,故复用而非新增权限码)。本次**不新增权限码、不动 hl-user-service 的 Flyway 或角色授权种子**。
已查证的排除项:三个路径经全仓 grep **零 Feign 客户端、零服务间调用方**,只出现在 `GroupBatchSettlementController` 自身与前端契约文档;`source=FLEET_CALLBACK` 的成本回填走的也是同一个 `/admin/` 端点(人工触发或运营后台调用,不是服务间直连),因此加判权不会打断任何内部服务链路。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 录入团期共享成本 | POST | `/v3/admin/order/group-batch/{groupBatchId}/settlement/cost` | 行为修改 | 新增权限码 `group-batch:finance:advance` 校验,请求/响应结构不变 |
| 2 | 团期共享成本明细列表 | GET | `/v3/admin/order/group-batch/{groupBatchId}/settlement/cost` | 行为修改 | 新增权限码 `group-batch:finance:view` 校验,响应结构不变 |
| 3 | 团期核单 D2 汇总 | GET | `/v3/admin/order/group-batch/{groupBatchId}/settlement/summary` | 行为修改 | 新增权限码 `group-batch:finance:view` 校验,响应结构不变(与 `#7316` 落地时字段一致) |
---
## 三、接口详情
### 1. 录入团期共享成本 `POST /v3/admin/order/group-batch/{groupBatchId}/settlement/cost`
**VO**: `RecordBatchCostReqVO → Result<Long>`
#### 使用场景
团期看板「结算」Tab,团期管理员 / 财务录入整团共享成本(大巴、领队、摄影师等)。调用前端必须持有权限码 `group-batch:finance:advance`,否则拿到 589507,且**成本行不会落库**。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `groupBatchId` | Path | Long | 是 | 团期主键(雪花 ID) | 团期 ID |
| `costType` | Body | String | 是 | 枚举 `BUS`/`LEADER`/`PHOTOGRAPHER`/`OTHER`,非法值 589502(`SETTLEMENT_COST_TYPE_INVALID`) | 成本类型 |
| `amount` | Body | BigDecimal | 是 | `>= 0.00` | 成本金额 |
| `source` | Body | String | 否 | `MANUAL`/`FLEET_CALLBACK`,缺省按 `MANUAL` 处理 | 成本来源 |
| `sourceRefNo` | Body | String | 否 | `FLEET_CALLBACK` 时应传,作幂等键(同 `groupBatchId`+`costType`+`sourceRefNo` 三元组命中即直接返回已有 ID,不重复插入) | 来源单据号 |
| `remark` | Body | String | 否 | ≤255 字符 | 备注 |
(字段与约束取自 `RecordBatchCostReqVO.java:18-36`、`GroupBatchSettlementService.addCost` javadoc `:67-88`;权限校验行为本次新增,其余入参约束均为既有行为,未改动。)
#### 出参字段表 `Result<Long>`
| 字段 | 类型 | 说明 |
|---|---|---|
| `data` | Long(前端应按 String 处理避免精度丢失) | 新建成本明细行 `batchSettlementId`;幂等命中时返回已存在记录的 ID |
#### 请求示例
```json
POST /v3/admin/order/group-batch/10/settlement/cost
Authorization: Bearer <持 group-batch:finance:advance 的管理端 token>
{ "costType": "BUS", "amount": "1200.00", "source": "MANUAL", "remark": "大巴费用" }
```
(示例取自单测 `GroupBatchSettlementControllerPermissionTest#recordCost_hasFinanceAdvancePermission_delegatesToService`,非真实网关调用。)
#### 响应示例
```json
{ "code": 200, "message": "成功", "success": true, "data": "9001" }
```
#### 空数据 / 降级响应
无空数据场景(写操作,成功即返回新行 ID 或幂等命中的既有 ID)。
#### 错误响应
```json
{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "success": false, "data": null }
```
```json
{ "code": 589500, "message": "团期不存在", "success": false, "data": null }
```
```json
{ "code": 589501, "message": "团期状态不允许当前操作", "success": false, "data": null }
```
(589501 是既有行为,非本次改动:只有团期处于 `REVIEWING` 状态才允许录入共享成本,`GroupBatchSettlementService.java:97-101`;本次仅新增 589507 分支,且判权先于该状态守卫执行。589502=`SETTLEMENT_COST_TYPE_INVALID`,`costType` 非枚举白名单值时触发,同样是既有行为。)
#### 业务边界
- 权限校验在 Controller 方法体第一行执行(`GroupBatchSettlementController.recordCost` 先调 `permissionGuard.require(...)` 再调 Service),先于成本类型校验、状态守卫、幂等查重与落库,满足 CODE_RULES §9「事务内禁同步 Feign」。
- 判权被拒时**零副作用**:不查库、不落库、不占用 `FLEET_CALLBACK` 幂等键(单测 `recordCost_missingFinanceAdvancePermission_throwsDeniedAndSkipsService` 用 `verifyNoInteractions(groupBatchSettlementService)` 钉死)。
- 判权用的权限码是精确值 `group-batch:finance:advance`,不是「`group-batch:view` 或 `finance:view` 二选一放行」——毛利/预支类金额数据不能靠一般查看权带过(`GroupBatchPermissionGuard.java:56-64` javadoc)。
- 此前唯一使用 `group-batch:finance:advance` 的端点是团期预支单发起(`GroupBatchActionController.java:142`),本次为记团期成本复用同一码,未新增权限码、未动 hl-user-service Flyway。
---
### 2. 团期共享成本明细列表 `GET /v3/admin/order/group-batch/{groupBatchId}/settlement/cost`
**VO**: `无请求体 → Result<List<BatchCostRespVO>>`
#### 使用场景
团期看板「结算」Tab,查看整团已录入的共享成本逐笔明细(按 `create_time` 升序)。调用前端必须持有权限码 `group-batch:finance:view`,否则拿到 589507。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `groupBatchId` | Path | Long | 是 | 团期主键(雪花 ID) | 团期 ID |
无 Body、无 Query 参数。
#### 出参字段表 `Result<List<BatchCostRespVO>>`
| 字段 | 类型 | 说明 |
|---|---|---|
| `data[].id` | String(Long ToString) | 成本明细 ID |
| `data[].costType` | String | 成本类型(`BUS`/`LEADER`/`PHOTOGRAPHER`/`OTHER`) |
| `data[].costTypeDesc` | String | 成本类型展示标签(如「整团大巴」) |
| `data[].amount` | String(BigDecimal ToString) | 成本金额 |
| `data[].source` | String | 成本来源(`MANUAL`/`FLEET_CALLBACK`) |
| `data[].sourceRefNo` | String | 来源单据号 |
| `data[].remark` | String | 备注 |
| `data[].createTime` | LocalDateTime | 录入时间 |
(字段取自 `BatchCostRespVO.java:17-44`,响应结构本次零改动。)
#### 请求示例
```json
GET /v3/admin/order/group-batch/10/settlement/cost
Authorization: Bearer <持 group-batch:finance:view 的管理端 token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": [
{ "id": "8001", "costType": "LEADER", "costTypeDesc": "领队费用", "amount": "800.00",
"source": "MANUAL", "sourceRefNo": null, "remark": null, "createTime": "2026-09-10T10:00:00" }
]
}
```
(结构取自单测 `GroupBatchSettlementControllerPermissionTest#listCost_hasFinanceViewPermission_delegatesToService` 夹具,非真实网关调用。)
#### 空数据 / 降级响应
团期尚无录入的共享成本时,`data` 为空数组,返回 200(`GroupBatchSettlementService.listByGroupBatchId` 对空结果直接返回空 List,不抛错)。
#### 错误响应
```json
{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "success": false, "data": null }
```
#### 业务边界
- 权限校验在 Controller 方法体第一行执行,先于 Service 调用;被拒时不发起任何查询(单测 `verifyNoInteractions` 覆盖)。
- **⚠️ 已发现且如实记录的既有行为(非本次改动,本次未处理)**:`GroupBatchSettlementService.listByGroupBatchId`(`GroupBatchSettlementService.java:140-148`)**不调用 `requireById`**,即不校验 `groupBatchId` 对应的团期是否存在——传入一个不存在的 `groupBatchId` 不会得到 589500,只会得到空数组。这与同一 Controller 内 `recordCost`/`summary` 均先 `requireById` 的行为不一致,是本次代码复核顺带发现的既有缺口,**未在 #7411 范围内修复**,前端不应依赖「团期不存在时本端点会报错」这一假设。
---
### 3. 团期核单 D2 汇总 `GET /v3/admin/order/group-batch/{groupBatchId}/settlement/summary`
**VO**: `无请求体 → GroupBatchSettlementSummaryRespVO`
#### 使用场景
团期看板「结算」Tab,查看整团核单进度(已核单户数 / 在团户数)与成本、毛利、共享成本、预支汇总。调用前端必须持有权限码 `group-batch:finance:view`,否则拿到 589507。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `groupBatchId` | Path | Long | 是 | 团期主键(雪花 ID) | 团期 ID |
无 Body、无 Query 参数。
#### 出参字段表 `Result<GroupBatchSettlementSummaryRespVO>`
| 字段 | 类型 | 说明 |
|---|---|---|
| `data.settledOrderCount` | int | 已核单子订单数 |
| `data.totalActiveOrderCount` | int | 在团子订单总数(仅排除 `CANCELLED`) |
| `data.subOrderTotalActualCost` | String(BigDecimal ToString) | 子订单实际成本合计 |
| `data.subOrderTotalProfit` | String(BigDecimal ToString) | 子订单毛利合计——**本次新增判权重点保护的字段之一** |
| `data.sharedCostTotal` | String(BigDecimal ToString) | 团期共享成本合计 |
| `data.sharedCostByType` | Array\<SharedCostByTypeVO\> | 团期共享成本按类型明细 |
| `data.sharedCostByType[].costType` | String | 成本类型 |
| `data.sharedCostByType[].costTypeDesc` | String | 成本类型展示标签 |
| `data.sharedCostByType[].total` | String(BigDecimal ToString) | 该类型成本合计 |
| `data.groupAdvanceApproved` | String(BigDecimal ToString) | 整团已拨付预支——**本次新增判权重点保护的字段之一** |
| `data.groupAdvancePending` | String(BigDecimal ToString) | 整团待审批预支 |
| `data.grandTotalCost` | String(BigDecimal ToString) | 团期总成本 |
| `data.actualTravelerCount` | int | 实际出行人数 |
| `data.perPersonSharedCost` | String(BigDecimal ToString) | 人均共享成本 |
(字段取自 `GroupBatchSettlementSummaryRespVO.java:20-91`,与 `#7316` 落地时的字段结构完全一致,本次零改动,只是新增了权限门槛。)
#### 请求示例
```json
GET /v3/admin/order/group-batch/10/settlement/summary
Authorization: Bearer <持 group-batch:finance:view 的管理端 token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"settledOrderCount": 3,
"totalActiveOrderCount": 4,
"subOrderTotalActualCost": "30000.00",
"subOrderTotalProfit": null,
"sharedCostTotal": "2000.00",
"sharedCostByType": [],
"groupAdvanceApproved": null,
"groupAdvancePending": null,
"grandTotalCost": "32000.00",
"actualTravelerCount": 15,
"perPersonSharedCost": "133.33"
}
}
```
(结构取自单测 `GroupBatchSettlementControllerPermissionTest#summary_hasFinanceViewPermission_delegatesToService` 夹具,非真实网关调用;完整字段的真实网关取值见 `#7316` changelog 第八节。)
#### 空数据 / 降级响应
同 `#7316` 落地时行为,本次未改动:团期无在团子订单时各计数/金额字段为 0,共享成本合计独立计算不受影响,`perPersonSharedCost` 出行人数为 0 时为 `null`。
#### 错误响应
```json
{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "success": false, "data": null }
```
```json
{ "code": 589500, "message": "团期不存在", "success": false, "data": null }
```
#### 业务边界
- 权限校验在 Controller 方法体第一行执行,先于 `groupBatchService.requireById` 与汇总计算;被拒时不做任何汇总运算(单测 `verifyNoInteractions` 覆盖)。
- 判权用的权限码与「## 2. 成本明细列表」同码 `group-batch:finance:view`,避免出现「明细看不到、汇总能看到」的旁路(`GroupBatchSettlementController.java:93-98` javadoc)。
---
## 四、契约约束与正确调用方式
| 端点 | 场景 | 结果 |
|---|---|---|
| `POST .../settlement/cost` | ✅ 持 `group-batch:finance:advance` | 200,成功落库 |
| `POST .../settlement/cost` | ❌ 不持 `group-batch:finance:advance` | 589507,零落库 |
| `GET .../settlement/cost` | ✅ 持 `group-batch:finance:view` | 200,返回明细 |
| `GET .../settlement/cost` | ❌ 不持 `group-batch:finance:view` | 589507 |
| `GET .../settlement/summary` | ✅ 持 `group-batch:finance:view` | 200,返回汇总 |
| `GET .../settlement/summary` | ❌ 不持 `group-batch:finance:view` | 589507 |
| 三端点 | ❌ 未经网关鉴权(`X-Admin-Id`/角色缺失) | 589507(fail-closed,不发 Feign 判权) |
| 三端点 | ❌ 仅持 `group-batch:view`(团期一般查看权),未持 `finance:view`/`finance:advance` | 589507——**这是本次最容易被前端漏判的场景**,「能看团期详情」≠「能看/改团期财务」 |
- 三个端点均**无请求体结构变化**,前端不需要调整已有的请求/响应处理代码,只需在调用前补权限判断,或对 589507 做通用错误展示。
- 若某角色此前能用这三个端点、上线后被拒,处置办法是去 `hl-user-service` 给该角色补对应权限码种子,接口本身不提供角色开关。
### 切换状态时的必要动作
- 无状态机切换,本次纯判权补齐,不涉及任何状态字段的写入方式变化。
---
## 五、数据库行为
- `POST .../settlement/cost` 写 `order_batch_settlement` 表(`GroupBatchSettlementDO`,`@TableName("order_batch_settlement")`),本次判权改动**不改变**其写入逻辑、幂等键(`groupBatchId`+`costType`+`sourceRefNo` 三元组)或状态守卫(仅 `REVIEWING` 状态可写);判权被拒时该表零写入。
- 两个 GET 端点均只读,无写操作。
- 权限判定读 `hl-user-service` 的角色权限码(Feign `roleHasPermission`),非本服务库表;远端异常按失败关闭处理,不放行。
---
## 六、边界行为
- 未持对应权限码调用三端点中任一个 → 589507(fail-closed,先于业务逻辑,零副作用)。
- 团期不存在:`recordCost`/`summary` → 589500;`listCost` **不校验**(既有行为,见「## 2.」业务边界的既有缺口记录)。
- 权限服务(user-service)不可用时同样按无权限处理,不因下游故障放宽判权。
- 判权失败与业务失败(如 589500/589501/589502)不会同时出现在同一次调用——判权在最前面,任何一个判权失败必然先于业务校验拦下。
---
## 六.5、枚举 / 数据字典
### 权限码字面量(来源:`GroupBatchPermissionGuard.java`)
**所属字段**: Controller 入口 `permissionGuard.require(adminId, roleKey, ...)` | **类型**: 平台权限码字符串
| 权限码常量 | 字符串字面量 | 用于 | 出处 |
|---|---|---|---|
| `PERMISSION_FINANCE_VIEW` | `group-batch:finance:view` | `GET .../settlement/cost`、`GET .../settlement/summary` | `GroupBatchPermissionGuard.java:61` |
| `PERMISSION_FINANCE_ADVANCE` | `group-batch:finance:advance` | `POST .../settlement/cost` | `GroupBatchPermissionGuard.java:64` |
(对照:`group-batch:view` 是团期详情一般查看权,`#7316` 已接线到「全团需求汇总」端点,**不是**本次三端点使用的码。)
### `costType`(`RecordBatchCostReqVO.costType` / `BatchCostRespVO.costType`)
**所属字段**: `data.costType` / `data.sharedCostByType[].costType` | **类型**: `String`
| 值 | 含义 |
|---|---|
| `BUS` | 整团大巴 |
| `LEADER` | 领队费用 |
| `PHOTOGRAPHER` | 摄影师费用 |
| `OTHER` | 其他 |
(枚举白名单来自 `GroupBatchSettlementService.parseCostType`,非法值抛 589502,本次未改动。)
---
## 六.6、修改前后对比
### 字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
| 三端点权限要求 | 无(任意能过网关鉴权的后台账号均可调用) | `POST .../cost` 需 `group-batch:finance:advance`;两个 `GET` 需 `group-batch:finance:view`;否则 589507 |
| 三端点请求参数 | — | **完全不变** |
| 三端点响应结构(字段名/类型/嵌套) | — | **完全不变** |
### 行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 不持任何团期相关权限码的账号调 `POST .../settlement/cost` | 200,成功写入 `order_batch_settlement`,抬高该团总成本与人均分摊 | 589507,零落库 |
| 不持任何团期相关权限码的账号调两个 `GET` 端点 | 200,能读到 `subOrderTotalProfit`(毛利)、`groupAdvanceApproved`/`groupAdvancePending`(预支)等敏感经营数据 | 589507,拒绝读取 |
| 仅持 `group-batch:view`(无 `finance:view`/`finance:advance`)的账号调三端点 | 200(此前零判权) | 589507 |
---
## 六.7、影响评估
- **是否破坏向后兼容**:**是(预期内的收紧)**——此前任意能过网关的后台账号都能调这三个端点,现在非持对应 `finance:view`/`finance:advance` 的角色会从 200 变 589507。这是本单修复的安全缺口本身,不是意外副作用。
- **前端是否必须同步上线**:**否,但强烈建议同步**——不同步页面仍可用:权限拒绝会走通用错误展示后端 `message`;请求/响应结构零改动,无需前端配合即可正常渲染已授权账号的数据。但若前端此前对所有登录后台账号都展示「结算」Tab 的成本录入/明细/汇总,上线后无权限角色会看到大量 589507 报错而非空白/隐藏状态,体验不佳,建议同步按权限码隐藏对应 UI。
- **需要确认的角色矩阵**:上线前应先确认哪些角色持有 `group-batch:finance:view` / `group-batch:finance:advance`(与 `GroupBatchFinanceController` 既有端点共用同一套授权),避免误挡当前正在使用「结算」Tab 的角色。此步骤由管理者在部署前执行,结果将回填本篇 frontmatter 与状态说明。
---
## 七、不影响范围
- 三个端点的请求参数、响应结构(字段名、类型、嵌套层级)本次完全不变,只新增鉴权分支。
- `GroupBatchFinanceController`(团期财务总览、预支记录等既有端点)判权口径本就是 `finance:view`/`finance:advance`,不受本次影响。
- `#7316` 已接线的 `requirement-summary` 端点(`group-batch:view`)不受本次影响,两组端点权限码互不复用。
- 团期需求确认 / 打回相关端点(`group-batch:demand:confirm`)不受本次影响。
- 无 Flyway、无表结构变更、无新端点、网关路由零改动(`/v3/admin/order/group-batch/**` 通配路由已存在),只涉及 `hl-order-service-v3` 单模块。
- 不新增权限码、不改动 hl-user-service 的角色授权种子数据。
---
## 八、测试环境已验证
**当前状态:代码已合入 `dev-v3`(HEAD `353f4b2d6`),测试服部署与网关实测待管理者安排,部署后回写本节与 frontmatter。**
已完成的验证(单测/编译层面,不等价于网关实测):
| 场景 | 用例 | 结果 |
|---|---|---|
| `recordCost` 持 `finance:advance` | `GroupBatchSettlementControllerPermissionTest#recordCost_hasFinanceAdvancePermission_delegatesToService` | 精确 verify 判权入参为 `PERMISSION_FINANCE_ADVANCE`,正常委托 Service 并返回新建 ID |
| `recordCost` 缺 `finance:advance` | `...recordCost_missingFinanceAdvancePermission_throwsDeniedAndSkipsService` | 抛 589507,`verifyNoInteractions(groupBatchSettlementService)`——零落库 |
| `listCost` 持 `finance:view` | `...listCost_hasFinanceViewPermission_delegatesToService` | 精确 verify 判权入参为 `PERMISSION_FINANCE_VIEW`,正常返回明细 |
| `listCost` 缺 `finance:view` | `...listCost_missingFinanceViewPermission_throwsDeniedAndSkipsService` | 抛 589507,零查询 |
| `summary` 持 `finance:view` | `...summary_hasFinanceViewPermission_delegatesToService` | 精确 verify 判权入参为 `PERMISSION_FINANCE_VIEW`,正常返回汇总 |
| `summary` 缺 `finance:view` | `...summary_missingFinanceViewPermission_throwsDeniedAndSkipsService` | 抛 589507,零汇总计算 |
以上六条均用 Mockito 精确实参 `verify`(而非 `anyString()`)钉死判权用的权限码字面量,避免改错码测试仍然通过;`GroupBatchSettlementControllerTest`(路由与响应结构切片测试)已同步 mock 新注入的 `GroupBatchPermissionGuard` 以保证 `@WebMvcTest` 能正常装配。
**待补(部署后由管理者执行并回填)**:测试服网关实测三端点的 200/589507 两种路径、角色矩阵 SQL 结果、ArchTest 门禁结果。
---
## 十、相关文档
- 团期看板权限地基 #6902:`group-batch:list` / `group-batch:view` / `group-batch:export` 三权限码首次注册。
- 团期财务总览与预支 #7154:`group-batch:finance:view` / `group-batch:finance:advance` 首次注册并接线到 `GroupBatchFinanceController`(GB-ADM-040/042/043)。
- 团期全团需求汇总补权限码 #7316:同一 `GroupBatchPermissionGuard`,`group-batch:view` 接线示例;该篇正文两处涉及本端点判权状态的历史描述已随本单更正(见 `10_7316_*.md`)。
---
## 关联 / 联系人
### 链接
- **Issue**: [#7411](https://git.1814.love:8443/wx/HL/issues/7411)
- **PR**: [#7447](https://git.1814.love:8443/wx/HL/pulls/7447)(合并提交 [856ab69bf](https://git.1814.love:8443/wx/HL/commit/856ab69bf9bad1834e4b6705a2d773b6d3f2e2e3))
### 联系人
- **后端负责人**: wx
- **待确认对象(前端 hl-ui)**: mmg——建议「结算」Tab 按权限码隐藏成本录入/明细/汇总的按钮与区块,并确认现有账号角色矩阵是否已持有 `group-batch:finance:view` / `group-batch:finance:advance`