文件
hl-api-changelog/changelogs/2026-04/2026-04-18_product-v2_datascope-permission.md
T
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
原始文件 Blame 文件历史

产品/产品线数据权限切面 @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)

{
  "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-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 灰度开关

# 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 ✅