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