refactor: 数据权限过滤从 GORM Callback 改为 Store 层显式调用
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m2s

- 移除 RegisterDataPermissionCallback 和 SkipDataPermission 机制
- 在 Auth 中间件预计算 SubordinateShopIDs 并注入 Context
- 新增 ApplyShopFilter/ApplyEnterpriseFilter/ApplyOwnerShopFilter 等 Helper 函数
- 所有 Store 层查询方法显式调用数据权限过滤函数
- 权限检查函数 CanManageShop/CanManageEnterprise 改为从 Context 获取数据

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 16:38:52 +08:00
parent 4ba1f5b99d
commit 03a0960c4d
46 changed files with 1573 additions and 705 deletions

View File

@@ -4,16 +4,17 @@ import "time"
// Fiber Locals 的上下文键
const (
ContextKeyRequestID = "requestid" // 请求记录ID
ContextKeyStartTime = "start_time" // 请求开始时间
ContextKeyUserID = "user_id" // 用户ID
ContextKeyUserType = "user_type" // 用户类型
ContextKeyShopID = "shop_id" // 店铺ID
ContextKeyEnterpriseID = "enterprise_id" // 企业ID
ContextKeyCustomerID = "customer_id" // 个人客户ID
ContextKeyUserInfo = "user_info" // 完整的用户信息
ContextKeyIP = "ip_address" // IP地址
ContextKeyUserAgent = "user_agent" // User-Agent
ContextKeyRequestID = "requestid" // 请求记录ID
ContextKeyStartTime = "start_time" // 请求开始时间
ContextKeyUserID = "user_id" // 用户ID
ContextKeyUserType = "user_type" // 用户类型
ContextKeyShopID = "shop_id" // 店铺ID
ContextKeyEnterpriseID = "enterprise_id" // 企业ID
ContextKeyCustomerID = "customer_id" // 个人客户ID
ContextKeyUserInfo = "user_info" // 完整的用户信息
ContextKeyIP = "ip_address" // IP地址
ContextKeyUserAgent = "user_agent" // User-Agent
ContextKeySubordinateShopIDs = "subordinate_shop_ids" // 下级店铺ID列表代理用户预计算
)
// 配置环境变量

View File

