2025-07-20 20:53:26 +08:00
|
|
|
|
package esign
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"crypto/hmac"
|
|
|
|
|
|
"crypto/md5"
|
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
|
"encoding/base64"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"encoding/hex"
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"sort"
|
2025-07-20 20:53:26 +08:00
|
|
|
|
"strconv"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"strings"
|
2025-07-20 20:53:26 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// 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 {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 构建待签名字符串,按照e签宝API规范拼接(兼容Python实现细节)
|
|
|
|
|
|
signStr := appendSignDataString(httpMethod, accept, contentMD5, contentType, date, headers, pathAndParameters)
|
2025-07-20 20:53:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 使用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()
|
|
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
}
|