You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

112 lines
4.1 KiB

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
}