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