@@ -1,250 +1,12 @@
package gorm
import (
"context"
"reflect"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/logger"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"go.uber.org/zap"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
// contextKey 用于 context value 的 key 类型
type contextKey string
// SkipDataPermissionKey 跳过数据权限过滤的 context key
const SkipDataPermissionKey contextKey = "skip_data_permission"
// SkipDataPermission 返回跳过数据权限过滤的 Context
// 用于需要查询所有数据的场景(如管理后台统计、系统任务等)
//
// 使用示例:
//
// ctx = gorm.SkipDataPermission(ctx)
// db.WithContext(ctx).Find(&accounts)
func SkipDataPermission(ctx context.Context) context.Context {
return context.WithValue(ctx, SkipDataPermissionKey, true)
}
// ShopStoreInterface 店铺 Store 接口
// 用于 Callback 获取下级店铺 ID避免循环依赖
type ShopStoreInterface interface {
GetSubordinateShopIDs(ctx context.Context, shopID uint) ([]uint, error)
}
// RegisterDataPermissionCallback 注册 GORM 数据权限过滤 Callback
//
// 自动化数据权限过滤规则:
// 1. 超级管理员跳过过滤,可以查看所有数据
// 2. 平台用户跳过过滤,可以查看所有数据
// 3. 代理用户只能查看自己店铺及下级店铺的数据(基于 shop_id 字段)
// 4. 企业用户只能查看自己企业的数据(基于 enterprise_id 字段)
// 5. 个人客户只能查看自己的数据(基于 creator 字段或 customer_id 字段)
// 6. 通过 SkipDataPermission(ctx) 可以绕过权限过滤
//
// 软删除过滤规则:
// 1. 所有查询自动排除 deleted_at IS NOT NULL 的记录
// 2. 使用 db.Unscoped() 可以查询包含已删除的记录
//
// 注意:
// - Callback 根据表的字段自动选择过滤策略
// - 必须在初始化 Store 之前注册
//
// 参数:
// - db: GORM DB 实例
// - shopStore: 店铺 Store用于查询下级店铺 ID
//
// 返回:
// - error: 注册错误
func RegisterDataPermissionCallback(db *gorm.DB, shopStore ShopStoreInterface) error {
// 注册查询前的 Callback
err := db.Callback().Query().Before("gorm:query").Register("data_permission:query", func(tx *gorm.DB) {
ctx := tx.Statement.Context
if ctx == nil {
return
}
// 1. 检查是否跳过数据权限过滤
if skip, ok := ctx.Value(SkipDataPermissionKey).(bool); ok && skip {
return
}
// 2. 获取用户类型
userType := middleware.GetUserTypeFromContext(ctx)
// 3. 超级管理员和平台用户跳过过滤,可以查看所有数据
if userType == constants.UserTypeSuperAdmin || userType == constants.UserTypePlatform {
return
}
// 4. 获取当前用户信息
userID := middleware.GetUserIDFromContext(ctx)
if userID == 0 {
// 未登录用户返回空结果
logger.GetAppLogger().Warn("数据权限过滤:未获取到用户 ID")
tx.Where("1 = 0")
return
}
shopID := middleware.GetShopIDFromContext(ctx)
// 5. 根据用户类型和表结构应用不同的过滤规则
schema := tx.Statement.Schema
if schema == nil {
return
}
// 5.1 代理用户:基于店铺层级过滤
if userType == constants.UserTypeAgent {
tableName := schema.Table
// 特殊处理:授权记录表(通过企业归属过滤,不含下级店铺)
if tableName == "tb_enterprise_card_authorization" {
if shopID == 0 {
// 代理用户没有 shop_id返回空结果
tx.Where("1 = 0")
return
}
// 只能看到自己店铺下企业的授权记录(不包含下级店铺)
tx.Where("enterprise_id IN (SELECT id FROM tb_enterprise WHERE owner_shop_id = ? AND deleted_at IS NULL)", shopID)
return
}
// 特殊处理:标签表和资源标签表(包含全局标签)
if tableName == "tb_tag" || tableName == "tb_resource_tag" {
if shopID == 0 {
// 没有 shop_id只能看全局标签
tx.Where("enterprise_id IS NULL AND shop_id IS NULL")
return
}
// 查询该店铺及下级店铺的 ID
subordinateShopIDs, err := shopStore.GetSubordinateShopIDs(ctx, shopID)
if err != nil {
logger.GetAppLogger().Error("数据权限过滤:获取下级店铺 ID 失败",
zap.Uint("shop_id", shopID),
zap.Error(err))
subordinateShopIDs = []uint{shopID}
}
// 过滤:店铺标签(自己店铺及下级店铺)或全局标签
tx.Where("shop_id IN ? OR (enterprise_id IS NULL AND shop_id IS NULL)", subordinateShopIDs)
return
}
if !hasShopIDField(schema) {
// 表没有 shop_id 字段,无法过滤
return
}
if shopID == 0 {
// 代理用户没有 shop_id只能看自己创建的数据
if hasCreatorField(schema) {
tx.Where("creator = ?", userID)
} else {
tx.Where("1 = 0")
}
return
}
// 查询该店铺及下级店铺的 ID
subordinateShopIDs, err := shopStore.GetSubordinateShopIDs(ctx, shopID)
if err != nil {
logger.GetAppLogger().Error("数据权限过滤:获取下级店铺 ID 失败",
zap.Uint("shop_id", shopID),
zap.Error(err))
// 降级为只能看自己店铺的数据
subordinateShopIDs = []uint{shopID}
}
// 过滤shop_id IN (自己店铺及下级店铺)
tx.Where("shop_id IN ?", subordinateShopIDs)
return
}
// 5.2 企业用户:基于 enterprise_id 过滤
if userType == constants.UserTypeEnterprise {
enterpriseID := middleware.GetEnterpriseIDFromContext(ctx)
tableName := schema.Table
// 特殊处理:标签表和资源标签表(包含全局标签)
if tableName == "tb_tag" || tableName == "tb_resource_tag" {
if enterpriseID != 0 {
// 过滤:企业标签或全局标签
tx.Where("enterprise_id = ? OR (enterprise_id IS NULL AND shop_id IS NULL)", enterpriseID)
} else {
// 没有 enterprise_id只能看全局标签
tx.Where("enterprise_id IS NULL AND shop_id IS NULL")
}
return
}
if hasEnterpriseIDField(schema) {
if enterpriseID != 0 {
tx.Where("enterprise_id = ?", enterpriseID)
} else {
// 企业用户没有 enterprise_id返回空结果
tx.Where("1 = 0")
}
return
}
// 如果表没有 enterprise_id 字段,但有 creator 字段,则只能看自己创建的数据
if hasCreatorField(schema) {
tx.Where("creator = ?", userID)
return
}
// 无法过滤,返回空结果
tx.Where("1 = 0")
return
}
// 5.3 个人客户:只能看自己的数据
if userType == constants.UserTypePersonalCustomer {
customerID := middleware.GetCustomerIDFromContext(ctx)
tableName := schema.Table
// 特殊处理:标签表和资源标签表(只能看全局标签)
if tableName == "tb_tag" || tableName == "tb_resource_tag" {
tx.Where("enterprise_id IS NULL AND shop_id IS NULL")
return
}
// 优先使用 customer_id 字段
if hasCustomerIDField(schema) {
if customerID != 0 {
tx.Where("customer_id = ?", customerID)
} else {
// 个人客户没有 customer_id返回空结果
tx.Where("1 = 0")
}
return
}
// 降级为使用 creator 字段
if hasCreatorField(schema) {
tx.Where("creator = ?", userID)
return
}
// 无法过滤,返回空结果
tx.Where("1 = 0")
return
}
// 6. 默认:未知用户类型,返回空结果
logger.GetAppLogger().Warn("数据权限过滤:未知用户类型",
zap.Uint("user_id", userID),
zap.Int("user_type", userType))
tx.Where("1 = 0")
})
return err
}
// RegisterSetCreatorUpdaterCallback 注册 GORM 创建数据时创建人更新人 Callback
func RegisterSetCreatorUpdaterCallback(db *gorm.DB) error {
err := db.Callback().Create().Before("gorm:create").Register("set_creator_updater", func(tx *gorm.DB) {
@@ -296,48 +58,3 @@ func RegisterSetCreatorUpdaterCallback(db *gorm.DB) error {
})
return err
}
// hasCreatorField 检查 Schema 是否包含 creator 字段
func hasCreatorField(s *schema.Schema) bool {
if s == nil {
return false
}
_, ok := s.FieldsByDBName["creator"]
return ok
}
// hasShopIDField 检查 Schema 是否包含 shop_id 字段
func hasShopIDField(s *schema.Schema) bool {
if s == nil {
return false
}
_, ok := s.FieldsByDBName["shop_id"]
return ok
}
// hasEnterpriseIDField 检查 Schema 是否包含 enterprise_id 字段
func hasEnterpriseIDField(s *schema.Schema) bool {
if s == nil {
return false
}
_, ok := s.FieldsByDBName["enterprise_id"]
return ok
}
// hasCustomerIDField 检查 Schema 是否包含 customer_id 字段
func hasCustomerIDField(s *schema.Schema) bool {
if s == nil {
return false
}
_, ok := s.FieldsByDBName["customer_id"]
return ok
}
// hasDeletedAtField 检查 Schema 是否包含 deleted_at 字段
func hasDeletedAtField(s *schema.Schema) bool {
if s == nil {
return false
}
_, ok := s.FieldsByDBName["deleted_at"]
return ok
}

View File

@@ -5,16 +5,19 @@ import (
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/logger"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
// UserContextInfo 用户上下文信息
type UserContextInfo struct {
UserID uint
UserType int
ShopID uint
EnterpriseID uint
CustomerID uint
UserID uint
UserType int
ShopID uint
EnterpriseID uint
CustomerID uint
SubordinateShopIDs []uint // 代理用户的下级店铺ID列表nil 表示不受数据权限限制
}
// SetUserContext 将用户信息设置到 context 中
@@ -25,6 +28,10 @@ func SetUserContext(ctx context.Context, info *UserContextInfo) context.Context
ctx = context.WithValue(ctx, constants.ContextKeyShopID, info.ShopID)
ctx = context.WithValue(ctx, constants.ContextKeyEnterpriseID, info.EnterpriseID)
ctx = context.WithValue(ctx, constants.ContextKeyCustomerID, info.CustomerID)
// SubordinateShopIDs: nil 表示不限制,空切片表示无权限
if info.SubordinateShopIDs != nil {
ctx = context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, info.SubordinateShopIDs)
}
return ctx
}
@@ -134,12 +141,21 @@ func SetUserToFiberContext(c *fiber.Ctx, info *UserContextInfo) {
c.Locals(constants.ContextKeyShopID, info.ShopID)
c.Locals(constants.ContextKeyEnterpriseID, info.EnterpriseID)
c.Locals(constants.ContextKeyCustomerID, info.CustomerID)
if info.SubordinateShopIDs != nil {
c.Locals(constants.ContextKeySubordinateShopIDs, info.SubordinateShopIDs)
}
// 设置到标准 context用于 GORM 数据权限过滤)
// 设置到标准 context用于数据权限过滤
ctx := SetUserContext(c.UserContext(), info)
c.SetUserContext(ctx)
}
// AuthShopStoreInterface 店铺存储接口
// 用于 Auth 中间件获取下级店铺 ID避免循环依赖
type AuthShopStoreInterface interface {
GetSubordinateShopIDs(ctx context.Context, shopID uint) ([]uint, error)
}
// AuthConfig Auth 中间件配置
type AuthConfig struct {
// TokenExtractor 自定义 token 提取函数
@@ -153,6 +169,10 @@ type AuthConfig struct {
// SkipPaths 跳过认证的路径列表
SkipPaths []string
// ShopStore 店铺存储,用于预计算代理用户的下级店铺 ID
// 可选,不传则不预计算 SubordinateShopIDs
ShopStore AuthShopStoreInterface
}
// Auth 认证中间件
@@ -196,6 +216,21 @@ func Auth(config AuthConfig) fiber.Handler {
return errors.Wrap(errors.CodeInvalidToken, err, "认证令牌无效")
}
// 预计算代理用户的下级店铺 ID
if config.ShopStore != nil &&
userInfo.UserType == constants.UserTypeAgent &&
userInfo.ShopID > 0 {
shopIDs, err := config.ShopStore.GetSubordinateShopIDs(c.UserContext(), userInfo.ShopID)
if err != nil {
// 降级处理:只包含自己的店铺 ID
shopIDs = []uint{userInfo.ShopID}
logger.GetAppLogger().Warn("预计算下级店铺失败,降级为只包含自己",
zap.Uint("shop_id", userInfo.ShopID),
zap.Error(err))
}
userInfo.SubordinateShopIDs = shopIDs
}
// 将用户信息设置到 context
SetUserToFiberContext(c, userInfo)

View File

@@ -0,0 +1,91 @@
package middleware
import (
"context"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"gorm.io/gorm"
)
// GetSubordinateShopIDs 获取当前用户可管理的店铺ID列表
// 返回 nil 表示不受数据权限限制(平台用户/超管)
// 返回 []uint 表示限制在这些店铺范围内(代理用户)
func GetSubordinateShopIDs(ctx context.Context) []uint {
if ctx == nil {
return nil
}
if ids, ok := ctx.Value(constants.ContextKeySubordinateShopIDs).([]uint); ok {
return ids
}
return nil
}
// ApplyShopFilter 应用店铺数据权限过滤
// 平台用户/超管不添加条件SubordinateShopIDs 为 nil
// 代理用户WHERE shop_id IN (subordinateShopIDs)
// 注意NULL shop_id 的记录对代理用户不可见
func ApplyShopFilter(ctx context.Context, query *gorm.DB) *gorm.DB {
shopIDs := GetSubordinateShopIDs(ctx)
if shopIDs == nil {
return query
}
return query.Where("shop_id IN ?", shopIDs)
}
// ApplyEnterpriseFilter 应用企业数据权限过滤
// 非企业用户:不添加条件
// 企业用户WHERE enterprise_id = ?
func ApplyEnterpriseFilter(ctx context.Context, query *gorm.DB) *gorm.DB {
userType := GetUserTypeFromContext(ctx)
if userType != constants.UserTypeEnterprise {
return query
}
enterpriseID := GetEnterpriseIDFromContext(ctx)
if enterpriseID == 0 {
// 企业用户但无企业ID返回空结果
return query.Where("1 = 0")
}
return query.Where("enterprise_id = ?", enterpriseID)
}
// ApplyOwnerShopFilter 应用归属店铺数据权限过滤
// 用于 Enterprise 等使用 owner_shop_id 字段的表
// 平台用户/超管:不添加条件
// 代理用户WHERE owner_shop_id IN (subordinateShopIDs)
func ApplyOwnerShopFilter(ctx context.Context, query *gorm.DB) *gorm.DB {
shopIDs := GetSubordinateShopIDs(ctx)
if shopIDs == nil {
return query
}
return query.Where("owner_shop_id IN ?", shopIDs)
}
// IsUnrestricted 检查当前用户是否不受数据权限限制
// 平台用户/超管返回 true代理/企业用户返回 false
func IsUnrestricted(ctx context.Context) bool {
return GetSubordinateShopIDs(ctx) == nil
}
// ApplySellerShopFilter 应用销售店铺数据权限过滤
// 用于 Order 等使用 seller_shop_id 字段的表
// 平台用户/超管:不添加条件
// 代理用户WHERE seller_shop_id IN (subordinateShopIDs)
func ApplySellerShopFilter(ctx context.Context, query *gorm.DB) *gorm.DB {
shopIDs := GetSubordinateShopIDs(ctx)
if shopIDs == nil {
return query
}
return query.Where("seller_shop_id IN ?", shopIDs)
}
// ApplyShopTagFilter 应用店铺标签数据权限过滤
// 用于 CardWallet 等使用 shop_id_tag 字段的表
// 平台用户/超管:不添加条件
// 代理用户WHERE shop_id_tag IN (subordinateShopIDs)
func ApplyShopTagFilter(ctx context.Context, query *gorm.DB) *gorm.DB {
shopIDs := GetSubordinateShopIDs(ctx)
if shopIDs == nil {
return query
}
return query.Where("shop_id_tag IN ?", shopIDs)
}

View File

@@ -2,20 +2,13 @@ package middleware
import (
"context"
"slices"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// ShopStoreInterface 店铺存储接口
// 用于权限检查时查询店铺信息和下级店铺ID
type ShopStoreInterface interface {
GetByID(ctx context.Context, id uint) (*model.Shop, error)
GetByIDs(ctx context.Context, ids []uint) ([]*model.Shop, error)
GetSubordinateShopIDs(ctx context.Context, shopID uint) ([]uint, error)
}
// EnterpriseStoreInterface 企业存储接口
// 用于权限检查时查询企业信息
type EnterpriseStoreInterface interface {
@@ -23,91 +16,80 @@ type EnterpriseStoreInterface interface {
GetByIDs(ctx context.Context, ids []uint) ([]*model.Enterprise, error)
}
// CanManageShop 检查当前用户是否有权管理目标店铺的账号
// 超级管理员和平台用户自动通过
// 代理账号只能管理自己店铺及下级店铺的账号
// 企业账号禁止管理店铺账号
func CanManageShop(ctx context.Context, targetShopID uint, shopStore ShopStoreInterface) error {
// CanManageShop 检查当前用户是否有权管理目标店铺
// 超级管理员和平台用户自动通过SubordinateShopIDs 为 nil
// 代理账号只能管理自己店铺及下级店铺
// 企业账号禁止管理店铺
func CanManageShop(ctx context.Context, targetShopID uint) error {
userType := GetUserTypeFromContext(ctx)
// 超级管理员和平台用户跳过权限检查
if userType == constants.UserTypeSuperAdmin || userType == constants.UserTypePlatform {
// 企业账号禁止管理店铺
if userType == constants.UserTypeEnterprise {
return errors.New(errors.CodeForbidden, "无权限管理店铺")
}
// 从 Context 获取预计算的下级店铺 ID 列表
subordinateIDs := GetSubordinateShopIDs(ctx)
// nil 表示不受限制(超级管理员/平台用户)
if subordinateIDs == nil {
return nil
}
// 企业账号禁止管理店铺账号
if userType != constants.UserTypeAgent {
return errors.New(errors.CodeForbidden, "无权限管理店铺账号")
}
// 获取当前代理账号的店铺ID
currentShopID := GetShopIDFromContext(ctx)
if currentShopID == 0 {
return errors.New(errors.CodeForbidden, "无权限管理店铺账号")
}
// 递归查询下级店铺ID包含自己
subordinateIDs, err := shopStore.GetSubordinateShopIDs(ctx, currentShopID)
if err != nil {
return errors.Wrap(errors.CodeInternalError, err, "查询下级店铺失败")
}
// 检查目标店铺是否在下级列表中
for _, id := range subordinateIDs {
if id == targetShopID {
return nil
}
}
return errors.New(errors.CodeForbidden, "无权限管理该店铺的账号")
}
// CanManageEnterprise 检查当前用户是否有权管理目标企业的账号
// 超级管理员和平台用户自动通过
// 代理账号只能管理归属于自己店铺或下级店铺的企业账号
// 企业账号禁止管理其他企业账号
func CanManageEnterprise(ctx context.Context, targetEnterpriseID uint, enterpriseStore EnterpriseStoreInterface, shopStore ShopStoreInterface) error {
userType := GetUserTypeFromContext(ctx)
// 超级管理员和平台用户跳过权限检查
if userType == constants.UserTypeSuperAdmin || userType == constants.UserTypePlatform {
if slices.Contains(subordinateIDs, targetShopID) {
return nil
}
// 企业账号禁止管理其他企业账号
if userType != constants.UserTypeAgent {
return errors.New(errors.CodeForbidden, "无权限管理企业账号")
return errors.New(errors.CodeForbidden, "无权限管理该店铺")
}
// CanManageEnterprise 检查当前用户是否有权管理目标企业
// 超级管理员和平台用户自动通过SubordinateShopIDs 为 nil
// 代理账号只能管理归属于自己店铺或下级店铺的企业
// 企业账号禁止管理其他企业
func CanManageEnterprise(ctx context.Context, targetEnterpriseID uint, enterpriseStore EnterpriseStoreInterface) error {
userType := GetUserTypeFromContext(ctx)
// 企业账号禁止管理其他企业
if userType == constants.UserTypeEnterprise {
return errors.New(errors.CodeForbidden, "无权限管理企业")
}
// 从 Context 获取预计算的下级店铺 ID 列表
subordinateIDs := GetSubordinateShopIDs(ctx)
// nil 表示不受限制(超级管理员/平台用户)
if subordinateIDs == nil {
return nil
}
// 获取目标企业信息
enterprise, err := enterpriseStore.GetByID(ctx, targetEnterpriseID)
if err != nil {
return errors.Wrap(errors.CodeForbidden, err, "无权限操作该资源或资源不存在")
return errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
}
// 代理账号不能管理平台级企业owner_shop_idNULL
// 代理账号不能管理平台级企业owner_shop_idNULL
if enterprise.OwnerShopID == nil {
return errors.New(errors.CodeForbidden, "无权限管理平台级企业账号")
}
// 获取当前代理账号的店铺ID
currentShopID := GetShopIDFromContext(ctx)
if currentShopID == 0 {
return errors.New(errors.CodeForbidden, "无权限管理企业账号")
}
// 递归查询下级店铺ID包含自己
subordinateIDs, err := shopStore.GetSubordinateShopIDs(ctx, currentShopID)
if err != nil {
return errors.Wrap(errors.CodeInternalError, err, "查询下级店铺失败")
return errors.New(errors.CodeForbidden, "无权限管理平台级企业")
}
// 检查企业归属的店铺是否在下级列表中
for _, id := range subordinateIDs {
if id == *enterprise.OwnerShopID {
return nil
}
if slices.Contains(subordinateIDs, *enterprise.OwnerShopID) {
return nil
}
return errors.New(errors.CodeForbidden, "无权限管理该企业的账号")
return errors.New(errors.CodeForbidden, "无权限管理该企业")
}
// ContainsShopID 检查目标店铺 ID 是否在当前用户可管理的店铺列表中
// 平台用户/超管返回 true不受限制
// 代理用户检查是否在 SubordinateShopIDs 中
func ContainsShopID(ctx context.Context, targetShopID uint) bool {
subordinateIDs := GetSubordinateShopIDs(ctx)
if subordinateIDs == nil {
return true // 不受限制
}
return slices.Contains(subordinateIDs, targetShopID)
}