添加文件: ymt_v3_generate/client.go

This commit is contained in:
renzhiyuan 2026-07-21 10:13:05 +08:00
parent 3c626ba75f
commit 649e43b41b
1 changed files with 181 additions and 0 deletions

181
ymt_v3_generate/client.go Normal file
View File

@ -0,0 +1,181 @@
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
}