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

198 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 (
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"sort"
"strconv"
"strings"
"time"
)
// appendSignDataString 拼接待签名字符串
func appendSignDataString(httpMethod, accept, contentMD5, contentType, date, headers, pathAndParameters string) string {
if accept == "" {
accept = "*/*"
}
if contentType == "" {
contentType = "application/json; charset=UTF-8"
}
// 前四项
signStr := httpMethod + "\n" + accept + "\n" + contentMD5 + "\n" + contentType + "\n"
// 处理 date
if date == "" {
signStr += "\n"
} else {
signStr += date + "\n"
}
// 处理 headers
if headers == "" {
signStr += pathAndParameters
} else {
signStr += headers + "\n" + pathAndParameters
}
return signStr
}
// generateSignature 生成e签宝API请求签名
// 使用HMAC-SHA256算法对请求参数进行签名
//
// 参数说明:
// - appSecret: 应用密钥
// - httpMethod: HTTP方法GET、POST等
// - accept: Accept头值
// - contentMD5: 请求体MD5值
// - contentType: Content-Type头值
// - date: Date头值
// - headers: 自定义头部信息
// - pathAndParameters: 请求路径和参数
//
// 返回: Base64编码的签名字符串
func generateSignature(appSecret, httpMethod, accept, contentMD5, contentType, date, headers, pathAndParameters string) string {
// 构建待签名字符串按照e签宝API规范拼接兼容Python实现细节
signStr := appendSignDataString(httpMethod, accept, contentMD5, contentType, date, headers, pathAndParameters)
// 使用HMAC-SHA256计算签名
h := hmac.New(sha256.New, []byte(appSecret))
h.Write([]byte(signStr))
digestBytes := h.Sum(nil)
// 对摘要结果进行Base64编码
signature := base64.StdEncoding.EncodeToString(digestBytes)
return signature
}
// generateNonce 生成随机字符串
// 使用当前时间的纳秒数作为随机字符串
//
// 返回: 纳秒时间戳字符串
func generateNonce() string {
return strconv.FormatInt(time.Now().UnixNano(), 10)
}
// getContentMD5 计算请求体的MD5值
// 对请求体进行MD5哈希计算然后进行Base64编码
//
// 参数:
// - body: 请求体字节数组
//
// 返回: Base64编码的MD5值
func getContentMD5(body []byte) string {
md5Sum := md5.Sum(body)
return base64.StdEncoding.EncodeToString(md5Sum[:])
}
// getCurrentTimestamp 获取当前时间戳(毫秒)
//
// 返回: 毫秒级时间戳字符串
func getCurrentTimestamp() string {
return strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
}
// getCurrentDate 获取当前UTC时间字符串
// 格式: "Mon, 02 Jan 2006 15:04:05 GMT"
//
// 返回: RFC1123格式的UTC时间字符串
func getCurrentDate() string {
return time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")
}
// formatDateForTemplate 格式化日期用于模板填写
// 格式: "2006年01月02日"
//
// 返回: 中文格式的日期字符串
func formatDateForTemplate() string {
return time.Now().Format("2006年01月02日")
}
// generateFileName 生成带时间戳的文件名
//
// 参数:
// - baseName: 基础文件名
// - extension: 文件扩展名
//
// 返回: 带时间戳的文件名
func generateFileName(baseName, extension string) string {
timestamp := time.Now().Format("20060102_150405")
return baseName + "_" + timestamp + "." + extension
}
// calculateExpireTime 计算过期时间戳
//
// 参数:
// - days: 过期天数
//
// 返回: 毫秒级时间戳
func calculateExpireTime(days int) int64 {
return time.Now().AddDate(0, 0, days).UnixMilli()
}
// verifySignature 验证e签宝回调签名
func VerifySignature(callbackData interface{}, headers map[string]string, queryParams map[string]string, appSecret string) error {
// 1. 获取签名相关参数
signature, ok := headers["X-Tsign-Open-Signature"]
if !ok {
return fmt.Errorf("缺少签名头: X-Tsign-Open-Signature")
}
timestamp, ok := headers["X-Tsign-Open-Timestamp"]
if !ok {
return fmt.Errorf("缺少时间戳头: X-Tsign-Open-Timestamp")
}
// 2. 构建查询参数字符串
var queryKeys []string
for key := range queryParams {
queryKeys = append(queryKeys, key)
}
sort.Strings(queryKeys) // 按ASCII码升序排序
var queryValues []string
for _, key := range queryKeys {
queryValues = append(queryValues, queryParams[key])
}
queryString := strings.Join(queryValues, "")
// 3. 获取请求体数据
bodyData, err := getRequestBodyString(callbackData)
if err != nil {
return fmt.Errorf("获取请求体数据失败: %w", err)
}
// 4. 构建验签数据
data := timestamp + queryString + bodyData
// 5. 计算签名
expectedSignature := calculateSignature(data, appSecret)
// 6. 比较签名
if strings.ToLower(expectedSignature) != strings.ToLower(signature) {
return fmt.Errorf("签名验证失败")
}
return nil
}
// calculateSignature 计算HMAC-SHA256签名
func calculateSignature(data, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(data))
return strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
}
// getRequestBodyString 获取请求体字符串
func getRequestBodyString(callbackData interface{}) (string, error) {
// 将map转换为JSON字符串
jsonBytes, err := json.Marshal(callbackData)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %w", err)
}
return string(jsonBytes), nil
}