model/diversion_bucket.go (121 lines of code) (raw):

package model import ( "crypto/md5" "fmt" "hash/fnv" "strconv" "strings" "github.com/Knetic/govaluate" "github.com/aliyun/aliyun-pairec-config-go-sdk/v2/common" ) // DiversionBucket is a interface by the bucket type to match diversion type DiversionBucket interface { Match(*ExperimentContext) bool } func NewDiversionBucket(bucketType uint32) DiversionBucket { if bucketType == common.Bucket_Type_UID { return &UidDiversionBucket{} } else if bucketType == common.Bucket_Type_UID_HASH { return &UidHashDiversionBucket{} } else if bucketType == common.Bucket_Type_Custom { return &CustomDiversionBucket{} } else if bucketType == common.Bucket_Type_Filter { return &FilterDiversionBucket{} } return nil } type UidDiversionBucket struct { buckets map[int]bool bucketCount int } func NewUidDiversionBucket(bucketCount int, bucketStr string) *UidDiversionBucket { diversionBucket := &UidDiversionBucket{ bucketCount: bucketCount, buckets: make(map[int]bool), } expBuckets := strings.Split(bucketStr, ",") for _, bucket := range expBuckets { if strings.Contains(bucket, "-") { bucketStrings := strings.Split(bucket, "-") if len(bucketStrings) == 2 { start, err1 := strconv.Atoi(bucketStrings[0]) end, err2 := strconv.Atoi(bucketStrings[1]) if err1 == nil && err2 == nil { for i := start; i < end; i++ { if i > int(diversionBucket.bucketCount) { break } diversionBucket.buckets[i] = true } } } } else { if val, err := strconv.Atoi(bucket); err == nil { diversionBucket.buckets[val] = true } } } return diversionBucket } func (b *UidDiversionBucket) Match(experimentContext *ExperimentContext) bool { uid, err := strconv.ParseUint(experimentContext.Uid, 10, 64) if err != nil { return false } mod := uid % uint64(b.bucketCount) if _, found := b.buckets[int(mod)]; found { return true } return false } type UidHashDiversionBucket struct { *UidDiversionBucket } func NewUidHashDiversionBucket(bucketCount int, bucketStr string) *UidHashDiversionBucket { diversionBucket := &UidHashDiversionBucket{ UidDiversionBucket: NewUidDiversionBucket(bucketCount, bucketStr), } return diversionBucket } func (b *UidHashDiversionBucket) Match(experimentContext *ExperimentContext) bool { md5 := md5.Sum([]byte(experimentContext.Uid)) hash := fnv.New64() hash.Write(md5[:]) mod := hash.Sum64() % uint64(b.bucketCount) if _, found := b.buckets[int(mod)]; found { return true } return false } type CustomDiversionBucket struct { } func NewCustomDiversionBucket() *CustomDiversionBucket { return &CustomDiversionBucket{} } func (b *CustomDiversionBucket) Match(experimentContext *ExperimentContext) bool { return false } type FilterDiversionBucket struct { Filter string EvaluableExpression *govaluate.EvaluableExpression } // NewFilterDiversionBucket return instance of FilterDiversionBucket func NewFilterDiversionBucket(filter string) (*FilterDiversionBucket, error) { diversionBucket := &FilterDiversionBucket{ Filter: filter, } evaluableExpression, err := govaluate.NewEvaluableExpression(filter) if err != nil { return nil, err } diversionBucket.EvaluableExpression = evaluableExpression return diversionBucket, nil } // Match is a function of FilterDiversionBucket implements the DiversionBucket interface func (b *FilterDiversionBucket) Match(experimentContext *ExperimentContext) bool { if b.EvaluableExpression != nil && experimentContext.FilterParams != nil { if result, err := b.EvaluableExpression.Evaluate(experimentContext.FilterParams); err == nil { return common.ToBool(result, false) } else { fmt.Println(err) } } return false }