Files
junhong_cmp_fiber/openspec/specs/data-scope-middleware/spec.md
huang 03a0960c4d
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m2s
refactor: 数据权限过滤从 GORM Callback 改为 Store 层显式调用
- 移除 RegisterDataPermissionCallback 和 SkipDataPermission 机制
- 在 Auth 中间件预计算 SubordinateShopIDs 并注入 Context
- 新增 ApplyShopFilter/ApplyEnterpriseFilter/ApplyOwnerShopFilter 等 Helper 函数
- 所有 Store 层查询方法显式调用数据权限过滤函数
- 权限检查函数 CanManageShop/CanManageEnterprise 改为从 Context 获取数据

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-26 16:38:52 +08:00

4.9 KiB
Raw Blame History

data-scope-middleware Specification

Purpose

数据权限范围中间件,负责在请求入口预计算用户的数据访问范围并注入 Context供业务层显式使用。

Requirements

Requirement: UserContextInfo 扩展

系统 SHALL 扩展 UserContextInfo 结构体以包含预计算的数据权限范围。

Scenario: 代理用户包含下级店铺 ID 列表

  • WHEN 代理用户登录成功
  • AND 用户有关联的店铺 ID
  • THEN UserContextInfo.SubordinateShopIDs 包含自己店铺及所有下级店铺的 ID 列表

Scenario: 平台用户/超管不限制

  • WHEN 平台用户或超级管理员登录成功
  • THEN UserContextInfo.SubordinateShopIDs 为 nil
  • AND nil 表示不受数据权限限制

Scenario: 企业用户使用 EnterpriseID

  • WHEN 企业用户登录成功
  • THEN UserContextInfo.EnterpriseID 包含用户所属企业 ID
  • AND UserContextInfo.SubordinateShopIDs 为 nil

Requirement: Auth 中间件预计算

系统 SHALL 在 Auth 中间件中预计算用户的数据访问范围。

Scenario: 代理用户预计算下级店铺

  • WHEN Auth 中间件验证 token 成功
  • AND 用户类型为代理用户
  • AND 用户有关联的店铺 ID
  • THEN 调用 GetSubordinateShopIDs 获取下级店铺 ID 列表
  • AND 将结果设置到 UserContextInfo.SubordinateShopIDs

Scenario: 获取下级店铺失败降级处理

  • WHEN 调用 GetSubordinateShopIDs 失败
  • THEN SubordinateShopIDs 降级为只包含用户自己的店铺 ID
  • AND 记录 Error 日志

Scenario: 非代理用户跳过预计算

  • WHEN Auth 中间件验证 token 成功
  • AND 用户类型不是代理用户
  • THEN 不调用 GetSubordinateShopIDs
  • AND SubordinateShopIDs 保持为 nil

Requirement: Context 数据获取函数

系统 SHALL 提供从 Context 获取数据权限范围的函数。

Scenario: 获取下级店铺 ID 列表

  • WHEN 调用 GetSubordinateShopIDs(ctx)
  • AND Context 包含 SubordinateShopIDs
  • THEN 返回下级店铺 ID 列表

Scenario: 获取空列表表示不限制

  • WHEN 调用 GetSubordinateShopIDs(ctx)
  • AND Context 中 SubordinateShopIDs 为 nil
  • THEN 返回 nil
  • AND 调用方应理解 nil 表示不受数据权限限制

Requirement: 查询过滤 Helper 函数

系统 SHALL 提供查询过滤 Helper 函数,供 Store 层显式调用。

Scenario: ApplyShopFilter 过滤店铺数据

  • WHEN 调用 ApplyShopFilter(ctx, query)
  • AND SubordinateShopIDs 不为 nil
  • THEN 返回添加了 WHERE shop_id IN (?) 条件的查询
  • AND 参数为 SubordinateShopIDs

Scenario: ApplyShopFilter 不限制时不添加条件

  • WHEN 调用 ApplyShopFilter(ctx, query)
  • AND SubordinateShopIDs 为 nil
  • THEN 返回原查询,不添加任何条件

Scenario: ApplyEnterpriseFilter 过滤企业数据

  • WHEN 调用 ApplyEnterpriseFilter(ctx, query)
  • AND 用户类型为企业用户
  • AND EnterpriseID 大于 0
  • THEN 返回添加了 WHERE enterprise_id = ? 条件的查询

Scenario: ApplyEnterpriseFilter 非企业用户不添加条件

  • WHEN 调用 ApplyEnterpriseFilter(ctx, query)
  • AND 用户类型不是企业用户
  • THEN 返回原查询,不添加任何条件

Scenario: ApplyOwnerShopFilter 过滤归属店铺数据

  • WHEN 调用 ApplyOwnerShopFilter(ctx, query)
  • AND SubordinateShopIDs 不为 nil
  • THEN 返回添加了 WHERE owner_shop_id IN (?) 条件的查询

Requirement: 权限检查函数改造

系统 SHALL 改造权限检查函数,从 Context 获取数据而非传入 Store。

Scenario: CanManageShop 从 Context 获取数据

  • WHEN 调用 CanManageShop(ctx, targetShopID)
  • AND 用户类型为代理用户
  • THEN 从 Context 获取 SubordinateShopIDs
  • AND 检查 targetShopID 是否在列表中

Scenario: CanManageShop 平台用户自动通过

  • WHEN 调用 CanManageShop(ctx, targetShopID)
  • AND SubordinateShopIDs 为 nil
  • THEN 返回成功(不受限制)

Scenario: CanManageEnterprise 从 Context 获取数据

  • WHEN 调用 CanManageEnterprise(ctx, targetEnterpriseID)
  • AND 用户类型为代理用户
  • THEN 从 Context 获取 SubordinateShopIDs
  • AND 查询目标企业的 owner_shop_id
  • AND 检查 owner_shop_id 是否在列表中

Requirement: AuthConfig 扩展

系统 SHALL 扩展 AuthConfig 以支持传入 ShopStore。

Scenario: AuthConfig 包含 ShopStore

  • WHEN 初始化 Auth 中间件
  • THEN AuthConfig 可选包含 ShopStore ShopStoreInterface
  • AND 用于调用 GetSubordinateShopIDs

Scenario: ShopStore 未配置时跳过预计算

  • WHEN AuthConfig.ShopStore 为 nil
  • THEN 不预计算 SubordinateShopIDs
  • AND 所有用户的 SubordinateShopIDs 为 nil