200 lines
4.8 KiB
Go
200 lines
4.8 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()
|
|||
|
|
nonce := generateNonce()
|
|||
|
|
date := getCurrentDate()
|
|||
|
|
|
|||
|
|
// 计算Content-MD5
|
|||
|
|
contentMD5 := ""
|
|||
|
|
if len(body) > 0 {
|
|||
|
|
contentMD5 = getContentMD5(body)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 根据Java示例,Headers为空字符串
|
|||
|
|
headers := ""
|
|||
|
|
|
|||
|
|
// 生成签名
|
|||
|
|
signature := generateSignature(h.config.AppSecret, method, "*/*", contentMD5, "application/json", date, headers, urlPath)
|
|||
|
|
|
|||
|
|
// 创建HTTP请求
|
|||
|
|
url := h.config.ServerURL + urlPath
|
|||
|
|
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-Nonce", nonce)
|
|||
|
|
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
|
|||
|
|
}
|