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 | |||
|  | } |