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, "&")
 | ||
| }
 |