hl-api-changelog/changelogs/2026-04/2026-04-18_product-v2_datascope-permission.md
wx 2cf5a54f9c feat(product-v2): 产品/产品线数据权限切面 @DataScope (PR #849)
- 管理端 37 个接口加 @DataScope
- 3 个 VO 加 canEdit/isOwner 字段
- CORE/GROUP 可见不可改、CUSTOM 仅自己
- 前端按 canEdit 灰化按钮
2026-04-18 20:04:03 +08:00

7.7 KiB

产品/产品线数据权限切面 @DataScope

合并时间2026-04-18 PRwx/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 分享 URLCUSTOM 产品给客户看)

  • /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"
  • 展示"我创建的" Tagv-if="row.isOwner"
  • 不要单独用 isOwner 判断按钮可用(超管时 isOwner=false 但应能操作 → 用 canEdit 判断才对)

2.5 不加字段的接口

  • ProductSimpleListVOGET /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 /listGET /simple-list
  • 详情GET /{id}GET /{id}/price-calendarGET /{id}/schedule/listGET /{id}/schedule/teamGET /{id}/suggest-priceGET /{id}/supplementGET /{id}/calculate-mileageGET /{id}/detail-blocksGET /{id}/operation-logsGET /{id}/validate-publishGET /{id}/fee-deduction-previewGET /{productId}/tiers
  • 写操作(行级校验):
    • PUT /{id}/itineraryPOST /{id}/itinerary/dayDELETE /{id}/itinerary/day/{dayNumber}
    • PUT /{id}/routePOST /{id}/price-calendar/batchDELETE /{id}/price-calendar
    • POST /{id}/scheduleDELETE /{id}/schedule/{scheduleId}PUT /{id}/schedulePOST /{id}/schedule/batch-createPOST /{id}/schedule/{scheduleId}/cancelPUT /{id}/schedule/team
    • POST /{id}/quotePUT /{id}/supplementPOST /{id}/daily-mileage
    • PUT /{id}/detail-blocksPOST /{id}/toggle-publishPOST /{id}/completePOST /{id}/action@DeprecatedDELETE /{id}
  • 不加切面,Service 层做权限
    • POST /basic(创建+更新合一,更新分支 Service 层 assertCanWrite
    • POST /{id}/copyService 层 assertCanRead,允许任何"可见者"复制;R9 规则)
    • PUT /{id}/transferService 层仅超管可操作)

4.2 AdminProductLineController/admin/product/line/**4 个

  • GET /listGET /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_设计_产品线与产品数据权限.md1600+ 行)。


八、本地验证摘录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