package ymt_v3_generate import ( "bytes" "context" "crypto/rsa" "encoding/json" "fmt" "io" "net/http" "time" ) // Client 蓝色兄弟营销开放 API 客户端 type Client struct { appID string privateKey *rsa.PrivateKey publicKey *rsa.PublicKey key []byte // AES 加密密钥 baseURL string httpClient *http.Client timeout time.Duration } // Option 客户端配置选项 type Option func(*Client) // NewClient 创建客户端 func NewClient(opts ...Option) *Client { c := &Client{ baseURL: "https://gateway.dev.cdlsxd.cn", // 默认测试环境 httpClient: &http.Client{ Timeout: 30 * time.Second, }, } for _, opt := range opts { opt(c) } return c } // WithAppID 设置应用 ID func WithAppID(appID string) Option { return func(c *Client) { c.appID = appID } } // WithPrivateKey 设置私钥(PEM 字符串) func WithPrivateKey(privateKeyPEM string) Option { return func(c *Client) { key, err := ParsePrivateKey(privateKeyPEM) if err != nil { panic(fmt.Sprintf("invalid private key: %v", err)) } c.privateKey = key } } // WithPublicKey 设置公钥(PEM 字符串) func WithPublicKey(publicKeyPEM string) Option { return func(c *Client) { key, err := ParsePublicKey(publicKeyPEM) if err != nil { panic(fmt.Sprintf("invalid public key: %v", err)) } c.publicKey = key } } // WithKey 设置 AES 加密密钥(base64 字符串) func WithKey(keyBase64 string) Option { return func(c *Client) { key, err := base64.StdEncoding.DecodeString(keyBase64) if err != nil { panic(fmt.Sprintf("invalid AES key: %v", err)) } c.key = key } } // WithBaseURL 设置基础 URL func WithBaseURL(baseURL string) Option { return func(c *Client) { c.baseURL = baseURL } } // WithTimeout 设置超时时间 func WithTimeout(timeout time.Duration) Option { return func(c *Client) { c.timeout = timeout c.httpClient.Timeout = timeout } } // WithHTTPClient 设置自定义 HTTP 客户端 func WithHTTPClient(httpClient *http.Client) Option { return func(c *Client) { c.httpClient = httpClient } } // Do 发送 API 请求 func (c *Client) Do(ctx context.Context, method, path string, requestBody interface{}, responseBody interface{}) error { // 1. 将请求体转为排序后的 JSON 字符串(plaintext) plaintext, err := RemoveZeroValuesAndSort(requestBody) if err != nil { return fmt.Errorf("failed to serialize request body: %w", err) } // 2. 加密得到 ciphertext ciphertext, err := EncryptPlaintext(plaintext, c.key) if err != nil { return fmt.Errorf("failed to encrypt request: %w", err) } // 3. 生成时间戳 timestamp := time.Now().Format("2006-01-02 15:04:05") // 4. 生成签名 sign, err := Sign(c.appID, timestamp, ciphertext, c.privateKey) if err != nil { return fmt.Errorf("failed to sign request: %w", err) } // 5. 构造 HTTP 请求 reqBody := map[string]string{"ciphertext": ciphertext} reqBytes, err := json.Marshal(reqBody) if err != nil { return fmt.Errorf("failed to marshal request body: %w", err) } req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, bytes.NewReader(reqBytes)) if err != nil { return fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Appid", c.appID) req.Header.Set("Timestamp", timestamp) req.Header.Set("Sign", sign) // 6. 发送请求 resp, err := c.httpClient.Do(req) if err != nil { return fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() respBytes, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("failed to read response body: %w", err) } // 7. 解析公共响应 var apiResp ApiResponse if err := json.Unmarshal(respBytes, &apiResp); err != nil { return fmt.Errorf("failed to parse response: %w", err) } if apiResp.Code != 200 { return &APIError{ Code: apiResp.Code, Message: apiResp.Message, Reason: apiResp.Reason, } } // 8. 解密 data.ciphertext decrypted, err := DecryptCiphertext(apiResp.Data.Ciphertext, c.key) if err != nil { return fmt.Errorf("failed to decrypt response: %w", err) } // 9. 反序列化到业务响应 if err := json.Unmarshal([]byte(decrypted), responseBody); err != nil { return fmt.Errorf("failed to unmarshal business response: %w", err) } return nil }