168 lines
4.0 KiB
Go

package openapi
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
"github.com/go-resty/resty/v2"
"github.com/google/uuid"
)
type Client struct {
client *resty.Client
}
func NewClient(endpoint, accessKeyId, secretAccessKey string) (*Client, error) {
if endpoint == "" {
return nil, fmt.Errorf("sdkerr: unset endpoint")
}
if _, err := url.Parse(endpoint); err != nil {
return nil, fmt.Errorf("sdkerr: invalid endpoint: %w", err)
}
if accessKeyId == "" {
return nil, fmt.Errorf("sdkerr: unset accessKey")
}
if secretAccessKey == "" {
return nil, fmt.Errorf("sdkerr: unset secretKey")
}
client := resty.New().
SetBaseURL(endpoint).
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", "certimate").
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
// 生成时间戳及流水号
now := time.Now()
eopDate := now.Format("20060102T150405Z")
eopReqId := uuid.New().String()
// 获取查询参数
queryStr := ""
if req.URL != nil {
queryStr = req.URL.Query().Encode()
}
// 获取请求正文
payloadStr := ""
if req.Body != nil {
reader, err := req.GetBody()
if err != nil {
return err
}
defer reader.Close()
payload, err := io.ReadAll(reader)
if err != nil {
return err
}
payloadStr = string(payload)
}
// 构造代签字符串
payloadHash := sha256.Sum256([]byte(payloadStr))
payloadHashHex := hex.EncodeToString(payloadHash[:])
dataToSign := fmt.Sprintf("ctyun-eop-request-id:%s\neop-date:%s\n\n%s\n%s", eopReqId, eopDate, queryStr, payloadHashHex)
// 生成 ktime
hasher := hmac.New(sha256.New, []byte(secretAccessKey))
hasher.Write([]byte(eopDate))
ktime := hasher.Sum(nil)
// 生成 kak
hasher = hmac.New(sha256.New, ktime)
hasher.Write([]byte(accessKeyId))
kak := hasher.Sum(nil)
// 生成 kdate
hasher = hmac.New(sha256.New, kak)
hasher.Write([]byte(now.Format("20060102")))
kdate := hasher.Sum(nil)
// 构造签名
hasher = hmac.New(sha256.New, kdate)
hasher.Write([]byte(dataToSign))
sign := hasher.Sum(nil)
signStr := base64.StdEncoding.EncodeToString(sign)
// 设置请求头
req.Header.Set("ctyun-eop-request-id", eopReqId)
req.Header.Set("eop-date", eopDate)
req.Header.Set("eop-authorization", fmt.Sprintf("%s Headers=ctyun-eop-request-id;eop-date Signature=%s", accessKeyId, signStr))
return nil
})
return &Client{
client: client,
}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {
c.client.SetTimeout(timeout)
return c
}
func (c *Client) NewRequest(method string, path string) (*resty.Request, error) {
if method == "" {
return nil, fmt.Errorf("sdkerr: unset method")
}
if path == "" {
return nil, fmt.Errorf("sdkerr: unset path")
}
req := c.client.R()
req.Method = method
req.URL = path
return req, nil
}
func (c *Client) DoRequest(request *resty.Request) (*resty.Response, error) {
if request == nil {
return nil, fmt.Errorf("sdkerr: nil request")
}
// WARN:
// PLEASE DO NOT USE `req.SetResult` or `req.SetError` here.
resp, err := request.Send()
if err != nil {
return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
} else if resp.IsError() {
return resp, fmt.Errorf("sdkerr: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
}
return resp, nil
}
func (c *Client) DoRequestWithResult(request *resty.Request, result any) (*resty.Response, error) {
if request == nil {
return nil, fmt.Errorf("sdkerr: nil request")
}
response, err := c.DoRequest(request)
if err != nil {
if response != nil {
json.Unmarshal(response.Body(), &result)
}
return response, err
}
if len(response.Body()) != 0 {
if err := json.Unmarshal(response.Body(), &result); err != nil {
return response, fmt.Errorf("sdkerr: failed to unmarshal response: %w", err)
}
}
return response, nil
}