| 
									
										
										
										
											2025-07-20 20:53:26 +08:00
										 |  |  |  | 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() | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | 	// date := getCurrentDate() | 
					
						
							|  |  |  |  | 	date := "" | 
					
						
							| 
									
										
										
										
											2025-07-20 20:53:26 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// 计算Content-MD5 | 
					
						
							|  |  |  |  | 	contentMD5 := "" | 
					
						
							|  |  |  |  | 	if len(body) > 0 { | 
					
						
							|  |  |  |  | 		contentMD5 = getContentMD5(body) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	headers := "" | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | 	// 生成签名(用原始urlPath) | 
					
						
							| 
									
										
										
										
											2025-07-20 20:53:26 +08:00
										 |  |  |  | 	signature := generateSignature(h.config.AppSecret, method, "*/*", contentMD5, "application/json", date, headers, urlPath) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | 	// 实际请求url用encode后的urlPath | 
					
						
							|  |  |  |  | 	encodedURLPath := encodeURLQueryParams(urlPath) | 
					
						
							|  |  |  |  | 	url := h.config.ServerURL + encodedURLPath | 
					
						
							| 
									
										
										
										
											2025-07-20 20:53:26 +08:00
										 |  |  |  | 	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) | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | 	// req.Header.Set("Date", date) | 
					
						
							| 
									
										
										
										
											2025-07-20 20:53:26 +08:00
										 |  |  |  | 	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 | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | // 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, "&") | 
					
						
							|  |  |  |  | } |