Skip to content

Commit 7650f22

Browse files
author
周倩
committed
Release 0.0.2
1 parent 8fdc43d commit 7650f22

File tree

4 files changed

+211
-27
lines changed

4 files changed

+211
-27
lines changed

beans/request_params.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ type RequestParam struct {
1212
// HTTP 请求参数
1313
Properties map[string]interface{}
1414

15+
// 自定义分流主体
16+
CustomIDs map[string]interface{}
17+
1518
// 网络请求超时时间,单位 ms,默认 3s
1619
TimeoutMilliseconds time.Duration
1720

experiment_helper.go

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ package sensorsabtest
33
import (
44
"errors"
55
"fmt"
6-
"github.com/sensorsdata/abtesting-sdk-go/beans"
7-
"github.com/sensorsdata/abtesting-sdk-go/utils"
8-
"github.com/sensorsdata/abtesting-sdk-go/utils/lru"
96
"reflect"
107
"strconv"
118
"sync"
129
"time"
10+
11+
"github.com/sensorsdata/abtesting-sdk-go/beans"
12+
"github.com/sensorsdata/abtesting-sdk-go/utils"
13+
"github.com/sensorsdata/abtesting-sdk-go/utils/lru"
1314
)
1415

1516
// 用户的试验缓存
@@ -40,7 +41,7 @@ func loadExperimentFromNetwork(sensors *SensorsABTest, distinctId string, isLogi
4041
experiment := filterExperiment(requestParam, experiments)
4142
if experiment.AbtestExperimentId != "" {
4243
if isTrack {
43-
trackABTestEvent(distinctId, isLoginId, experiment, sensors, nil)
44+
trackABTestEvent(distinctId, isLoginId, experiment, sensors, nil, requestParam.CustomIDs)
4445
}
4546
// 回调试验变量给客户
4647
experiment.DistinctId = distinctId
@@ -57,7 +58,7 @@ func loadExperimentFromNetwork(sensors *SensorsABTest, distinctId string, isLogi
5758

5859
func loadExperimentFromCache(sensors *SensorsABTest, distinctId string, isLoginId bool, requestParam beans.RequestParam, isTrack bool) (error, beans.Experiment) {
5960
var experiment beans.Experiment
60-
experiments, ok := loadExperimentCache(distinctId)
61+
experiments, ok := loadExperimentCache(distinctId, isLoginId, requestParam.CustomIDs)
6162
if experiments != nil || ok {
6263
experiment = filterExperiment(requestParam, experiments.([]beans.Experiment))
6364
}
@@ -72,13 +73,13 @@ func loadExperimentFromCache(sensors *SensorsABTest, distinctId string, isLoginI
7273
}
7374

7475
// 缓存试验
75-
saveExperiment2Cache(distinctId, experiments, sensors.config.ExperimentCacheTime)
76+
saveExperiment2Cache(distinctId, isLoginId, requestParam.CustomIDs, experiments, sensors.config.ExperimentCacheTime)
7677
// 筛选试验
7778
experiment = filterExperiment(requestParam, experiments)
7879
}
7980

8081
if isTrack && experiment.AbtestExperimentId != "" {
81-
trackABTestEvent(distinctId, isLoginId, experiment, sensors, nil)
82+
trackABTestEvent(distinctId, isLoginId, experiment, sensors, nil, requestParam.CustomIDs)
8283
}
8384
experiment.DistinctId = distinctId
8485
experiment.IsLoginId = isLoginId
@@ -115,7 +116,7 @@ func filterExperiment(requestParam beans.RequestParam, experiments []beans.Exper
115116
}
116117
}
117118

118-
func trackABTestEvent(distinctId string, isLoginId bool, experiment beans.Experiment, sensors *SensorsABTest, properties map[string]interface{}) {
119+
func trackABTestEvent(distinctId string, isLoginId bool, experiment beans.Experiment, sensors *SensorsABTest, properties map[string]interface{}, customIDs map[string]interface{}) {
119120
if sensors.config.SensorsAnalytics.C == nil {
120121
return
121122
}
@@ -126,12 +127,12 @@ func trackABTestEvent(distinctId string, isLoginId bool, experiment beans.Experi
126127
}
127128

128129
// 如果在缓存中,则不触发 $ABTestTrigger 事件
129-
_, ok := loadEventFromCache(getId(distinctId, experiment))
130+
_, ok := loadEventFromCache(distinctId, customIDs, experiment)
130131
if ok {
131132
return
132133
}
133134

134-
saveEvent2Cache(distinctId, experiment, sensors)
135+
saveEvent2Cache(distinctId, customIDs, experiment, sensors)
135136
if properties == nil {
136137
properties = map[string]interface{}{
137138
"$abtest_experiment_id": experiment.AbtestExperimentId,
@@ -168,41 +169,44 @@ func initCache(config beans.ABTestConfig) {
168169
}
169170

170171
// 从缓存读取 $ABTestTrigger
171-
func loadEventFromCache(idExperiment string) (interface{}, bool) {
172+
func loadEventFromCache(distinctId string, customIDs map[string]interface{}, experiment beans.Experiment) (interface{}, bool) {
172173
eventsLock.Lock()
173174
defer eventsLock.Unlock()
174-
return eventsCache.Get(idExperiment)
175+
idEvent := getEventKey(distinctId, customIDs, experiment)
176+
return eventsCache.Get(idEvent)
175177
}
176178

177179
// 保存 $ABTestTrigger 到缓存中
178-
func saveEvent2Cache(distinctId string, experiment beans.Experiment, sensors *SensorsABTest) {
180+
func saveEvent2Cache(distinctId string, customIDs map[string]interface{}, experiment beans.Experiment, sensors *SensorsABTest) {
179181
// 缓存 $ABTestTrigger 事件
180182
if sensors.config.EnableEventCache {
181183
eventsLock.Lock()
182184
defer eventsLock.Unlock()
183-
idExperiment := getId(distinctId, experiment)
184-
eventsCache.Add(idExperiment, experiment)
185+
idEvent := getEventKey(distinctId, customIDs, experiment)
186+
eventsCache.Add(idEvent, experiment)
185187
// 进行清理缓存
186-
removeCache(idExperiment, func(id string) {
188+
removeCache(idEvent, func(id string) {
187189
eventsCache.Remove(id)
188190
}, sensors.config.EventCacheTime)
189191
}
190192
}
191193

192194
// 从缓存读取试验
193-
func loadExperimentCache(distinctId string) (interface{}, bool) {
195+
func loadExperimentCache(distinctId string, isLoginId bool, customIds map[string]interface{}) (interface{}, bool) {
194196
experimentLock.Lock()
195197
defer experimentLock.Unlock()
196-
return experimentCache.Get(distinctId)
198+
idKey := getExperimentKey(distinctId, customIds, isLoginId)
199+
return experimentCache.Get(idKey)
197200
}
198201

199202
// 保存试验到缓存
200-
func saveExperiment2Cache(distinctId string, experiment []beans.Experiment, timeout time.Duration) {
203+
func saveExperiment2Cache(distinctId string, isLoginId bool, customIds map[string]interface{}, experiments []beans.Experiment, timeout time.Duration) {
201204
experimentLock.Lock()
202205
defer experimentLock.Unlock()
203-
experimentCache.Add(distinctId, experiment)
206+
idKey := getExperimentKey(distinctId, customIds, isLoginId)
207+
experimentCache.Add(idKey, experiments)
204208
// 进行清理缓存
205-
removeCache(distinctId, func(id string) {
209+
removeCache(idKey, func(id string) {
206210
experimentCache.Remove(id)
207211
}, timeout)
208212
}
@@ -238,7 +242,13 @@ func buildRequestParam(distinctId string, isLoginId bool, requestParam beans.Req
238242

239243
params["abtest_lib_version"] = SDK_VERSION
240244
params["platform"] = LIB_NAME
241-
params["properties"] = requestParam.Properties
245+
if requestParam.Properties != nil && len(requestParam.Properties) > 0 {
246+
params["custom_properties"] = requestParam.Properties
247+
}
248+
if requestParam.CustomIDs != nil && len(requestParam.CustomIDs) > 0 {
249+
params["custom_ids"] = requestParam.CustomIDs
250+
}
251+
242252
return params
243253
}
244254

@@ -259,6 +269,10 @@ func removeCache(idExperiment string, removeCache func(id string), timeout time.
259269
}
260270

261271
// 拼接缓存唯一标识
262-
func getId(distinctId string, experiment beans.Experiment) string {
263-
return distinctId + "$" + experiment.AbtestExperimentId
272+
func getEventKey(distinctId string, customIds map[string]interface{}, experiment beans.Experiment) string {
273+
return distinctId + "$" + utils.MapToJson(customIds) + "$" + experiment.AbtestExperimentId
274+
}
275+
276+
func getExperimentKey(distinctId string, customIds map[string]interface{}, isLoginId bool) string {
277+
return distinctId + "$" + utils.MapToJson(customIds) + "$" + strconv.FormatBool(isLoginId)
264278
}

sensors_abtesting.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ package sensorsabtest
33
import (
44
"errors"
55
"github.com/sensorsdata/abtesting-sdk-go/beans"
6+
"github.com/sensorsdata/abtesting-sdk-go/utils"
67
"github.com/sensorsdata/sa-sdk-go"
78
)
89

910
const (
10-
SDK_VERSION = "0.0.1"
11+
SDK_VERSION = "0.0.2"
1112
LIB_NAME = "Golang"
1213
)
1314

@@ -79,7 +80,15 @@ func (sensors *SensorsABTest) TrackABTestTrigger(experiment beans.Experiment, pr
7980
if err != nil {
8081
return err
8182
}
82-
trackABTestEvent(experiment.DistinctId, experiment.IsLoginId, experiment, sensors, property)
83+
return sensors.TrackABTestTriggerWithCustomId(experiment, nil, property)
84+
}
85+
86+
func (sensors *SensorsABTest) TrackABTestTriggerWithCustomId(experiment beans.Experiment, customId map[string]interface{}, property map[string]interface{}) error {
87+
err := checkId(experiment.DistinctId)
88+
if err != nil {
89+
return err
90+
}
91+
trackABTestEvent(experiment.DistinctId, experiment.IsLoginId, experiment, sensors, property, customId)
8392
return nil
8493
}
8594

@@ -93,7 +102,17 @@ func checkRequestParams(param beans.RequestParam) error {
93102
return errors.New("RequestParam.DefaultValue must not be nil")
94103
}
95104

96-
return nil
105+
var err error
106+
// 检查自定义属性
107+
if param.Properties != nil && len(param.Properties) > 0 {
108+
err = utils.CheckProperty(param.Properties)
109+
}
110+
111+
// 检查自定义主体
112+
if param.CustomIDs != nil && len(param.CustomIDs) > 0 {
113+
err = utils.CheckCustomIds(param.CustomIDs)
114+
}
115+
return err
97116
}
98117

99118
func checkId(id string) error {

utils/data_utls.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package utils
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"regexp"
7+
"strings"
8+
"time"
9+
)
10+
11+
const (
12+
KEY_MAX = 100
13+
VALUE_MAX = 8192
14+
15+
NAME_PATTERN_BAD = "^(^distinct_id$|^original_id$|^time$|^properties$|^id$|^first_id$|^second_id$|^users$|^events$|^event$|^user_id$|^date$|^datetime$)$"
16+
NAME_PATTERN_OK = "^[a-zA-Z_$][a-zA-Z\\d_$]{0,99}$"
17+
)
18+
19+
var patternBad, patternOk *regexp.Regexp
20+
21+
func init() {
22+
patternBad, _ = regexp.Compile(NAME_PATTERN_BAD)
23+
patternOk, _ = regexp.Compile(NAME_PATTERN_OK)
24+
}
25+
26+
func CheckProperty(properties map[string]interface{}) error {
27+
//check properties
28+
if properties != nil {
29+
for k, v := range properties {
30+
//check key
31+
err := isKeyValid(k, v)
32+
if err != nil {
33+
return err
34+
}
35+
//check value
36+
err = isValueValid(properties, k, v)
37+
if err != nil {
38+
return err
39+
}
40+
str, ok := v.([]string)
41+
if ok {
42+
data, _ := json.Marshal(str)
43+
properties[k] = string(data)
44+
}
45+
}
46+
}
47+
return nil
48+
}
49+
50+
func CheckCustomIds(customIds map[string]interface{}) error {
51+
//check properties
52+
if customIds != nil {
53+
for k, v := range customIds {
54+
//check key
55+
if strings.HasPrefix(k, "$") {
56+
return errors.New("'$' 开头的不合法的 ID, key = " + k)
57+
}
58+
err := isKeyValid(k, v)
59+
if err != nil {
60+
return err
61+
}
62+
//check value
63+
str, ok := v.(string)
64+
if ok {
65+
if str == "" {
66+
return errors.New("ID 属性值为空的不合法属性,key = " + k)
67+
} else if len(str) > 1024 {
68+
return errors.New("ID 属性值长度超过 1024 的不合法属性,key = " + k)
69+
}
70+
}
71+
err = isValueValid(customIds, k, v)
72+
if err != nil {
73+
return err
74+
}
75+
}
76+
}
77+
return nil
78+
}
79+
80+
// 检查是否符合命名规范,符合变量命名和不是预置关键字
81+
func checkPattern(name []byte) bool {
82+
return !patternBad.Match(name) && patternOk.Match(name)
83+
}
84+
85+
// 检查 key 是否合法
86+
func isKeyValid(key string, value interface{}) error {
87+
if len(key) > KEY_MAX {
88+
return errors.New("the max length of property key is 100," + "key = " + key)
89+
}
90+
91+
if len(key) == 0 {
92+
return errors.New("The key is empty or null," + "key = " + key + ", value = " + value.(string))
93+
}
94+
isMatch := checkPattern([]byte(key))
95+
if !isMatch {
96+
return errors.New("property key must be a valid variable name," + "key = " + key)
97+
}
98+
return nil
99+
}
100+
101+
// 检查 value 是否合法
102+
func isValueValid(properties map[string]interface{}, key string, value interface{}) error {
103+
switch value.(type) {
104+
case int:
105+
case bool:
106+
case float64:
107+
case string:
108+
if len(value.(string)) > VALUE_MAX {
109+
return errors.New("the max length of property value is 8192," + "value = " + value.(string))
110+
}
111+
case []string: //value in properties list MUST be string
112+
case time.Time: //only support time.Time
113+
properties[key] = value.(time.Time).Format("2006-01-02 15:04:05.999")
114+
default:
115+
return errors.New("property value must be a string/int/float64/bool/time.Time/[]string," + "key = " + key)
116+
}
117+
return nil
118+
}
119+
120+
func MapToJson(param map[string]interface{}) string {
121+
dataType, _ := json.Marshal(param)
122+
dataString := string(dataType)
123+
return dataString
124+
}
125+
126+
func JsonToMap(str string) map[string]interface{} {
127+
128+
var tempMap map[string]interface{}
129+
err := json.Unmarshal([]byte(str), &tempMap)
130+
131+
if err != nil {
132+
return nil
133+
}
134+
135+
return tempMap
136+
}
137+
138+
func MapEquals(x, y map[string]int) bool {
139+
if len(x) != len(y) {
140+
return false
141+
}
142+
for k, xv := range x {
143+
if yv, ok := y[k]; !ok || yv != xv {
144+
return false
145+
}
146+
}
147+
return true
148+
}

0 commit comments

Comments
 (0)