feat(product-v2): 产品/产品线数据权限切面 @DataScope (PR #849)
- 管理端 37 个接口加 @DataScope - 3 个 VO 加 canEdit/isOwner 字段 - CORE/GROUP 可见不可改、CUSTOM 仅自己 - 前端按 canEdit 灰化按钮
这个提交包含在:
父节点
aed14f4451
当前提交
2cf5a54f9c
@ -0,0 +1,184 @@
|
||||
# 产品/产品线数据权限切面 @DataScope
|
||||
|
||||
**合并时间**:2026-04-18
|
||||
**PR**:https://git.1814.love:8443/wx/HL/pulls/849
|
||||
**Issue**:#839
|
||||
**影响范围**:`hl-product-service-v2` 管理端 37 个 `/admin/product/**` 和 `/admin/product/line/**` 接口
|
||||
**前端需同步**:✅ 是(VO 新增 canEdit/isOwner + 403/404 语义 + 部分接口响应条数变化)
|
||||
|
||||
---
|
||||
|
||||
## 一、权限规则(必读)
|
||||
|
||||
### 1.1 CORE 产品 / GROUP 产品(小蒙马) / CORE 产品线 / GROUP 产品线
|
||||
- **所有拥有"产品管理"菜单权限的管理员都可查看**列表和详情(不限创建人)
|
||||
- **只有创建人可操作**(编辑 / 删除 / 上下架 / 复制 / 班期 / 价格日历 / 行程 / 详情区块等所有写接口)
|
||||
- 非创建人试图调写接口 → `code=403 message="无操作权限"`
|
||||
|
||||
### 1.2 CUSTOM 产品 / CUSTOM 产品线(私人定制)
|
||||
- **只有创建人可见**(列表返回自动过滤;非创建人按 ID 查详情 → `code=404`)
|
||||
- **只有创建人可操作**
|
||||
- 其他管理员(非超管)**完全不知道别人 CUSTOM 产品的存在**
|
||||
|
||||
### 1.3 SUPER_ADMIN(超级管理员)
|
||||
- **所有产品/产品线都可看、都可操作**(不受 1.1 / 1.2 限制)
|
||||
- 详情返回里 `canEdit=true`(恒真)
|
||||
- 但 `isOwner` 仍按真实 created_by 比对(**超管不自动算创建人**)
|
||||
|
||||
### 1.4 未登录 / Token 失效
|
||||
- `code=401` 拒绝(即使 Nacos 开关 `hl.data-scope.enabled=false` 关闭切面也拒绝)
|
||||
|
||||
### 1.5 分享 URL(CUSTOM 产品给客户看)
|
||||
- 走 `/mp/**` → Feign → `/internal/**` 链路,**完全不经过本切面**
|
||||
- 客户端无 adminId,不受数据权限影响
|
||||
|
||||
---
|
||||
|
||||
## 二、响应字段新增(3 个 VO)
|
||||
|
||||
### 2.1 `ProductListRespVO`(列表接口 `GET /admin/product/item/list`)
|
||||
```json
|
||||
{
|
||||
"productId": 1001,
|
||||
"productType": "CUSTOM",
|
||||
"name": "草原深度定制",
|
||||
"createdBy": 1002,
|
||||
... // 其他原有字段
|
||||
"canEdit": true, // ← 新增
|
||||
"isOwner": true // ← 新增
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 `ProductDetailRespVO`(详情接口 `GET /admin/product/item/{id}`)
|
||||
同 2.1,新增 `canEdit` + `isOwner`。
|
||||
|
||||
### 2.3 `ProductLineRespVO`(产品线列表 / 详情)
|
||||
```json
|
||||
{
|
||||
"lineId": 501,
|
||||
"name": "呼伦贝尔经典",
|
||||
"productType": "CORE",
|
||||
"createdBy": 1001,
|
||||
... // 其他原有字段
|
||||
"canEdit": false, // ← 新增
|
||||
"isOwner": false // ← 新增
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 字段语义(所有 3 个 VO 都适用)
|
||||
|
||||
| 字段 | 类型 | 语义 | 超管下取值 |
|
||||
|------|------|------|-----------|
|
||||
| `canEdit` | Boolean(非 null) | 当前登录管理员是否可编辑该记录 | 恒 `true` |
|
||||
| `isOwner` | Boolean(非 null) | 当前登录管理员是否为该记录创建人(`created_by == adminId`) | 按真实 `created_by` 判断,不开后门 |
|
||||
|
||||
**前端使用建议**:
|
||||
- 编辑按钮 / 删除按钮 / 上下架按钮:`v-if="row.canEdit"` 或灰化(`disabled="!row.canEdit"`)
|
||||
- 展示"我创建的" Tag:`v-if="row.isOwner"`
|
||||
- **不要**单独用 `isOwner` 判断按钮可用(超管时 `isOwner=false` 但应能操作 → 用 `canEdit` 判断才对)
|
||||
|
||||
### 2.5 不加字段的接口
|
||||
- `ProductSimpleListVO`(`GET /admin/product/item/simple-list`)— 下拉用,不返
|
||||
- `ProductLineDropdownVO` / simple-list — 下拉用,不返
|
||||
|
||||
---
|
||||
|
||||
## 三、错误码 / 响应码变化
|
||||
|
||||
| 场景 | 之前 | 现在 |
|
||||
|------|------|------|
|
||||
| 未带 Token / Token 失效 | 401 | **401**(不变) |
|
||||
| 非超管按 ID 查他人 CUSTOM 详情 | 200 + 数据 | **404 `"资源不存在或无权限"`** |
|
||||
| 非超管编辑他人 CORE/GROUP/CUSTOM 任一写操作 | 200(数据被改) | **403 `"无操作权限"`** |
|
||||
| 非超管列表返回其他定制师的 CUSTOM | 返回 | **不返回**(total 也减少) |
|
||||
|
||||
**前端错误处理建议**:
|
||||
- 404:统一弹 "资源不存在或无权限,请刷新列表"
|
||||
- 403:统一弹 "您没有操作此产品的权限"
|
||||
|
||||
---
|
||||
|
||||
## 四、受影响接口清单(37 个)
|
||||
|
||||
### 4.1 `AdminProductController`(`/admin/product/item/**`)33 个
|
||||
- **列表查询**:`GET /list`、`GET /simple-list`
|
||||
- **详情**:`GET /{id}`、`GET /{id}/price-calendar`、`GET /{id}/schedule/list`、`GET /{id}/schedule/team`、`GET /{id}/suggest-price`、`GET /{id}/supplement`、`GET /{id}/calculate-mileage`、`GET /{id}/detail-blocks`、`GET /{id}/operation-logs`、`GET /{id}/validate-publish`、`GET /{id}/fee-deduction-preview`、`GET /{productId}/tiers`
|
||||
- **写操作**(行级校验):
|
||||
- `PUT /{id}/itinerary`、`POST /{id}/itinerary/day`、`DELETE /{id}/itinerary/day/{dayNumber}`
|
||||
- `PUT /{id}/route`、`POST /{id}/price-calendar/batch`、`DELETE /{id}/price-calendar`
|
||||
- `POST /{id}/schedule`、`DELETE /{id}/schedule/{scheduleId}`、`PUT /{id}/schedule`、`POST /{id}/schedule/batch-create`、`POST /{id}/schedule/{scheduleId}/cancel`、`PUT /{id}/schedule/team`
|
||||
- `POST /{id}/quote`、`PUT /{id}/supplement`、`POST /{id}/daily-mileage`
|
||||
- `PUT /{id}/detail-blocks`、`POST /{id}/toggle-publish`、`POST /{id}/complete`、`POST /{id}/action`(@Deprecated)、`DELETE /{id}`
|
||||
- **不加切面,Service 层做权限**:
|
||||
- `POST /basic`(创建+更新合一,更新分支 Service 层 assertCanWrite)
|
||||
- `POST /{id}/copy`(Service 层 assertCanRead,允许任何"可见者"复制;R9 规则)
|
||||
- `PUT /{id}/transfer`(Service 层仅超管可操作)
|
||||
|
||||
### 4.2 `AdminProductLineController`(`/admin/product/line/**`)4 个
|
||||
- `GET /list`、`GET /simple-list`(按产品线 product_type 过滤)
|
||||
- `GET /item/{id}`(ROW_CHECK)
|
||||
- `DELETE /item/{id}`(ROW_CHECK)
|
||||
- `POST /item`(创建+更新合一,更新分支 Service 层 assertCanWrite)
|
||||
|
||||
---
|
||||
|
||||
## 五、服务端配置(运维关注)
|
||||
|
||||
### 5.1 Nacos 灰度开关
|
||||
```yaml
|
||||
# hl-product-service-v2-dev.yml / -prod.yml
|
||||
hl:
|
||||
data-scope:
|
||||
enabled: true # 总开关;false 时切面不过滤(401 校验仍生效)
|
||||
reject-status: 404 # 越权访问响应码(CUSTOM 不泄露存在性)
|
||||
log-level: warn # 越权日志级别
|
||||
```
|
||||
|
||||
### 5.2 数据库迁移(上线前 DBA 执行)
|
||||
```sql
|
||||
-- 1. 补齐 created_by NULL 数据(防 R3 行级校验误伤)
|
||||
UPDATE product SET created_by=1, updated_by=COALESCE(updated_by, 1)
|
||||
WHERE created_by IS NULL AND deleted_at IS NULL;
|
||||
|
||||
UPDATE product_line SET created_by=1, updated_by=COALESCE(updated_by, 1)
|
||||
WHERE created_by IS NULL AND deleted_at IS NULL;
|
||||
|
||||
-- 2. 补齐 product_line.product_type NULL 数据
|
||||
UPDATE product_line SET product_type='CORE'
|
||||
WHERE (product_type IS NULL OR product_type='') AND deleted_at IS NULL;
|
||||
|
||||
-- 3. 组合索引
|
||||
ALTER TABLE product
|
||||
ADD INDEX idx_type_creator_deleted (product_type, created_by, deleted_at);
|
||||
ALTER TABLE product_line
|
||||
ADD INDEX idx_type_creator (product_type, created_by);
|
||||
```
|
||||
|
||||
详见 `hl-product-service-v2/src/main/resources/db/migration/V20260418__data_scope_created_by_backfill.sql`。
|
||||
|
||||
---
|
||||
|
||||
## 六、部署影响
|
||||
|
||||
需要**重启 hl-product-service-v2**。其他服务无影响(/mp/**、/internal/**、前端静态资源均未变)。
|
||||
|
||||
---
|
||||
|
||||
## 七、设计文档
|
||||
|
||||
完整需求 / 评审 / 架构 / 测试报告详见 PR 关联的任务文档 `docs/tasks/20260418_设计_产品线与产品数据权限.md`(1600+ 行)。
|
||||
|
||||
---
|
||||
|
||||
## 八、本地验证摘录(smoke test 结果)
|
||||
|
||||
| 测试场景 | 预期 | 实际 |
|
||||
|---------|------|------|
|
||||
| 超管列表 /item/list | total=7396 全 canEdit=true | ✅ |
|
||||
| CUSTOMIZER 列表 | total=7394(自动过滤 2 条他人 CUSTOM) | ✅ |
|
||||
| CUSTOMIZER 查他人 CUSTOM 详情 | code=404 | ✅ |
|
||||
| CUSTOMIZER 查他人 CORE 详情 | code=200, canEdit=false, isOwner=false | ✅ |
|
||||
| CUSTOMIZER 删他人 CORE | code=403 | ✅ |
|
||||
| CUSTOMIZER 删他人 CUSTOM | code=403 | ✅ |
|
||||
| 超管查 CUSTOM 详情 | code=200, canEdit=true, isOwner=false(超管不开后门) | ✅ |
|
||||
| 无 Token | code=401 | ✅ |
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户