This commit is contained in:
2025-07-20 20:53:26 +08:00
parent 83bf9aea7d
commit 8ad1d7288e
158 changed files with 18156 additions and 13188 deletions

View File

@@ -0,0 +1,199 @@
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
}