package ymt_v3 import ( "bytes" "encoding/json" "fmt" "io" "net/http" ) // Env 环境 type Env string const ( EnvTest Env = "test" EnvProduct Env = "product" ) // 环境地址 const ( testBaseURL = "https://gateway.dev.cdlsxd.cn" productBaseURL = "https://market.api.86698.cn" ) // ClientConfig 客户端配置 type ClientConfig struct { AppID string // 应用ID PrivateKey string // 应用私钥(PEM格式) PublicKey string // 平台公钥(PEM格式) Key string // 业务参数加密key EncryptType EncryptType // 加密类型:aes 或 sm4 Env Env // 环境:test 或 product HTTPClient *http.Client // HTTP客户端,可选 } // Client SDK客户端 type Client struct { config *ClientConfig httpClient *http.Client baseURL string } // NewClient 创建新的SDK客户端 func NewClient(config *ClientConfig) (*Client, error) { if config.AppID == "" { return nil, fmt.Errorf("AppID不能为空") } if config.PrivateKey == "" { return nil, fmt.Errorf("PrivateKey不能为空") } if config.PublicKey == "" { return nil, fmt.Errorf("PublicKey不能为空") } if config.Key == "" { return nil, fmt.Errorf("Key不能为空") } if config.EncryptType == "" { config.EncryptType = EncryptTypeAES } if config.Env == "" { config.Env = EnvTest } var baseURL string switch config.Env { case EnvTest: baseURL = testBaseURL case EnvProduct: baseURL = productBaseURL default: return nil, fmt.Errorf("不支持的环境: %s", config.Env) } httpClient := config.HTTPClient if httpClient == nil { httpClient = &http.Client{} } return &Client{ config: config, httpClient: httpClient, baseURL: baseURL, }, nil } // doRequest 发送请求并处理响应 func (c *Client) doRequest(path string, bizParams interface{}, result interface{}) error { // 1. 加密业务参数 ciphertext, err := EncryptBizParams(bizParams, []byte(c.config.Key), c.config.EncryptType) if err != nil { return NewSDKError("加密业务参数失败", err) } // 2. 生成时间戳 timestamp := GenerateTimestamp() // 3. 构建签名字符串并签名 signStr := BuildSignStr(c.config.AppID, timestamp, ciphertext) sign, err := SignWithRSA(signStr, c.config.PrivateKey) if err != nil { return NewSDKError("签名失败", err) } // 4. 构建请求体 reqBody := map[string]string{ "ciphertext": ciphertext, } reqBodyBytes, err := json.Marshal(reqBody) if err != nil { return NewSDKError("序列化请求体失败", err) } // 5. 创建HTTP请求 url := c.baseURL + path req, err := http.NewRequest("POST", url, bytes.NewReader(reqBodyBytes)) if err != nil { return NewSDKError("创建HTTP请求失败", err) } // 6. 设置Header req.Header.Set("Content-Type", "application/json") req.Header.Set("Appid", c.config.AppID) req.Header.Set("Timestamp", timestamp) req.Header.Set("Sign", sign) // 7. 发送请求 resp, err := c.httpClient.Do(req) if err != nil { return NewSDKError("发送HTTP请求失败", err) } defer resp.Body.Close() // 8. 读取响应 respBody, err := io.ReadAll(resp.Body) if err != nil { return NewSDKError("读取响应失败", err) } // 9. 解析公共响应 var commonResp CommonResponse if err := json.Unmarshal(respBody, &commonResp); err != nil { return NewSDKError("解析响应JSON失败", err) } // 10. 检查业务错误 if commonResp.Code != 200 { return NewAPIError(commonResp.Code, commonResp.Message, commonResp.Reason) } // 11. 检查是否有加密数据 if commonResp.Data == nil || commonResp.Data.Ciphertext == "" { return NewSDKError("响应中没有加密数据", nil) } // 12. 解密响应数据 decrypted, err := DecryptBizParams(commonResp.Data.Ciphertext, []byte(c.config.Key), c.config.EncryptType) if err != nil { return NewSDKError("解密响应数据失败", err) } // 13. 解析解密后的数据到结果 if err := json.Unmarshal(decrypted, result); err != nil { return NewSDKError("解析解密后的响应数据失败", err) } return nil } // ==================== API方法 ==================== // KeyOrder 获取券码 // POST /openapi/v1/key/order func (c *Client) KeyOrder(req *KeyOrderRequest) (*KeyOrderResponse, error) { result := &KeyOrderResponse{} if err := c.doRequest("/openapi/v1/key/order", req, result); err != nil { return nil, err } return result, nil } // KeyQuery 券码查询 // POST /openapi/v1/key/query func (c *Client) KeyQuery(req *KeyQueryRequest) (*KeyOrderResponse, error) { result := &KeyOrderResponse{} if err := c.doRequest("/openapi/v1/key/query", req, result); err != nil { return nil, err } return result, nil } // KeyDiscard 券码作废 // POST /openapi/v1/key/discard func (c *Client) KeyDiscard(req *KeyDiscardRequest) (*KeyDiscardResponse, error) { result := &KeyDiscardResponse{} if err := c.doRequest("/openapi/v1/key/discard", req, result); err != nil { return nil, err } return result, nil } // BatchOrder 批量发卡 // POST /openapi/v1/key/batch_order func (c *Client) BatchOrder(req *BatchOrderRequest) (*BatchOrderResponse, error) { result := &BatchOrderResponse{} if err := c.doRequest("/openapi/v1/key/batch_order", req, result); err != nil { return nil, err } return result, nil } // BatchQuery 批量查询 // POST /openapi/v1/key/batch_query func (c *Client) BatchQuery(req *BatchQueryRequest) (*BatchQueryResponse, error) { result := &BatchQueryResponse{} if err := c.doRequest("/openapi/v1/key/batch_query", req, result); err != nil { return nil, err } return result, nil } // VerifyNotify 验证回调通知 func (c *Client) VerifyNotify(header *NotifyHeader, data interface{}) error { return VerifyNotifySign(header, data, []byte(c.config.Key), c.config.PublicKey, c.config.EncryptType) } // GetBaseURL 获取当前环境的基础URL func (c *Client) GetBaseURL() string { return c.baseURL } // GetAppID 获取应用ID func (c *Client) GetAppID() string { return c.config.AppID } // String 返回客户端配置的字符串表示(隐藏敏感信息) func (c *Client) String() string { return fmt.Sprintf("Client{AppID: %s, Env: %s, BaseURL: %s, EncryptType: %s}", c.config.AppID, c.config.Env, c.baseURL, c.config.EncryptType) }