package xingwei import ( "bytes" "context" "crypto/md5" "encoding/json" "errors" "fmt" "io" "net/http" "strconv" "sync/atomic" "time" "tyapi-server/internal/shared/external_logger" ) // 行为数据API状态码常量 const ( CodeSuccess = 200 // 操作成功 CodeSystemError = 500 // 系统内部错误 CodeMerchantError = 3001 // 商家相关报错(商家不存在、商家被禁用、商家余额不足) CodeAccountExpired = 3002 // 账户已过期 CodeIPWhitelistMissing = 3003 // 未添加ip白名单 CodeUnauthorized = 3004 // 未授权调用该接口 CodeProductIDError = 4001 // 产品id错误 CodeInterfaceDisabled = 4002 // 接口被停用 CodeQueryException = 5001 // 接口查询异常,请联系技术人员 CodeNotFound = 6000 // 未查询到结果 ) var ( ErrDatasource = errors.New("数据源异常") ErrSystem = errors.New("系统异常") ErrNotFound = errors.New("未查询到结果") // 请求ID计数器,确保唯一性 requestIDCounter int64 ) // XingweiResponse 行为数据API响应结构 type XingweiResponse struct { Msg string `json:"msg"` Code int `json:"code"` Data interface{} `json:"data"` } // XingweiErrorCode 行为数据错误码定义 type XingweiErrorCode struct { Code int Message string } // 行为数据错误码映射 var XingweiErrorCodes = map[int]XingweiErrorCode{ CodeSuccess: {Code: CodeSuccess, Message: "操作成功"}, CodeSystemError: {Code: CodeSystemError, Message: "系统内部错误"}, CodeMerchantError: {Code: CodeMerchantError, Message: "商家相关报错(商家不存在、商家被禁用、商家余额不足)"}, CodeAccountExpired: {Code: CodeAccountExpired, Message: "账户已过期"}, CodeIPWhitelistMissing: {Code: CodeIPWhitelistMissing, Message: "未添加ip白名单"}, CodeUnauthorized: {Code: CodeUnauthorized, Message: "未授权调用该接口"}, CodeProductIDError: {Code: CodeProductIDError, Message: "产品id错误"}, CodeInterfaceDisabled: {Code: CodeInterfaceDisabled, Message: "接口被停用"}, CodeQueryException: {Code: CodeQueryException, Message: "接口查询异常,请联系技术人员"}, CodeNotFound: {Code: CodeNotFound, Message: "未查询到结果"}, } // GetXingweiErrorMessage 根据错误码获取错误消息 func GetXingweiErrorMessage(code int) string { if errorCode, exists := XingweiErrorCodes[code]; exists { return errorCode.Message } return fmt.Sprintf("未知错误码: %d", code) } type XingweiConfig struct { URL string ApiID string ApiKey string } type XingweiService struct { config XingweiConfig logger *external_logger.ExternalServiceLogger } // NewXingweiService 是一个构造函数,用于初始化 XingweiService func NewXingweiService(url, apiID, apiKey string, logger *external_logger.ExternalServiceLogger) *XingweiService { return &XingweiService{ config: XingweiConfig{ URL: url, ApiID: apiID, ApiKey: apiKey, }, logger: logger, } } // generateRequestID 生成请求ID func (x *XingweiService) generateRequestID() string { timestamp := time.Now().UnixNano() // 使用原子计数器确保唯一性 counter := atomic.AddInt64(&requestIDCounter, 1) hash := md5.Sum([]byte(fmt.Sprintf("%d_%d_%s", timestamp, counter, x.config.ApiID))) return fmt.Sprintf("xingwei_%x", hash[:8]) } // createSign 创建签名:使用MD5算法将apiId、timestamp、apiKey字符串拼接生成sign // 参考Java示例:DigestUtils.md5Hex(apiId + timestamp + apiKey) func (x *XingweiService) createSign(timestamp int64) string { signStr := x.config.ApiID + strconv.FormatInt(timestamp, 10) + x.config.ApiKey hash := md5.Sum([]byte(signStr)) return fmt.Sprintf("%x", hash) } // CallAPI 调用行为数据的 API func (x *XingweiService) CallAPI(ctx context.Context, projectID string, params map[string]interface{}) (resp []byte, err error) { startTime := time.Now() requestID := x.generateRequestID() timestamp := time.Now().UnixMilli() // 从ctx中获取transactionId var transactionID string if ctxTransactionID, ok := ctx.Value("transaction_id").(string); ok { transactionID = ctxTransactionID } // 记录请求日志 if x.logger != nil { x.logger.LogRequest(requestID, transactionID, "xingwei_api", x.config.URL, params) } // 将请求参数转换为JSON jsonData, marshalErr := json.Marshal(params) if marshalErr != nil { err = errors.Join(ErrSystem, marshalErr) if x.logger != nil { x.logger.LogError(requestID, transactionID, "xingwei_api", err, params) } return nil, err } // 创建HTTP POST请求 req, newRequestErr := http.NewRequestWithContext(ctx, "POST", x.config.URL, bytes.NewBuffer(jsonData)) if newRequestErr != nil { err = errors.Join(ErrSystem, newRequestErr) if x.logger != nil { x.logger.LogError(requestID, transactionID, "xingwei_api", err, params) } return nil, err } // 设置请求头 req.Header.Set("Content-Type", "application/json") req.Header.Set("timestamp", strconv.FormatInt(timestamp, 10)) req.Header.Set("sign", x.createSign(timestamp)) req.Header.Set("API-ID", x.config.ApiID) req.Header.Set("project_id", projectID) // 创建HTTP客户端 client := &http.Client{ Timeout: 20 * time.Second, } // 发送请求 httpResp, clientDoErr := client.Do(req) if clientDoErr != nil { err = errors.Join(ErrSystem, clientDoErr) if x.logger != nil { x.logger.LogError(requestID, transactionID, "xingwei_api", err, params) } return nil, err } defer func(Body io.ReadCloser) { closeErr := Body.Close() if closeErr != nil { // 记录关闭错误 if x.logger != nil { x.logger.LogError(requestID, transactionID, "xingwei_api", errors.Join(ErrSystem, fmt.Errorf("关闭响应体失败: %w", closeErr)), params) } } }(httpResp.Body) // 计算请求耗时 duration := time.Since(startTime) // 读取响应体 bodyBytes, ReadErr := io.ReadAll(httpResp.Body) if ReadErr != nil { err = errors.Join(ErrSystem, ReadErr) if x.logger != nil { x.logger.LogError(requestID, transactionID, "xingwei_api", err, params) } return nil, err } // 记录响应日志 if x.logger != nil { x.logger.LogResponse(requestID, transactionID, "xingwei_api", httpResp.StatusCode, bodyBytes, duration) } // 检查HTTP状态码 if httpResp.StatusCode != http.StatusOK { err = errors.Join(ErrSystem, fmt.Errorf("行为数据请求失败,状态码: %d", httpResp.StatusCode)) if x.logger != nil { x.logger.LogError(requestID, transactionID, "xingwei_api", err, params) } return nil, err } // 解析响应结构 var xingweiResp XingweiResponse if err := json.Unmarshal(bodyBytes, &xingweiResp); err != nil { err = errors.Join(ErrSystem, fmt.Errorf("响应解析失败: %w", err)) if x.logger != nil { x.logger.LogError(requestID, transactionID, "xingwei_api", err, params) } return nil, err } // 检查业务状态码 switch xingweiResp.Code { case CodeSuccess: // 成功响应,返回data字段 if xingweiResp.Data == nil { return []byte("{}"), nil } // 将data转换为JSON字节 dataBytes, err := json.Marshal(xingweiResp.Data) if err != nil { err = errors.Join(ErrSystem, fmt.Errorf("data字段序列化失败: %w", err)) if x.logger != nil { x.logger.LogError(requestID, transactionID, "xingwei_api", err, params) } return nil, err } return dataBytes, nil case CodeNotFound: // 未查询到结果,返回查空错误 if x.logger != nil { x.logger.LogError(requestID, transactionID, "xingwei_api", errors.Join(ErrNotFound, fmt.Errorf("未查询到结果")), params) } return nil, errors.Join(ErrNotFound, fmt.Errorf("未查询到结果")) case CodeSystemError: // 系统内部错误 errorMsg := GetXingweiErrorMessage(xingweiResp.Code) systemErr := fmt.Errorf("行为数据系统错误[%d]: %s", xingweiResp.Code, errorMsg) if x.logger != nil { x.logger.LogError(requestID, transactionID, "xingwei_api", errors.Join(ErrSystem, systemErr), params) } return nil, errors.Join(ErrSystem, systemErr) default: // 其他业务错误 errorMsg := GetXingweiErrorMessage(xingweiResp.Code) businessErr := fmt.Errorf("行为数据业务错误[%d]: %s", xingweiResp.Code, errorMsg) if x.logger != nil { x.logger.LogError(requestID, transactionID, "xingwei_api", errors.Join(ErrDatasource, businessErr), params) } return nil, errors.Join(ErrDatasource, businessErr) } } // GetConfig 获取配置信息 func (x *XingweiService) GetConfig() XingweiConfig { return x.config }