package console import ( "yunyan/pb" "github.com/gin-gonic/gin" ) // 品牌商账号 / 渠道商账号的数据收敛(P3 渠道分成体系)。 // // 语义: // - 渠道商账号(5):锁死到自己那一个渠道商——由 acctScope.statsClause 直接出 channel_id = ?; // - 品牌商账号(3, 即代理):可见「本品牌商名下全部渠道商」的行,外加「本品牌商产品」的行 // (历史数据 channel_id 为空,只能靠产品归属找回,否则品牌商看不到自己迁移前的数据)。 // // 两条件取 OR:任一命中都是本品牌商的数据,不会串到别的品牌商——product 与 channel 都按 // brandid 归属,交集之外的行必然不属于本品牌商。 // // 这些收敛在后端强制执行,前端传什么过滤条件都只能在这个范围内再缩小。 // brandChannelIds 取某品牌商名下全部渠道商 id(含未启用的:历史统计行仍挂在它名下)。 func (this *serverComp) brandChannelIds(brandId uint32) []string { sys := consoleDeviceConn() list, err := dvChannels(sys, brandId, -1, "") if err != nil { return nil } out := make([]string, 0, len(list)) for _, ch := range list { out = append(out, ch.Id) } return out } // brandProductIds 取某品牌商名下全部产品 id(走产品缓存,避免每次查库)。 func (this *serverComp) brandProductIds(brandId uint32) []uint32 { products, err := this.module.deviceCache.GetProducts() if err != nil { return nil } out := make([]uint32, 0, 8) for _, p := range products { if p.Brandid == brandId { out = append(out, p.Id) } } return out } // statsScopeClause 组合出统计查询的账号作用域条件: // 先取通用收敛(应用/产品/区域/渠道商),品牌商账号再额外补上「本品牌商的渠道商 ∪ 产品」限定。 // 返回的条件由调用方与前端下钻条件 AND,越权不可能穿透。 func (this *serverComp) statsScopeClause(c *gin.Context) (string, []interface{}) { s := scopeOf(c) // 账号绑定里存的是应用名与国内/海外,统计表按部署粒度存,收敛前同样要展开(口径与前端下钻一致)。 s.apps = this.expandAppNames(s.apps) s.regions = expandRegionCodes(s.regions) where, args := s.statsClause() // 只有「品牌商账号」需要额外收敛:超管/管理员不受限;渠道商账号已被 statsClause 锁死。 if s.unlimited || s.brandId == 0 || s.channelId != "" { return where, args } channels := this.brandChannelIds(s.brandId) products := this.brandProductIds(s.brandId) var bw string var ba []interface{} switch { case len(channels) > 0 && len(products) > 0: bw = "(channel_id IN ? OR product_id IN ?)" ba = []interface{}{channels, products} case len(channels) > 0: bw = "channel_id IN ?" ba = []interface{}{channels} case len(products) > 0: bw = "product_id IN ?" ba = []interface{}{products} default: // 该品牌商既无渠道商也无产品:没有任何数据属于它,返回恒假而不是「不过滤」, // 否则会退化成看到全量。 bw = "1=0" } return andWhere(where, args, bw, ba) } // requireBrandScope 校验请求里的品牌商 id 在账号可见范围内;越界即写好错误响应并返回 false。 func (this *serverComp) requireBrandScope(c *gin.Context, brandId uint32) bool { if scopeOf(c).allowBrand(brandId) { return true } writeErr(c, pb.ErrorCode_InsufficientPermissions, "无权访问该品牌商的数据") return false } // effectiveBrandId 把请求里的品牌商过滤收敛到账号绑定:品牌商/渠道商账号一律强制成自己的 // brandid(忽略前端传值),其余角色按前端传值。用于列表类查询。 func effectiveBrandId(c *gin.Context, reqBrandId uint32) uint32 { s := scopeOf(c) if s.unlimited || s.brandId == 0 { return reqBrandId } return s.brandId } // effectiveChannelId 把请求里的渠道商过滤收敛到账号绑定:渠道商账号一律强制成自己的 channelid, // 其余角色按前端传值。 func effectiveChannelId(c *gin.Context, reqChannelId string) string { s := scopeOf(c) if s.unlimited || s.channelId == "" { return reqChannelId } return s.channelId }