From 54d4cba9e031b4380c08d38861da4e67c15172ab Mon Sep 17 00:00:00 2001 From: renzhiyuan <465386466@qq.com> Date: Tue, 21 Jul 2026 18:16:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6:=20client.?= =?UTF-8?q?go?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client.go | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 client.go diff --git a/client.go b/client.go new file mode 100644 index 0000000..b167dae --- /dev/null +++ b/client.go @@ -0,0 +1,247 @@ +package xyshanghai + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "time" +) + +// Client 供应商API客户端 +type Client struct { + baseURL string + httpClient *http.Client + apiKey *APIKey +} + +// Option 客户端配置选项 +type Option func(*Client) + +// WithBaseURL 设置基础URL +func WithBaseURL(baseURL string) Option { + return func(c *Client) { + c.baseURL = baseURL + } +} + +// WithTimeout 设置HTTP超时时间 +func WithTimeout(timeout time.Duration) Option { + return func(c *Client) { + c.httpClient.Timeout = timeout + } +} + +// WithHTTPClient 设置自定义HTTP客户端 +func WithHTTPClient(httpClient *http.Client) Option { + return func(c *Client) { + c.httpClient = httpClient + } +} + +// NewClient 创建新的客户端 +func NewClient(apiKey *APIKey, opts ...Option) *Client { + c := &Client{ + httpClient: &http.Client{Timeout: 30 * time.Second}, + apiKey: apiKey, + } + for _, opt := range opts { + opt(c) + } + return c +} + +// doRequest 发送请求并处理响应 +func (c *Client) doRequest(ctx context.Context, path string, reqBody interface{}) (*CommonResponse, error) { + // 序列化业务数据 + plaintext, err := json.Marshal(reqBody) + if err != nil { + return nil, fmt.Errorf("marshal request body: %w", err) + } + + // SM4加密 + encryptedData, err := SM4Encrypt(c.apiKey.SM4Key, plaintext) + if err != nil { + return nil, fmt.Errorf("encrypt data: %w", err) + } + + // 生成时间戳 + timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10) + + // 生成签名 + sign := SM3Sign(c.apiKey.SM3Salt, timestamp+encryptedData) + + // 构建请求体 + commonReq := CommonRequestBody{EncryptedData: encryptedData} + reqBytes, err := json.Marshal(commonReq) + if err != nil { + return nil, fmt.Errorf("marshal common request: %w", err) + } + + // 创建HTTP请求 + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, bytes.NewReader(reqBytes)) + if err != nil { + return nil, fmt.Errorf("create request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("timestamp", timestamp) + req.Header.Set("sign", sign) + + // 发送请求 + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("do request: %w", err) + } + defer resp.Body.Close() + + // 读取响应 + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response body: %w", err) + } + + // 解析公共响应 + var commonResp CommonResponse + if err := json.Unmarshal(respBody, &commonResp); err != nil { + return nil, fmt.Errorf("unmarshal common response: %w", err) + } + + return &commonResp, nil +} + +// decryptData 解密响应中的data字段 +func (c *Client) decryptData(encryptedData string, target interface{}) error { + plaintext, err := SM4Decrypt(c.apiKey.SM4Key, encryptedData) + if err != nil { + return fmt.Errorf("decrypt data: %w", err) + } + if err := json.Unmarshal(plaintext, target); err != nil { + return fmt.Errorf("unmarshal decrypted data: %w", err) + } + return nil +} + +// CreateOrder 卡券/直充权益下单 +func (c *Client) CreateOrder(ctx context.Context, req *CreateOrderRequest) (*CreateOrderResponse, error) { + commonResp, err := c.doRequest(ctx, "/order/create", req) + if err != nil { + return nil, err + } + + if commonResp.Code != 0 { + return nil, NewSDKError(ErrorCode(commonResp.Code), commonResp.Msg, nil) + } + + if commonResp.Data == nil { + return nil, NewSDKError(ErrCodeFailure, "response data is nil", nil) + } + + var resp CreateOrderResponse + if err := c.decryptData(*commonResp.Data, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +// QueryOrder 卡券/直充/微信立减金订单查询 +func (c *Client) QueryOrder(ctx context.Context, req *QueryOrderRequest) (*QueryOrderResponse, error) { + commonResp, err := c.doRequest(ctx, "/order/query", req) + if err != nil { + return nil, err + } + + if commonResp.Code != 0 { + return nil, NewSDKError(ErrorCode(commonResp.Code), commonResp.Msg, nil) + } + + if commonResp.Data == nil { + return nil, NewSDKError(ErrCodeFailure, "response data is nil", nil) + } + + var resp QueryOrderResponse + if err := c.decryptData(*commonResp.Data, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +// RechargeOrder 微信立减金订单充值 +func (c *Client) RechargeOrder(ctx context.Context, req *RechargeOrderRequest) (*RechargeOrderResponse, error) { + commonResp, err := c.doRequest(ctx, "/order/recharge", req) + if err != nil { + return nil, err + } + + if commonResp.Code != 0 { + return nil, NewSDKError(ErrorCode(commonResp.Code), commonResp.Msg, nil) + } + + if commonResp.Data == nil { + return nil, NewSDKError(ErrCodeFailure, "response data is nil", nil) + } + + var resp RechargeOrderResponse + if err := c.decryptData(*commonResp.Data, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +// ParseCallbackRequest 解析回调请求,返回业务数据 +func (c *Client) ParseCallbackRequest(r *http.Request) (*CallbackRequest, error) { + // 读取请求体 + body, err := io.ReadAll(r.Body) + if err != nil { + return nil, fmt.Errorf("read request body: %w", err) + } + defer r.Body.Close() + + // 解析通用请求体 + var commonReq CommonRequestBody + if err := json.Unmarshal(body, &commonReq); err != nil { + return nil, fmt.Errorf("unmarshal common request: %w", err) + } + + // 验证签名(可选) + timestamp := r.Header.Get("timestamp") + sign := r.Header.Get("sign") + expectedSign := SM3Sign(c.apiKey.SM3Salt, timestamp+commonReq.EncryptedData) + if sign != expectedSign { + return nil, NewSDKError(ErrCodeFailure, "sign verification failed", nil) + } + + // 解密业务数据 + plaintext, err := SM4Decrypt(c.apiKey.SM4Key, commonReq.EncryptedData) + if err != nil { + return nil, fmt.Errorf("decrypt callback data: %w", err) + } + + var callbackReq CallbackRequest + if err := json.Unmarshal(plaintext, &callbackReq); err != nil { + return nil, fmt.Errorf("unmarshal callback request: %w", err) + } + + return &callbackReq, nil +} + +// CallbackHandler 返回一个http.Handler,用于处理回调通知 +func (c *Client) CallbackHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callbackReq, err := c.ParseCallbackRequest(r) + if err != nil { + http.Error(w, "invalid request", http.StatusBadRequest) + return + } + + // 用户需要实现自己的处理逻辑,这里仅返回成功 + // 实际使用时,用户应通过闭包或回调函数注入业务处理 + _ = callbackReq + + // 返回成功 + w.WriteHeader(http.StatusOK) + w.Write([]byte("ok")) + }) +} \ No newline at end of file