280 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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
 | ||
| }
 |