220 lines
5.3 KiB
Go
220 lines
5.3 KiB
Go
package esign
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"net/url"
|
||
"sort"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// HTTPClient e签宝HTTP客户端
|
||
// 处理所有e签宝API的HTTP请求,包括签名生成、请求头设置等
|
||
type HTTPClient struct {
|
||
config *Config
|
||
client *http.Client
|
||
}
|
||
|
||
// NewHTTPClient 创建HTTP客户端
|
||
func NewHTTPClient(config *Config) *HTTPClient {
|
||
return &HTTPClient{
|
||
config: config,
|
||
client: &http.Client{Timeout: 30 * time.Second},
|
||
}
|
||
}
|
||
|
||
// UpdateConfig 更新配置
|
||
func (h *HTTPClient) UpdateConfig(config *Config) {
|
||
h.config = config
|
||
}
|
||
|
||
// Request e签宝通用请求函数
|
||
// 处理所有e签宝API的HTTP请求,包括签名生成、请求头设置等
|
||
//
|
||
// 参数说明:
|
||
// - method: HTTP方法(GET、POST等)
|
||
// - urlPath: API路径
|
||
// - body: 请求体字节数组
|
||
//
|
||
// 返回: 响应体字节数组和错误信息
|
||
func (h *HTTPClient) Request(method, urlPath string, body []byte) ([]byte, error) {
|
||
// 生成签名所需参数
|
||
timestamp := getCurrentTimestamp()
|
||
// date := getCurrentDate()
|
||
date := ""
|
||
|
||
// 计算Content-MD5
|
||
contentMD5 := ""
|
||
if len(body) > 0 {
|
||
contentMD5 = getContentMD5(body)
|
||
}
|
||
|
||
headers := ""
|
||
|
||
// 生成签名(用原始urlPath)
|
||
signature := generateSignature(h.config.AppSecret, method, "*/*", contentMD5, "application/json", date, headers, urlPath)
|
||
|
||
// 实际请求url用encode后的urlPath
|
||
encodedURLPath := encodeURLQueryParams(urlPath)
|
||
url := h.config.ServerURL + encodedURLPath
|
||
req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建HTTP请求失败: %v", err)
|
||
}
|
||
|
||
// 设置请求头
|
||
req.Header.Set("Content-Type", "application/json")
|
||
req.Header.Set("Content-MD5", contentMD5)
|
||
// req.Header.Set("Date", date)
|
||
req.Header.Set("Accept", "*/*")
|
||
req.Header.Set("X-Tsign-Open-App-Id", h.config.AppID)
|
||
req.Header.Set("X-Tsign-Open-Auth-Mode", "Signature")
|
||
req.Header.Set("X-Tsign-Open-Ca-Timestamp", timestamp)
|
||
req.Header.Set("X-Tsign-Open-Ca-Signature", signature)
|
||
|
||
// 发送请求
|
||
client := &http.Client{Timeout: 30 * time.Second}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("发送HTTP请求失败: %v", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// 读取响应
|
||
responseBody, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("读取响应失败: %v", err)
|
||
}
|
||
|
||
// 打印响应内容用于调试
|
||
fmt.Printf("API响应状态码: %d\n", resp.StatusCode)
|
||
fmt.Printf("API响应内容: %s\n", string(responseBody))
|
||
|
||
// 检查响应状态码
|
||
if resp.StatusCode != 200 {
|
||
return nil, fmt.Errorf("API请求失败,状态码: %d", resp.StatusCode)
|
||
}
|
||
|
||
return responseBody, nil
|
||
}
|
||
|
||
// MarshalRequest 序列化请求数据为JSON
|
||
//
|
||
// 参数:
|
||
// - data: 要序列化的数据
|
||
//
|
||
// 返回: JSON字节数组和错误信息
|
||
func MarshalRequest(data interface{}) ([]byte, error) {
|
||
jsonData, err := json.Marshal(data)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("序列化请求数据失败: %v", err)
|
||
}
|
||
return jsonData, nil
|
||
}
|
||
|
||
// UnmarshalResponse 反序列化响应数据
|
||
//
|
||
// 参数:
|
||
// - responseBody: 响应体字节数组
|
||
// - response: 目标响应结构体指针
|
||
//
|
||
// 返回: 错误信息
|
||
func UnmarshalResponse(responseBody []byte, response interface{}) error {
|
||
if err := json.Unmarshal(responseBody, response); err != nil {
|
||
return fmt.Errorf("解析响应失败: %v,响应内容: %s", err, string(responseBody))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// CheckResponseCode 检查API响应码
|
||
//
|
||
// 参数:
|
||
// - code: 响应码
|
||
// - message: 响应消息
|
||
//
|
||
// 返回: 错误信息
|
||
func CheckResponseCode(code int, message string) error {
|
||
if code != 0 {
|
||
return fmt.Errorf("API调用失败: %s", message)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// sortURLQueryParams 对URL查询参数按字典序(ASCII码)升序排序
|
||
//
|
||
// 参数:
|
||
// - urlPath: 包含查询参数的URL路径
|
||
//
|
||
// 返回: 排序后的URL路径
|
||
func sortURLQueryParams(urlPath string) string {
|
||
// 检查是否包含查询参数
|
||
if !strings.Contains(urlPath, "?") {
|
||
return urlPath
|
||
}
|
||
|
||
// 分离路径和查询参数
|
||
parts := strings.SplitN(urlPath, "?", 2)
|
||
if len(parts) != 2 {
|
||
return urlPath
|
||
}
|
||
|
||
basePath := parts[0]
|
||
queryString := parts[1]
|
||
|
||
// 解析查询参数
|
||
values, err := url.ParseQuery(queryString)
|
||
if err != nil {
|
||
// 如果解析失败,返回原始路径
|
||
return urlPath
|
||
}
|
||
|
||
// 获取所有参数键并排序
|
||
var keys []string
|
||
for key := range values {
|
||
keys = append(keys, key)
|
||
}
|
||
sort.Strings(keys)
|
||
|
||
// 重新构建查询字符串
|
||
var sortedPairs []string
|
||
for _, key := range keys {
|
||
for _, value := range values[key] {
|
||
sortedPairs = append(sortedPairs, key+"="+value)
|
||
}
|
||
}
|
||
|
||
// 组合排序后的查询参数
|
||
sortedQueryString := strings.Join(sortedPairs, "&")
|
||
|
||
// 返回完整的URL路径
|
||
if sortedQueryString != "" {
|
||
return basePath + "?" + sortedQueryString
|
||
}
|
||
return basePath
|
||
}
|
||
|
||
// encodeURLQueryParams 对urlPath中的query参数值进行encode
|
||
func encodeURLQueryParams(urlPath string) string {
|
||
if !strings.Contains(urlPath, "?") {
|
||
return urlPath
|
||
}
|
||
parts := strings.SplitN(urlPath, "?", 2)
|
||
basePath := parts[0]
|
||
queryString := parts[1]
|
||
values, err := url.ParseQuery(queryString)
|
||
if err != nil {
|
||
return urlPath
|
||
}
|
||
var encodedPairs []string
|
||
for key, vals := range values {
|
||
for _, val := range vals {
|
||
encodedPairs = append(encodedPairs, key+"="+url.QueryEscape(val))
|
||
}
|
||
}
|
||
return basePath + "?" + strings.Join(encodedPairs, "&")
|
||
}
|