package tianyuanapi import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "encoding/hex" "encoding/json" "errors" "fmt" "io" "net/http" "time" ) // API调用相关错误类型 var ( ErrQueryEmpty = errors.New("查询为空") ErrSystem = errors.New("接口异常") ErrDecryptFail = errors.New("解密失败") ErrRequestParam = errors.New("请求参数结构不正确") ErrInvalidParam = errors.New("参数校验不正确") ErrInvalidIP = errors.New("未经授权的IP") ErrMissingAccessId = errors.New("缺少Access-Id") ErrInvalidAccessId = errors.New("未经授权的AccessId") ErrFrozenAccount = errors.New("账户已冻结") ErrArrears = errors.New("账户余额不足,无法请求") ErrProductNotFound = errors.New("产品不存在") ErrProductDisabled = errors.New("产品已停用") ErrNotSubscribed = errors.New("未订阅此产品") ErrBusiness = errors.New("业务失败") ) // 错误码映射 - 严格按照用户要求 var ErrorCodeMap = map[error]int{ ErrQueryEmpty: 1000, ErrSystem: 1001, ErrDecryptFail: 1002, ErrRequestParam: 1003, ErrInvalidParam: 1003, ErrInvalidIP: 1004, ErrMissingAccessId: 1005, ErrInvalidAccessId: 1006, ErrFrozenAccount: 1007, ErrArrears: 1007, ErrProductNotFound: 1008, ErrProductDisabled: 1008, ErrNotSubscribed: 1008, ErrBusiness: 2001, } // ApiCallOptions API调用选项 type ApiCallOptions struct { Json bool `json:"json,omitempty"` // 是否返回JSON格式 } // Client 天元API客户端 type Client struct { accessID string key string baseURL string timeout time.Duration client *http.Client } // Config 客户端配置 type Config struct { AccessID string // 访问ID Key string // AES密钥(16进制) BaseURL string // API基础URL Timeout time.Duration // 超时时间 } // Request 请求参数 type Request struct { InterfaceName string `json:"interfaceName"` // 接口名称 Params map[string]interface{} `json:"params"` // 请求参数 Timeout int `json:"timeout"` // 超时时间(毫秒) Options *ApiCallOptions `json:"options"` // 调用选项 } // ApiResponse HTTP API响应 type ApiResponse struct { Code int `json:"code"` Message string `json:"message"` TransactionID string `json:"transaction_id"` // 流水号 Data string `json:"data"` // 加密的数据 } // Response Call方法的响应 type Response struct { Code int `json:"code"` Message string `json:"message"` Success bool `json:"success"` TransactionID string `json:"transaction_id"` // 流水号 Data map[string]interface{} `json:"data"` // 解密后的数据 Timeout int64 `json:"timeout"` // 请求耗时(毫秒) Error string `json:"error,omitempty"` } // NewClient 创建新的客户端实例 func NewClient(config Config) (*Client, error) { // 参数校验 if config.AccessID == "" { return nil, fmt.Errorf("accessID不能为空") } if config.Key == "" { return nil, fmt.Errorf("key不能为空") } if config.BaseURL == "" { config.BaseURL = "http://127.0.0.1:8080" } if config.Timeout == 0 { config.Timeout = 60 * time.Second } // 验证密钥格式 if _, err := hex.DecodeString(config.Key); err != nil { return nil, fmt.Errorf("无效的密钥格式,必须是16进制字符串: %v", err) } return &Client{ accessID: config.AccessID, key: config.Key, baseURL: config.BaseURL, timeout: config.Timeout, client: &http.Client{ Timeout: config.Timeout, }, }, nil } // Call 调用API接口 func (c *Client) Call(req Request) (*Response, error) { startTime := time.Now() // 参数校验 if err := c.validateRequest(req); err != nil { return nil, fmt.Errorf("请求参数校验失败: %v", err) } // 加密参数 jsonData, err := json.Marshal(req.Params) if err != nil { return nil, fmt.Errorf("参数序列化失败: %v", err) } encryptedData, err := c.encrypt(string(jsonData)) if err != nil { return nil, fmt.Errorf("数据加密失败: %v", err) } // 构建请求体 requestBody := map[string]interface{}{ "data": encryptedData, } // 添加选项 if req.Options != nil { requestBody["options"] = req.Options } else { // 默认选项 defaultOptions := &ApiCallOptions{ Json: true, } requestBody["options"] = defaultOptions } requestBodyBytes, err := json.Marshal(requestBody) if err != nil { return nil, fmt.Errorf("请求体序列化失败: %v", err) } // 创建HTTP请求 url := fmt.Sprintf("%s/api/v1/%s", c.baseURL, req.InterfaceName) httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBodyBytes)) if err != nil { return nil, fmt.Errorf("创建HTTP请求失败: %v", err) } // 设置请求头 httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("Access-Id", c.accessID) httpReq.Header.Set("User-Agent", "TianyuanAPI-Go-SDK/1.0.0") // 发送请求 resp, err := c.client.Do(httpReq) if err != nil { endTime := time.Now() requestTime := endTime.Sub(startTime).Milliseconds() return &Response{ Success: false, Message: "请求失败", Error: err.Error(), Timeout: requestTime, }, nil } defer resp.Body.Close() // 读取响应 body, err := io.ReadAll(resp.Body) if err != nil { endTime := time.Now() requestTime := endTime.Sub(startTime).Milliseconds() return &Response{ Success: false, Message: "读取响应失败", Error: err.Error(), Timeout: requestTime, }, nil } // 解析HTTP API响应 var apiResp ApiResponse if err := json.Unmarshal(body, &apiResp); err != nil { endTime := time.Now() requestTime := endTime.Sub(startTime).Milliseconds() return &Response{ Success: false, Message: "响应解析失败", Error: err.Error(), Timeout: requestTime, }, nil } // 计算请求耗时 endTime := time.Now() requestTime := endTime.Sub(startTime).Milliseconds() // 构建Call方法的响应 response := &Response{ Code: apiResp.Code, Message: apiResp.Message, Success: apiResp.Code == 0, TransactionID: apiResp.TransactionID, Timeout: requestTime, } // 如果有加密数据,尝试解密 if apiResp.Data != "" { decryptedData, err := c.decrypt(apiResp.Data) if err == nil { var decryptedMap map[string]interface{} if json.Unmarshal([]byte(decryptedData), &decryptedMap) == nil { response.Data = decryptedMap } } } // 根据响应码返回对应的错误 if apiResp.Code != 0 { err := GetErrorByCode(apiResp.Code) return nil, err } return response, nil } // CallInterface 简化接口调用方法 func (c *Client) CallInterface(interfaceName string, params map[string]interface{}, options ...*ApiCallOptions) (*Response, error) { var opts *ApiCallOptions if len(options) > 0 { opts = options[0] } req := Request{ InterfaceName: interfaceName, Params: params, Timeout: 60000, Options: opts, } return c.Call(req) } // validateRequest 校验请求参数 func (c *Client) validateRequest(req Request) error { if req.InterfaceName == "" { return fmt.Errorf("interfaceName不能为空") } if req.Params == nil { return fmt.Errorf("params不能为空") } return nil } // encrypt AES CBC加密 func (c *Client) encrypt(plainText string) (string, error) { keyBytes, err := hex.DecodeString(c.key) if err != nil { return "", err } block, err := aes.NewCipher(keyBytes) if err != nil { return "", err } // 生成随机IV iv := make([]byte, aes.BlockSize) if _, err := io.ReadFull(rand.Reader, iv); err != nil { return "", err } // 填充数据 paddedData := c.pkcs7Pad([]byte(plainText), aes.BlockSize) // 加密 ciphertext := make([]byte, len(iv)+len(paddedData)) copy(ciphertext, iv) mode := cipher.NewCBCEncrypter(block, iv) mode.CryptBlocks(ciphertext[len(iv):], paddedData) return base64.StdEncoding.EncodeToString(ciphertext), nil } // decrypt AES CBC解密 func (c *Client) decrypt(encryptedText string) (string, error) { keyBytes, err := hex.DecodeString(c.key) if err != nil { return "", err } ciphertext, err := base64.StdEncoding.DecodeString(encryptedText) if err != nil { return "", err } block, err := aes.NewCipher(keyBytes) if err != nil { return "", err } if len(ciphertext) < aes.BlockSize { return "", fmt.Errorf("密文太短") } iv := ciphertext[:aes.BlockSize] ciphertext = ciphertext[aes.BlockSize:] if len(ciphertext)%aes.BlockSize != 0 { return "", fmt.Errorf("密文长度不是块大小的倍数") } mode := cipher.NewCBCDecrypter(block, iv) mode.CryptBlocks(ciphertext, ciphertext) // 去除填充 unpaddedData, err := c.pkcs7Unpad(ciphertext) if err != nil { return "", err } return string(unpaddedData), nil } // pkcs7Pad PKCS7填充 func (c *Client) pkcs7Pad(data []byte, blockSize int) []byte { padding := blockSize - len(data)%blockSize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(data, padtext...) } // pkcs7Unpad PKCS7去除填充 func (c *Client) pkcs7Unpad(data []byte) ([]byte, error) { length := len(data) if length == 0 { return nil, fmt.Errorf("数据为空") } unpadding := int(data[length-1]) if unpadding > length { return nil, fmt.Errorf("无效的填充") } return data[:length-unpadding], nil } // GetErrorByCode 根据错误码获取错误 func GetErrorByCode(code int) error { // 对于有多个错误对应同一错误码的情况,返回第一个 switch code { case 1000: return ErrQueryEmpty case 1001: return ErrSystem case 1002: return ErrDecryptFail case 1003: return ErrRequestParam case 1004: return ErrInvalidIP case 1005: return ErrMissingAccessId case 1006: return ErrInvalidAccessId case 1007: return ErrFrozenAccount case 1008: return ErrProductNotFound case 2001: return ErrBusiness default: return fmt.Errorf("未知错误码: %d", code) } } // GetCodeByError 根据错误获取错误码 func GetCodeByError(err error) int { if code, exists := ErrorCodeMap[err]; exists { return code } return -1 }