添加文件: ymt_v3_2/client.go
This commit is contained in:
parent
b87b49cf3b
commit
475414a5e5
|
|
@ -0,0 +1,188 @@
|
||||||
|
package ymt_v3_2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
httpClient *http.Client
|
||||||
|
baseURL string
|
||||||
|
appID string
|
||||||
|
privateKey *rsa.PrivateKey
|
||||||
|
publicKey *rsa.PublicKey
|
||||||
|
aesKey []byte
|
||||||
|
signType string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*Client)
|
||||||
|
|
||||||
|
func WithBaseURL(url string) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.baseURL = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTimeout(d time.Duration) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.httpClient.Timeout = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithHTTPClient(client *http.Client) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.httpClient = client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAppID(appID string) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.appID = appID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithPrivateKeyPEM(pemData string) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
key, err := parsePrivateKey(pemData)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to parse private key: %v", err))
|
||||||
|
}
|
||||||
|
c.privateKey = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithPublicKeyPEM(pemData string) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
key, err := parsePublicKey(pemData)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to parse public key: %v", err))
|
||||||
|
}
|
||||||
|
c.publicKey = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAESKey(key string) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.aesKey = []byte(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithSignType(signType string) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.signType = signType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(opts ...Option) *Client {
|
||||||
|
c := &Client{
|
||||||
|
httpClient: &http.Client{Timeout: 30 * time.Second},
|
||||||
|
baseURL: "https://gateway.dev.cdlsxd.cn",
|
||||||
|
signType: "RSA",
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Do(ctx context.Context, method, path string, bizReq interface{}, bizResp interface{}) error {
|
||||||
|
plaintext, err := marshalSortedNoZero(bizReq)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, err := aesEncryptECB(plaintext, c.aesKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encrypt: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
|
||||||
|
signStr := c.appID + timestamp + ciphertext
|
||||||
|
sign, err := rsaSign([]byte(signStr), c.privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sign: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := cipherRequest{Ciphertext: ciphertext}
|
||||||
|
bodyBytes, err := json.Marshal(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal request body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := c.baseURL + path
|
||||||
|
req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(bodyBytes))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Appid", c.appID)
|
||||||
|
req.Header.Set("Timestamp", timestamp)
|
||||||
|
req.Header.Set("Sign", sign)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("http request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBytes, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var commonResp commonResponse
|
||||||
|
if err := json.Unmarshal(respBytes, &commonResp); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if commonResp.Code != 200 {
|
||||||
|
return &APIError{
|
||||||
|
Code: commonResp.Code,
|
||||||
|
Message: commonResp.Message,
|
||||||
|
Reason: commonResp.Reason,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(commonResp.Data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cd cipherData
|
||||||
|
if err := json.Unmarshal(commonResp.Data, &cd); err == nil && cd.Ciphertext != "" {
|
||||||
|
decrypted, err := aesDecryptECB(cd.Ciphertext, c.aesKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decrypt response: %w", err)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(decrypted, bizResp); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal decrypted response: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := json.Unmarshal(commonResp.Data, bizResp); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal plain data: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type cipherRequest struct {
|
||||||
|
Ciphertext string `json:"ciphertext"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type commonResponse struct {
|
||||||
|
Code int32 `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
Data json.RawMessage `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type cipherData struct {
|
||||||
|
Ciphertext string `json:"ciphertext"`
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue