添加文件: 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