package zhicha import ( "bytes" "context" "crypto/aes" "crypto/cipher" "crypto/md5" "encoding/base64" "encoding/hex" "encoding/json" "errors" "fmt" "io" "net/http" "strconv" "time" "tyapi-server/internal/shared/external_logger" ) var ( ErrDatasource = errors.New("数据源异常") ErrSystem = errors.New("系统异常") ) type ZhichaResp struct { Code string `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` Success bool `json:"success"` } type ZhichaConfig struct { URL string AppID string AppSecret string EncryptKey string } type ZhichaService struct { config ZhichaConfig logger *external_logger.ExternalServiceLogger } // NewZhichaService 是一个构造函数,用于初始化 ZhichaService func NewZhichaService(url, appID, appSecret, encryptKey string, logger *external_logger.ExternalServiceLogger) *ZhichaService { return &ZhichaService{ config: ZhichaConfig{ URL: url, AppID: appID, AppSecret: appSecret, EncryptKey: encryptKey, }, logger: logger, } } // generateRequestID 生成请求ID func (z *ZhichaService) generateRequestID() string { timestamp := time.Now().UnixNano() hash := md5.Sum([]byte(fmt.Sprintf("%d_%s", timestamp, z.config.AppID))) return fmt.Sprintf("zhicha_%x", hash[:8]) } // generateSign 生成签名 func (z *ZhichaService) generateSign(timestamp int64) string { // 第一步:对app_secret进行MD5加密 encryptedSecret := fmt.Sprintf("%x", md5.Sum([]byte(z.config.AppSecret))) // 第二步:将加密后的密钥和时间戳拼接,再次MD5加密 signStr := encryptedSecret + strconv.FormatInt(timestamp, 10) sign := fmt.Sprintf("%x", md5.Sum([]byte(signStr))) return sign } // CallAPI 调用智查金控的 API func (z *ZhichaService) CallAPI(ctx context.Context, proID string, params map[string]interface{}) (data interface{}, err error) { startTime := time.Now() requestID := z.generateRequestID() timestamp := time.Now().Unix() // 从ctx中获取transactionId var transactionID string if ctxTransactionID, ok := ctx.Value("transaction_id").(string); ok { transactionID = ctxTransactionID } // 记录请求日志 if z.logger != nil { z.logger.LogRequest(requestID, transactionID, proID, z.config.URL, params) } jsonData, marshalErr := json.Marshal(params) if marshalErr != nil { err = errors.Join(ErrSystem, marshalErr) if z.logger != nil { z.logger.LogError(requestID, transactionID, proID, err, params) } return nil, err } // 创建HTTP POST请求 req, err := http.NewRequestWithContext(ctx, "POST", z.config.URL, bytes.NewBuffer(jsonData)) if err != nil { err = errors.Join(ErrSystem, err) if z.logger != nil { z.logger.LogError(requestID, transactionID, proID, err, params) } return nil, err } // 设置请求头 req.Header.Set("Content-Type", "application/json") req.Header.Set("appId", z.config.AppID) req.Header.Set("proId", proID) req.Header.Set("timestamp", strconv.FormatInt(timestamp, 10)) req.Header.Set("sign", z.generateSign(timestamp)) // 创建HTTP客户端 client := &http.Client{ Timeout: 20 * time.Second, } // 发送请求 response, err := client.Do(req) if err != nil { err = errors.Join(ErrSystem, err) if z.logger != nil { z.logger.LogError(requestID, transactionID, proID, err, params) } return nil, err } defer response.Body.Close() // 读取响应 respBody, err := io.ReadAll(response.Body) if err != nil { err = errors.Join(ErrSystem, err) if z.logger != nil { z.logger.LogError(requestID, transactionID, proID, err, params) } return nil, err } // 记录响应日志 if z.logger != nil { duration := time.Since(startTime) z.logger.LogResponse(requestID, transactionID, proID, response.StatusCode, respBody, duration) } // 检查HTTP状态码 if response.StatusCode != http.StatusOK { err = errors.Join(ErrDatasource, fmt.Errorf("HTTP状态码 %d", response.StatusCode)) if z.logger != nil { z.logger.LogError(requestID, transactionID, proID, err, params) } return nil, err } // 解析响应 var zhichaResp ZhichaResp if err := json.Unmarshal(respBody, &zhichaResp); err != nil { err = errors.Join(ErrSystem, fmt.Errorf("响应解析失败: %s", err.Error())) if z.logger != nil { z.logger.LogError(requestID, transactionID, proID, err, params) } return nil, err } // 检查业务状态码 if zhichaResp.Code != "200" && zhichaResp.Code != "201" { // 创建智查金控错误用于日志记录 zhichaErr := NewZhichaErrorFromCode(zhichaResp.Code) if zhichaErr.Code == "未知错误" { zhichaErr.Message = zhichaResp.Message } // 记录智查金控的详细错误信息到日志 if z.logger != nil { z.logger.LogError(requestID, transactionID, proID, zhichaErr, params) } // 对外统一返回数据源异常错误 return nil, ErrDatasource } // 返回data字段 return zhichaResp.Data, nil } // Encrypt 使用配置的加密密钥对数据进行AES-128-CBC加密 func (z *ZhichaService) Encrypt(data string) (string, error) { if z.config.EncryptKey == "" { return "", fmt.Errorf("加密密钥未配置") } // 将十六进制密钥转换为字节 binKey, err := hex.DecodeString(z.config.EncryptKey) if err != nil { return "", fmt.Errorf("密钥格式错误: %w", err) } if len(binKey) < 16 { // AES-128, 16 bytes return "", fmt.Errorf("密钥长度不足,需要至少16字节") } // 从密钥前16个字符生成IV iv := []byte(z.config.EncryptKey[:16]) // 创建AES加密器 block, err := aes.NewCipher(binKey) if err != nil { return "", fmt.Errorf("创建AES加密器失败: %w", err) } // 对数据进行PKCS7填充 paddedData := z.pkcs7Padding([]byte(data), aes.BlockSize) // 创建CBC模式加密器 mode := cipher.NewCBCEncrypter(block, iv) // 加密 ciphertext := make([]byte, len(paddedData)) mode.CryptBlocks(ciphertext, paddedData) // 返回Base64编码结果 return base64.StdEncoding.EncodeToString(ciphertext), nil } // Decrypt 使用配置的加密密钥对数据进行AES-128-CBC解密 func (z *ZhichaService) Decrypt(encryptedData string) (string, error) { if z.config.EncryptKey == "" { return "", fmt.Errorf("加密密钥未配置") } // 将十六进制密钥转换为字节 binKey, err := hex.DecodeString(z.config.EncryptKey) if err != nil { return "", fmt.Errorf("密钥格式错误: %w", err) } if len(binKey) < 16 { // AES-128, 16 bytes return "", fmt.Errorf("密钥长度不足,需要至少16字节") } // 从密钥前16个字符生成IV iv := []byte(z.config.EncryptKey[:16]) // 解码Base64数据 decodedData, err := base64.StdEncoding.DecodeString(encryptedData) if err != nil { return "", fmt.Errorf("Base64解码失败: %w", err) } // 检查数据长度是否为AES块大小的倍数 if len(decodedData) == 0 || len(decodedData)%aes.BlockSize != 0 { return "", fmt.Errorf("加密数据长度无效,必须是%d字节的倍数", aes.BlockSize) } // 创建AES解密器 block, err := aes.NewCipher(binKey) if err != nil { return "", fmt.Errorf("创建AES解密器失败: %w", err) } // 创建CBC模式解密器 mode := cipher.NewCBCDecrypter(block, iv) // 解密 plaintext := make([]byte, len(decodedData)) mode.CryptBlocks(plaintext, decodedData) // 移除PKCS7填充 unpadded, err := z.pkcs7Unpadding(plaintext) if err != nil { return "", fmt.Errorf("移除填充失败: %w", err) } return string(unpadded), nil } // pkcs7Padding 使用PKCS7填充数据 func (z *ZhichaService) pkcs7Padding(src []byte, blockSize int) []byte { padding := blockSize - len(src)%blockSize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(src, padtext...) } // pkcs7Unpadding 移除PKCS7填充 func (z *ZhichaService) pkcs7Unpadding(src []byte) ([]byte, error) { length := len(src) if length == 0 { return nil, fmt.Errorf("数据为空") } unpadding := int(src[length-1]) if unpadding > length { return nil, fmt.Errorf("填充长度无效") } return src[:length-unpadding], nil }