- 管理端 37 个接口加 @DataScope - 3 个 VO 加 canEdit/isOwner 字段 - CORE/GROUP 可见不可改、CUSTOM 仅自己 - 前端按 canEdit 灰化按钮
7.7 KiB
7.7 KiB
产品/产品线数据权限切面 @DataScope
合并时间:2026-04-18
PR:wx/HL#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)
{
"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(产品线列表 / 详情)
{
"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-calendarPOST /{id}/schedule、DELETE /{id}/schedule/{scheduleId}、PUT /{id}/schedule、POST /{id}/schedule/batch-create、POST /{id}/schedule/{scheduleId}/cancel、PUT /{id}/schedule/teamPOST /{id}/quote、PUT /{id}/supplement、POST /{id}/daily-mileagePUT /{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 灰度开关
# 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 执行)
-- 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 | ✅ |