pet_home_server/src/server/thinkingdata/thinkingdata.go
2026-01-21 17:56:16 +08:00

315 lines
10 KiB
Go

package thinkingdata
import (
"encoding/json"
"errors"
"sync"
"time"
)
const (
Track = "track"
TrackUpdate = "track_update"
TrackOverwrite = "track_overwrite"
UserSet = "user_set"
UserUnset = "user_unset"
UserSetOnce = "user_setOnce"
UserAdd = "user_add"
UserAppend = "user_append"
UserUniqAppend = "user_uniq_append"
UserDel = "user_del"
SdkVersion = "2.0.3"
LibName = "Golang"
)
type Data struct {
IsComplex bool `json:"-"` // properties are nested or not
AccountId string `json:"#account_id,omitempty"`
DistinctId string `json:"#distinct_id,omitempty"`
Type string `json:"#type"`
Time string `json:"#time"`
Timestamp int64 `json:"#timestamp,omitempty"`
EventName string `json:"#event_name,omitempty"`
EventId string `json:"#event_id,omitempty"`
FirstCheckId string `json:"#first_check_id,omitempty"`
Ip string `json:"#ip,omitempty"`
UUID string `json:"#uuid,omitempty"`
AppId string `json:"#app_id,omitempty"`
Properties map[string]interface{} `json:"properties"`
PropertiesSummary string `json:"properties_summary,omitempty"`
}
// TDConsumer define operation interface
type TDConsumer interface {
Add(d Data) error
Flush() error
Close() error
IsStringent() bool // check data or not.
}
type TDAnalytics struct {
consumer TDConsumer
superProperties map[string]interface{}
mutex *sync.RWMutex
dynamicSuperProperties func() map[string]interface{}
}
// New init SDK
func New(c TDConsumer) TDAnalytics {
tdLogInfo("init SDK success")
return TDAnalytics{
consumer: c,
superProperties: make(map[string]interface{}),
mutex: new(sync.RWMutex),
}
}
// GetSuperProperties get common properties
func (ta *TDAnalytics) GetSuperProperties() map[string]interface{} {
result := make(map[string]interface{})
ta.mutex.Lock()
mergeProperties(result, ta.superProperties)
ta.mutex.Unlock()
return result
}
// SetSuperProperties set common properties
func (ta *TDAnalytics) SetSuperProperties(superProperties map[string]interface{}) {
ta.mutex.Lock()
mergeProperties(ta.superProperties, superProperties)
ta.mutex.Unlock()
}
// ClearSuperProperties clear common properties
func (ta *TDAnalytics) ClearSuperProperties() {
ta.mutex.Lock()
ta.superProperties = make(map[string]interface{})
ta.mutex.Unlock()
}
// SetDynamicSuperProperties set common properties dynamically.
// not recommend to add the operation which with a lot of computation
func (ta *TDAnalytics) SetDynamicSuperProperties(action func() map[string]interface{}) {
ta.mutex.Lock()
ta.dynamicSuperProperties = action
ta.mutex.Unlock()
}
// GetDynamicSuperProperties dynamic common properties
func (ta *TDAnalytics) GetDynamicSuperProperties() map[string]interface{} {
result := make(map[string]interface{})
ta.mutex.RLock()
if ta.dynamicSuperProperties != nil {
mergeProperties(result, ta.dynamicSuperProperties())
}
ta.mutex.RUnlock()
return result
}
// Track report ordinary event
func (ta *TDAnalytics) Track(accountId, distinctId, eventName string, properties map[string]interface{}) error {
return ta.track(accountId, distinctId, Track, eventName, "", properties)
}
// TrackFirst report first event
func (ta *TDAnalytics) TrackFirst(accountId, distinctId, eventName, firstCheckId string, properties map[string]interface{}) error {
if len(firstCheckId) == 0 {
msg := "the 'firstCheckId' must be provided"
tdLogInfo(msg)
return errors.New(msg)
}
p := make(map[string]interface{})
mergeProperties(p, properties)
p["#first_check_id"] = firstCheckId
return ta.track(accountId, distinctId, Track, eventName, "", p)
}
// TrackUpdate report updatable event
func (ta *TDAnalytics) TrackUpdate(accountId, distinctId, eventName, eventId string, properties map[string]interface{}) error {
return ta.track(accountId, distinctId, TrackUpdate, eventName, eventId, properties)
}
// TrackOverwrite report overridable event
func (ta *TDAnalytics) TrackOverwrite(accountId, distinctId, eventName, eventId string, properties map[string]interface{}) error {
return ta.track(accountId, distinctId, TrackOverwrite, eventName, eventId, properties)
}
func (ta *TDAnalytics) track(accountId, distinctId, dataType, eventName, eventId string, properties map[string]interface{}) error {
defer func() {
if r := recover(); r != nil {
tdLogError("%+v\ndata: %+v", r, properties)
}
}()
if len(eventName) == 0 {
msg := "the event name must be provided"
tdLogError(msg)
return errors.New(msg)
}
// eventId not be null unless eventType is equal Track.
if len(eventId) == 0 && dataType != Track {
msg := "the event id must be provided"
tdLogError(msg)
return errors.New(msg)
}
p := ta.GetSuperProperties()
dynamicSuperProperties := ta.GetDynamicSuperProperties()
mergeProperties(p, dynamicSuperProperties)
// preset properties has the highest priority
p["#lib"] = LibName
p["#lib_version"] = SdkVersion
// custom properties
mergeProperties(p, properties)
return ta.add(accountId, distinctId, dataType, eventName, eventId, p)
}
// UserSet set user properties. would overwrite existing names.
func (ta *TDAnalytics) UserSet(accountId string, distinctId string, properties map[string]interface{}) error {
return ta.user(accountId, distinctId, UserSet, properties)
}
// UserUnset clear the user properties of users.
func (ta *TDAnalytics) UserUnset(accountId string, distinctId string, s []string) error {
if len(s) == 0 {
msg := "invalid params for UserUnset: keys is nil"
tdLogInfo(msg)
return errors.New(msg)
}
prop := make(map[string]interface{})
for _, v := range s {
prop[v] = 0
}
return ta.user(accountId, distinctId, UserUnset, prop)
}
func (ta *TDAnalytics) UserUnsetWithProperties(accountId string, distinctId string, properties map[string]interface{}) error {
if len(properties) == 0 {
msg := "invalid params for UserUnset: properties is nil"
tdLogInfo(msg)
return errors.New(msg)
}
return ta.user(accountId, distinctId, UserUnset, properties)
}
// UserSetOnce set user properties, If such property had been set before, this message would be neglected.
func (ta *TDAnalytics) UserSetOnce(accountId string, distinctId string, properties map[string]interface{}) error {
return ta.user(accountId, distinctId, UserSetOnce, properties)
}
// UserAdd to accumulate operations against the property.
func (ta *TDAnalytics) UserAdd(accountId string, distinctId string, properties map[string]interface{}) error {
return ta.user(accountId, distinctId, UserAdd, properties)
}
// UserAppend to add user properties of array type.
func (ta *TDAnalytics) UserAppend(accountId string, distinctId string, properties map[string]interface{}) error {
return ta.user(accountId, distinctId, UserAppend, properties)
}
// UserUniqAppend append user properties to array type by unique.
func (ta *TDAnalytics) UserUniqAppend(accountId string, distinctId string, properties map[string]interface{}) error {
return ta.user(accountId, distinctId, UserUniqAppend, properties)
}
// UserDelete delete a user, This operation cannot be undone.
func (ta *TDAnalytics) UserDelete(accountId string, distinctId string) error {
return ta.user(accountId, distinctId, UserDel, nil)
}
// UserDeleteWithProperties delete a user, This operation cannot be undone.
func (ta *TDAnalytics) UserDeleteWithProperties(accountId string, distinctId string, properties map[string]interface{}) error {
return ta.user(accountId, distinctId, UserDel, properties)
}
func (ta *TDAnalytics) user(accountId, distinctId, dataType string, properties map[string]interface{}) error {
defer func() {
if r := recover(); r != nil {
tdLogError("%+v\ndata: %+v", r, properties)
}
}()
if properties == nil && dataType != UserDel {
msg := "invalid params for " + dataType + ": properties is nil"
tdLogError(msg)
return errors.New(msg)
}
p := make(map[string]interface{})
mergeProperties(p, properties)
return ta.add(accountId, distinctId, dataType, "", "", p)
}
// Flush report data immediately.
func (ta *TDAnalytics) Flush() error {
return ta.consumer.Flush()
}
// Close and exit sdk
func (ta *TDAnalytics) Close() error {
err := ta.consumer.Close()
tdLogInfo("SDK close")
return err
}
func (ta *TDAnalytics) add(accountId, distinctId, dataType, eventName, eventId string, properties map[string]interface{}) error {
if len(accountId) == 0 && len(distinctId) == 0 {
msg := "invalid parameters: account_id and distinct_id cannot be empty at the same time"
tdLogError(msg)
return errors.New(msg)
}
// get "#ip" value in properties, empty string will be return when not found.
ip := extractStringProperty(properties, "#ip")
// get "#app_id" value in properties, empty string will be return when not found.
appId := extractStringProperty(properties, "#app_id")
// get "#time" value in properties, empty string will be return when not found.
eventTime := extractTime(properties)
firstCheckId := extractStringProperty(properties, "#first_check_id")
// get "#uuid" value in properties, empty string will be return when not found.
uuid := extractStringProperty(properties, "#uuid")
if len(uuid) == 0 {
uuid = generateUUID()
}
properties_summary, err := json.Marshal(properties)
if err != nil {
properties_summary = []byte{}
}
data := Data{
AccountId: accountId,
DistinctId: distinctId,
Type: dataType,
Time: eventTime,
Timestamp: time.Now().Unix(),
EventName: eventName,
EventId: eventId,
FirstCheckId: firstCheckId,
Ip: ip,
UUID: uuid,
Properties: properties,
PropertiesSummary: string(properties_summary),
}
if len(appId) > 0 {
data.AppId = appId
}
err = formatProperties(&data, ta)
if err != nil {
return err
}
return ta.consumer.Add(data)
}
// Deprecated: please use TDConsumer
type Consumer interface {
TDConsumer
}