package polling import ( "context" "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/middleware" ) // 本文件承载人工轮询入口共享的权限判定:既有人工触发与人工优先入队必须使用同一套语义, // 因此判定只在此处实现一次,调用方不得各自内联,避免权限口径漂移。 // pollingUserTypePermission 检查用户类型是否有手动轮询权限。 // 返回 skip=true 表示超级管理员/平台用户直接放行; // 返回 err!=nil 表示企业账号无权限; // 返回 skip=false, err=nil 表示代理账号需继续细粒度检查。 func pollingUserTypePermission(ctx context.Context) (skip bool, err error) { userType := middleware.GetUserTypeFromContext(ctx) if userType == constants.UserTypeSuperAdmin || userType == constants.UserTypePlatform { return true, nil } if userType == constants.UserTypeEnterprise { return false, errors.New(errors.CodeForbidden, "企业账号无权限手动触发轮询") } return false, nil } // pollingDeniedMessage 是优先轮询入口对外统一的拒绝文案:越权与不存在共用,不产生可枚举差异。 const pollingDeniedMessage = "无权限操作该资源或资源不存在" // PriorityPollingReadScope 返回卡轮询优先项读侧可访问的店铺快照范围。 // // 判定顺序与人工入队共用同一用户类型分类(pollingUserTypePermission): // 1. 超级管理员与平台账号 → 不受限(返回 nil); // 2. 企业账号 → 拒绝; // 3. 其余(代理)→ 取认证中间件预计算的下级店铺集合;集合缺失(nil)是「未预计算」而不是 // 「不受限」,一律按拒绝处理,绝不让非代理身份或缺失范围回退成全量读取。 func PriorityPollingReadScope(ctx context.Context) ([]uint, error) { skip, err := pollingUserTypePermission(ctx) if err != nil { // 企业账号同样不允许读取优先轮询事实;对外只暴露统一的不可枚举拒绝文案。 return nil, errors.New(errors.CodeForbidden, pollingDeniedMessage) } if skip { return nil, nil } shopIDs := middleware.GetSubordinateShopIDs(ctx) if shopIDs == nil { return nil, errors.New(errors.CodeForbidden, pollingDeniedMessage) } return shopIDs, nil } // canManagePollingCard 检查用户是否有权管理单张卡。 func canManagePollingCard(ctx context.Context, iotCardStore *postgres.IotCardStore, cardID uint) error { skip, err := pollingUserTypePermission(ctx) if err != nil || skip { return err } // 代理账号只能管理自己店铺及下级店铺的卡 card, err := iotCardStore.GetByID(ctx, cardID) if err != nil { return errors.Wrap(errors.CodeForbidden, err, "无权限操作该资源或资源不存在") } // 平台卡(ShopID为nil)代理不能管理 if card.ShopID == nil { return errors.New(errors.CodeForbidden, "无权限操作平台卡") } // 检查代理是否有权管理该店铺 return middleware.CanManageShop(ctx, *card.ShopID) } // canManagePollingCards 检查用户是否有权管理多张卡。 func canManagePollingCards(ctx context.Context, iotCardStore *postgres.IotCardStore, cardIDs []uint) error { skip, err := pollingUserTypePermission(ctx) if err != nil || skip { return err } // 从 Context 获取预计算的下级店铺 ID 列表 subordinateIDs := middleware.GetSubordinateShopIDs(ctx) if subordinateIDs == nil { // 平台用户/超管不受限制,但这里不应该进入(前面已经检查过用户类型) return errors.New(errors.CodeForbidden, "无权限操作") } // 构建可管理的店铺ID集合 allowedShopIDs := make(map[uint]bool) for _, id := range subordinateIDs { allowedShopIDs[id] = true } // 批量查询卡信息 cards, err := iotCardStore.GetByIDs(ctx, cardIDs) if err != nil { return errors.Wrap(errors.CodeForbidden, err, "查询卡信息失败") } // 验证所有卡都在可管理范围内 for _, card := range cards { if card.ShopID == nil { return errors.New(errors.CodeForbidden, "无权限操作平台卡") } if !allowedShopIDs[*card.ShopID] { return errors.New(errors.CodeForbidden, "包含无权限操作的卡") } } return nil }