package dataReport import ( "bytes" "context" "encoding/json" "fmt" "io/ioutil" "math" "math/rand" "net/http" "net/url" "strconv" "strings" "time" "videoweb/pub/common/godef" "videoweb/pub/common/log" "videoweb/pub/common/useragent" "videoweb/pub/pubdef" "github.com/google/uuid" "github.com/moul/http2curl" ) var config = Config{} func Init(c Config) { config = c // 根据运行模式设置SDK配置API地址 switch c.RunMod { case "prod", "release": sdkConfigAPIURL = sdkConfigAPIProd default: sdkConfigAPIURL = sdkConfigAPIDev } } // GenerateGlobalEventID 生成全局唯一的 event_id func GenerateGlobalEventID() string { // 生成一个新的 UUID return strings.ReplaceAll(uuid.New().String(), "-", "") } func AsyncReport(eventData interface{}, retryCount int) { // TODO 后续可改成kafka异步处理 godef.Go(func() { // 自动重试 baseDelay := 1 * time.Second for i := 0; i < retryCount; i++ { err := report(eventData) if err != nil { log.Error("Event.report failed", log.Any("retryCount", retryCount), log.Any("eventData", eventData), log.E(err)) // 指数退避 + 随机抖动 sleepTime := time.Duration(math.Pow(2, float64(i)))*baseDelay + time.Duration(rand.Intn(1000))*time.Millisecond time.Sleep(sleepTime) continue } break } }) } // 上报事件 func report(eventData interface{}) error { // 将结构体转换为表单数据 formData, err := structToFormData(eventData) if err != nil { return err } apiUrl := config.ApiUrl + "/api/eventTracking/report.json" req, err := http.NewRequest("POST", apiUrl, bytes.NewBufferString(formData)) if err != nil { log.Error("Event report NewRequest fail", log.E(err)) return fmt.Errorf("failed to create request: %v", err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") client := &http.Client{ Timeout: 5 * time.Second, // 设置超时时间为 5 秒 } command, _ := http2curl.GetCurlCommand(req) fmt.Println(command) resp, err := client.Do(req) if err != nil { log.Error("Event report fail", log.E(err)) return fmt.Errorf("failed to send request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Error("Event report fail", log.Any("http.code", resp.StatusCode)) return fmt.Errorf("failed to report event, http.code: %d", resp.StatusCode) } // 读取返回的 body body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Error("Event report fail", log.E(err)) return fmt.Errorf("failed to read response body: %v", err) } res := &Response{} err = json.Unmarshal(body, res) if err != nil { log.Error("Event report json.Unmarshal(body, res) fail", log.Any("body", string(body)), log.E(err)) return err } if res.Code != 0 { log.Error("Event report fail", log.Any("code", res.Code), log.Any("msg", res.Msg), log.Any("data", res.Data)) return fmt.Errorf("failed to report event, res.code: %d", res.Code) } // 输出响应状态和返回的 body log.Info("Event report Response", log.Any("eventData", eventData), log.Any("body", string(body))) return nil } func structToFormData(data interface{}) (string, error) { // 序列化结构体为 JSON 字符串 jsonData, err := json.Marshal(data) if err != nil { return "", err } // 反序列化为 map var dataMap map[string]interface{} err = json.Unmarshal(jsonData, &dataMap) if err != nil { return "", err } var formData []string // 遍历 map,将其转换为 form 数据 for key, value := range dataMap { if value == nil { continue // 如果值为 nil,跳过 } // 将值转换为字符串 valueStr := fmt.Sprintf("%v", value) // 使用 URL 编码 formData = append(formData, fmt.Sprintf( "%s=%s", url.QueryEscape(key), url.QueryEscape(valueStr), )) } return strings.Join(formData, "&"), nil } func NewCommonField(eventId, event string, districtCode string, ua useragent.UA, uid, ip string) CommonField { return NewCommonFieldAt(eventId, event, districtCode, ua, uid, ip, time.Now().Unix()) } func NewCommonFieldAt(eventId, event string, districtCode string, ua useragent.UA, uid, ip string, clientTS int64) CommonField { channel := strings.TrimSpace(districtCode) if channel == "" { channel = "system" } if eventId == "" { eventId = GenerateGlobalEventID() } // 五项公共字段和 app_version 使用当前请求中的 SDK 原值,旧客户端缺失时留空。 // 不能用业务 DevID、用户表会话或推算的平台补齐,否则仍会与客户端埋点不一致。 sdk := ua.SDKCommon if missing := sdk.MissingFields(); len(missing) > 0 { log.Warn("【SDK公共参数缺失】NewCommonFieldAt", log.Any("接口", ua.RequestPath), log.Any("事件", event), log.Any("eventId", eventId), log.Any("userId", uid), log.Any("客户端版本", ua.Ver), log.Any("已获取SDK参数", sdk != nil), log.Any("缺失字段", missing)) } if sdk == nil { sdk = &useragent.SDKCommonFields{} } return CommonField{ UID: uid, EventID: eventId, Event: event, Channel: channel, AppID: pubdef.TrackerAppId(), AppVersion: sdk.AppVersion, ClientTS: fmt.Sprintf("%v", clientTS), Device: sdk.Device, DeviceID: sdk.DeviceID, DeviceFingerprint: sdk.DeviceFingerprint, FPVersion: sdk.FPVersion, UserAgent: strings.TrimSpace(ua.UserAgent), DeviceBrand: strings.TrimSpace(ua.DeviceBrand), DeviceModel: strings.TrimSpace(ua.DeviceModel), SystemName: strings.TrimSpace(ua.SystemName), SystemVersion: strings.TrimSpace(ua.SystemVersion), SID: sdk.SID, IP: ip, } } func JsonNumber(v int64) json.Number { return json.Number(strconv.FormatInt(v, 10)) } func JsonRawMessage(v interface{}) json.RawMessage { b, _ := json.Marshal(v) return b } // 批量上报事件 func BatchReport(ctx context.Context, data interface{}) error { apiUrl := config.ApiUrl + "/api/eventTracking/batchReport.json" reqBody, err := json.Marshal(data) if err != nil { log.Error("Event report json.Marshal fail", log.E(err)) return err } req, err := http.NewRequest("POST", apiUrl, bytes.NewReader(reqBody)) if err != nil { log.Error("Event report NewRequest fail", log.E(err)) return fmt.Errorf("failed to create request: %v", err) } req.Header.Set("Content-Type", "application/json") curl, _ := http2curl.GetCurlCommand(req) client := &http.Client{ Timeout: 5 * time.Second, // 设置超时时间为 5 秒 } resp, err := client.Do(req) if err != nil { log.Error("Event report fail", log.Any("curl", curl), log.E(err)) return fmt.Errorf("failed to send request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Error("Event report fail", log.Any("curl", curl), log.Any("http.code", resp.StatusCode)) return fmt.Errorf("failed to report event, status code: %d", resp.StatusCode) } // 读取返回的 body body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Error("Event report fail", log.Any("curl", curl), log.E(err)) return fmt.Errorf("failed to read response body: %v", err) } // 输出响应状态和返回的 body log.Info("Event report Response", log.Any("body", string(body))) return nil }