Files
tyapi-server/internal/shared/esign/http.go
2025-07-28 01:46:39 +08:00

220 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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