647 lines
17 KiB
Go
647 lines
17 KiB
Go
package utils
|
||
|
||
import (
|
||
"bytes"
|
||
"crypto"
|
||
"crypto/rand"
|
||
"crypto/rsa"
|
||
"crypto/sha1"
|
||
"crypto/sha256"
|
||
"crypto/x509"
|
||
"encoding/base64"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"encoding/pem"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"net/url"
|
||
"os"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
Host = "https://api.mch.weixin.qq.com"
|
||
MethodGET = "GET"
|
||
MethodPOST = "POST"
|
||
)
|
||
|
||
// MchConfig 商户信息配置,用于调用商户API
|
||
// https://pay.weixin.qq.com/doc/v3/merchant/4012716434
|
||
//
|
||
// 引用微信支付工具库 参考:https://pay.weixin.qq.com/doc/v3/merchant/4015119334
|
||
type MchConfig struct {
|
||
mchId string
|
||
certificateSerialNo string
|
||
privateKeyFilePath string
|
||
wechatPayPublicKeyId string
|
||
wechatPayPublicKeyFilePath string
|
||
privateKey *rsa.PrivateKey
|
||
wechatPayPublicKey *rsa.PublicKey
|
||
|
||
aesKey string
|
||
}
|
||
|
||
// MchId 商户号
|
||
func (c *MchConfig) MchId() string {
|
||
return c.mchId
|
||
}
|
||
|
||
// CertificateSerialNo 商户API证书序列号
|
||
func (c *MchConfig) CertificateSerialNo() string {
|
||
return c.certificateSerialNo
|
||
}
|
||
|
||
// PrivateKey 商户API证书对应的私钥
|
||
func (c *MchConfig) PrivateKey() *rsa.PrivateKey {
|
||
return c.privateKey
|
||
}
|
||
|
||
// WechatPayPublicKeyId 微信支付公钥ID
|
||
func (c *MchConfig) WechatPayPublicKeyId() string {
|
||
return c.wechatPayPublicKeyId
|
||
}
|
||
|
||
// WechatPayPublicKey 微信支付公钥
|
||
func (c *MchConfig) WechatPayPublicKey() *rsa.PublicKey {
|
||
return c.wechatPayPublicKey
|
||
}
|
||
|
||
// CreateMchConfig MchConfig 构造函数
|
||
func CreateMchConfig(
|
||
mchId string,
|
||
certificateSerialNo string,
|
||
privateKeyFilePath string,
|
||
wechatPayPublicKeyId string,
|
||
wechatPayPublicKeyFilePath string,
|
||
aesKey string,
|
||
) (*MchConfig, error) {
|
||
mchConfig := &MchConfig{
|
||
mchId: mchId,
|
||
certificateSerialNo: certificateSerialNo,
|
||
privateKeyFilePath: privateKeyFilePath,
|
||
wechatPayPublicKeyId: wechatPayPublicKeyId,
|
||
wechatPayPublicKeyFilePath: wechatPayPublicKeyFilePath,
|
||
aesKey: aesKey,
|
||
}
|
||
privateKey, err := LoadPrivateKeyWithPath(mchConfig.privateKeyFilePath)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
mchConfig.privateKey = privateKey
|
||
wechatPayPublicKey, err := LoadPublicKeyWithPath(mchConfig.wechatPayPublicKeyFilePath)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
mchConfig.wechatPayPublicKey = wechatPayPublicKey
|
||
return mchConfig, nil
|
||
}
|
||
|
||
// LoadPrivateKey 通过私钥的文本内容加载私钥
|
||
func LoadPrivateKey(privateKeyStr string) (privateKey *rsa.PrivateKey, err error) {
|
||
block, _ := pem.Decode([]byte(privateKeyStr))
|
||
if block == nil {
|
||
return nil, fmt.Errorf("decode private key err")
|
||
}
|
||
if block.Type != "PRIVATE KEY" {
|
||
return nil, fmt.Errorf("the kind of PEM should be PRVATE KEY")
|
||
}
|
||
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("parse private key err:%s", err.Error())
|
||
}
|
||
privateKey, ok := key.(*rsa.PrivateKey)
|
||
if !ok {
|
||
return nil, fmt.Errorf("not a RSA private key")
|
||
}
|
||
return privateKey, nil
|
||
}
|
||
|
||
// LoadPublicKey 通过公钥的文本内容加载公钥
|
||
func LoadPublicKey(publicKeyStr string) (publicKey *rsa.PublicKey, err error) {
|
||
block, _ := pem.Decode([]byte(publicKeyStr))
|
||
if block == nil {
|
||
return nil, errors.New("decode public key error")
|
||
}
|
||
if block.Type != "PUBLIC KEY" {
|
||
return nil, fmt.Errorf("the kind of PEM should be PUBLIC KEY")
|
||
}
|
||
key, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("parse public key err:%s", err.Error())
|
||
}
|
||
publicKey, ok := key.(*rsa.PublicKey)
|
||
if !ok {
|
||
return nil, fmt.Errorf("%s is not rsa public key", publicKeyStr)
|
||
}
|
||
return publicKey, nil
|
||
}
|
||
|
||
// LoadPrivateKeyWithPath 通过私钥的文件路径内容加载私钥
|
||
func LoadPrivateKeyWithPath(path string) (privateKey *rsa.PrivateKey, err error) {
|
||
privateKeyBytes, err := os.ReadFile(path)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("read private pem file err:%s", err.Error())
|
||
}
|
||
return LoadPrivateKey(string(privateKeyBytes))
|
||
}
|
||
|
||
// LoadPublicKeyWithPath 通过公钥的文件路径加载公钥
|
||
func LoadPublicKeyWithPath(path string) (publicKey *rsa.PublicKey, err error) {
|
||
publicKeyBytes, err := os.ReadFile(path)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("read certificate pem file err:%s", err.Error())
|
||
}
|
||
return LoadPublicKey(string(publicKeyBytes))
|
||
}
|
||
|
||
// EncryptOAEPWithPublicKey 使用 OAEP padding方式用公钥进行加密
|
||
func EncryptOAEPWithPublicKey(message string, publicKey *rsa.PublicKey) (ciphertext string, err error) {
|
||
if publicKey == nil {
|
||
return "", fmt.Errorf("you should input *rsa.PublicKey")
|
||
}
|
||
ciphertextByte, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, publicKey, []byte(message), nil)
|
||
if err != nil {
|
||
return "", fmt.Errorf("encrypt message with public key err:%s", err.Error())
|
||
}
|
||
ciphertext = base64.StdEncoding.EncodeToString(ciphertextByte)
|
||
return ciphertext, nil
|
||
}
|
||
|
||
// SignSHA256WithRSA 通过私钥对字符串以 SHA256WithRSA 算法生成签名信息
|
||
func SignSHA256WithRSA(source string, privateKey *rsa.PrivateKey) (signature string, err error) {
|
||
if privateKey == nil {
|
||
return "", fmt.Errorf("private key should not be nil")
|
||
}
|
||
h := crypto.Hash.New(crypto.SHA256)
|
||
_, err = h.Write([]byte(source))
|
||
if err != nil {
|
||
return "", nil
|
||
}
|
||
hashed := h.Sum(nil)
|
||
signatureByte, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return base64.StdEncoding.EncodeToString(signatureByte), nil
|
||
}
|
||
|
||
// VerifySHA256WithRSA 通过公钥对字符串和签名结果以 SHA256WithRSA 验证签名有效性
|
||
func VerifySHA256WithRSA(source string, signature string, publicKey *rsa.PublicKey) error {
|
||
if publicKey == nil {
|
||
return fmt.Errorf("public key should not be nil")
|
||
}
|
||
|
||
sigBytes, err := base64.StdEncoding.DecodeString(signature)
|
||
if err != nil {
|
||
return fmt.Errorf("verify failed: signature is not base64 encoded")
|
||
}
|
||
hashed := sha256.Sum256([]byte(source))
|
||
err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashed[:], sigBytes)
|
||
if err != nil {
|
||
return fmt.Errorf("verify signature with public key error:%s", err.Error())
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// GenerateNonce 生成一个长度为 NonceLength 的随机字符串(只包含大小写字母与数字)
|
||
func GenerateNonce() (string, error) {
|
||
const (
|
||
// NonceSymbols 随机字符串可用字符集
|
||
NonceSymbols = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||
// NonceLength 随机字符串的长度
|
||
NonceLength = 32
|
||
)
|
||
|
||
bs := make([]byte, NonceLength)
|
||
_, err := rand.Read(bs)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
symbolsByteLength := byte(len(NonceSymbols))
|
||
for i, b := range bs {
|
||
bs[i] = NonceSymbols[b%symbolsByteLength]
|
||
}
|
||
return string(bs), nil
|
||
}
|
||
|
||
// BuildAuthorization 构建请求头中的 Authorization 信息
|
||
func BuildAuthorization(
|
||
mchid string,
|
||
certificateSerialNo string,
|
||
privateKey *rsa.PrivateKey,
|
||
method string,
|
||
canonicalURL string,
|
||
body []byte,
|
||
) (string, error) {
|
||
const (
|
||
SignatureMessageFormat = "%s\n%s\n%d\n%s\n%s\n" // 数字签名原文格式
|
||
// HeaderAuthorizationFormat 请求头中的 Authorization 拼接格式
|
||
HeaderAuthorizationFormat = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\""
|
||
)
|
||
|
||
nonce, err := GenerateNonce()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
timestamp := time.Now().Unix()
|
||
message := fmt.Sprintf(SignatureMessageFormat, method, canonicalURL, timestamp, nonce, body)
|
||
signature, err := SignSHA256WithRSA(message, privateKey)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
authorization := fmt.Sprintf(
|
||
HeaderAuthorizationFormat,
|
||
mchid, nonce, timestamp, certificateSerialNo, signature,
|
||
)
|
||
return authorization, nil
|
||
}
|
||
|
||
// ExtractResponseBody 提取应答报文的 Body
|
||
func ExtractResponseBody(response *http.Response) ([]byte, error) {
|
||
if response.Body == nil {
|
||
return nil, nil
|
||
}
|
||
|
||
body, err := io.ReadAll(response.Body)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("read response HttpBody err:[%s]", err.Error())
|
||
}
|
||
response.Body = io.NopCloser(bytes.NewBuffer(body))
|
||
return body, nil
|
||
}
|
||
|
||
const (
|
||
WechatPayTimestamp = "Wechatpay-Timestamp" // 微信支付回包时间戳
|
||
WechatPayNonce = "Wechatpay-Nonce" // 微信支付回包随机字符串
|
||
WechatPaySignature = "Wechatpay-Signature" // 微信支付回包签名信息
|
||
WechatPaySerial = "Wechatpay-Serial" // 微信支付回包平台序列号
|
||
RequestID = "Request-Id" // 微信支付回包请求ID
|
||
)
|
||
|
||
// ValidateResponse 验证微信支付回包的签名信息
|
||
func ValidateResponse(
|
||
wechatpayPublicKeyId string,
|
||
wechatpayPublicKey *rsa.PublicKey,
|
||
headers *http.Header,
|
||
body []byte,
|
||
) error {
|
||
requestID := headers.Get(RequestID)
|
||
timestampStr := headers.Get(WechatPayTimestamp)
|
||
serialNo := headers.Get(WechatPaySerial)
|
||
signature := headers.Get(WechatPaySignature)
|
||
nonce := headers.Get(WechatPayNonce)
|
||
|
||
// 拒绝过期请求
|
||
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
|
||
if err != nil {
|
||
return fmt.Errorf("invalid timestamp: %v", err)
|
||
}
|
||
if time.Now().Sub(time.Unix(timestamp, 0)) > 5*time.Minute {
|
||
return errors.New("invalid timestamp")
|
||
}
|
||
|
||
if serialNo != wechatpayPublicKeyId {
|
||
return fmt.Errorf(
|
||
"serial-no mismatch: got %s, expected %s, request-id: %s",
|
||
serialNo,
|
||
wechatpayPublicKeyId,
|
||
requestID,
|
||
)
|
||
}
|
||
|
||
message := fmt.Sprintf("%s\n%s\n%s\n", timestampStr, nonce, body)
|
||
if err := VerifySHA256WithRSA(message, signature, wechatpayPublicKey); err != nil {
|
||
return fmt.Errorf("invalid signature: %v, request-id: %s", err, requestID)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// ApiException 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
|
||
type ApiException struct {
|
||
HttpStatusCode int // 应答报文的 HTTP 状态码
|
||
HttpHeader http.Header // 应答报文的 Header 信息
|
||
HttpBody []byte // 应答报文的 Body 原文
|
||
ErrCode string // 微信支付回包的错误码
|
||
ErrMessage string // 微信支付回包的错误信息
|
||
}
|
||
|
||
func (c *ApiException) Error() string {
|
||
buf := bytes.NewBuffer(nil)
|
||
buf.WriteString(fmt.Sprintf("srv error:[StatusCode: %d, Body: %s", c.HttpStatusCode, string(c.HttpBody)))
|
||
if len(c.HttpHeader) > 0 {
|
||
buf.WriteString(" Header: ")
|
||
for key, value := range c.HttpHeader {
|
||
buf.WriteString(fmt.Sprintf("\n - %v=%v", key, value))
|
||
}
|
||
buf.WriteString("\n")
|
||
}
|
||
buf.WriteString("]")
|
||
return buf.String()
|
||
}
|
||
|
||
func (c *ApiException) StatusCode() int {
|
||
return c.HttpStatusCode
|
||
}
|
||
|
||
func (c *ApiException) Header() http.Header {
|
||
return c.HttpHeader
|
||
}
|
||
|
||
func (c *ApiException) Body() []byte {
|
||
return c.HttpBody
|
||
}
|
||
|
||
func (c *ApiException) ErrorCode() string {
|
||
return c.ErrCode
|
||
}
|
||
|
||
func (c *ApiException) ErrorMessage() string {
|
||
return c.ErrMessage
|
||
}
|
||
|
||
func NewApiException(statusCode int, header http.Header, body []byte) error {
|
||
ret := &ApiException{
|
||
HttpStatusCode: statusCode,
|
||
HttpHeader: header,
|
||
HttpBody: body,
|
||
}
|
||
|
||
bodyObject := map[string]interface{}{}
|
||
if err := json.Unmarshal(body, &bodyObject); err == nil {
|
||
if val, ok := bodyObject["code"]; ok {
|
||
ret.ErrCode = val.(string)
|
||
}
|
||
if val, ok := bodyObject["message"]; ok {
|
||
ret.ErrMessage = val.(string)
|
||
}
|
||
}
|
||
|
||
return ret
|
||
}
|
||
|
||
// Time 复制 time.Time 对象,并返回复制体的指针
|
||
func Time(t time.Time) *time.Time {
|
||
return &t
|
||
}
|
||
|
||
// String 复制 string 对象,并返回复制体的指针
|
||
func String(s string) *string {
|
||
return &s
|
||
}
|
||
|
||
// Bool 复制 bool 对象,并返回复制体的指针
|
||
func Bool(b bool) *bool {
|
||
return &b
|
||
}
|
||
|
||
// Float64 复制 float64 对象,并返回复制体的指针
|
||
func Float64(f float64) *float64 {
|
||
return &f
|
||
}
|
||
|
||
// Float32 复制 float32 对象,并返回复制体的指针
|
||
func Float32(f float32) *float32 {
|
||
return &f
|
||
}
|
||
|
||
// Int64 复制 int64 对象,并返回复制体的指针
|
||
func Int64(i int64) *int64 {
|
||
return &i
|
||
}
|
||
|
||
// Int32 复制 int64 对象,并返回复制体的指针
|
||
func Int32(i int32) *int32 {
|
||
return &i
|
||
}
|
||
|
||
func (srv *MchConfig) Request(host, method, path string, reqBody []byte) (response []byte, err error) {
|
||
|
||
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
fmt.Print(reqUrl.Path)
|
||
|
||
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
//httpRequest.Header.Set("mchid", srv.mchId)
|
||
httpRequest.Header.Set("Accept", "application/json")
|
||
httpRequest.Header.Set("Wechatpay-Serial", srv.WechatPayPublicKeyId())
|
||
httpRequest.Header.Set("Content-Type", "application/json")
|
||
|
||
authorization, err := BuildAuthorization(
|
||
srv.MchId(),
|
||
srv.CertificateSerialNo(),
|
||
srv.PrivateKey(),
|
||
method,
|
||
reqUrl.Path,
|
||
reqBody,
|
||
)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
httpRequest.Header.Set("Authorization", authorization)
|
||
|
||
client := &http.Client{}
|
||
httpResponse, err := client.Do(httpRequest)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
respBody, err := ExtractResponseBody(httpResponse)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
|
||
// 2XX 成功,验证应答签名
|
||
err = ValidateResponse(
|
||
srv.WechatPayPublicKeyId(),
|
||
srv.WechatPayPublicKey(),
|
||
&httpResponse.Header,
|
||
respBody,
|
||
)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return respBody, nil
|
||
}
|
||
|
||
return nil, &ApiException{
|
||
HttpStatusCode: httpResponse.StatusCode,
|
||
HttpHeader: httpResponse.Header,
|
||
HttpBody: respBody,
|
||
}
|
||
}
|
||
|
||
func (srv *MchConfig) Request2(host, method, path string, reqBody []byte) (response []byte, err error) {
|
||
|
||
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
httpRequest.Header.Set("Accept", "application/json")
|
||
httpRequest.Header.Set("Wechatpay-Serial", srv.WechatPayPublicKeyId())
|
||
httpRequest.Header.Set("Content-Type", "application/json")
|
||
httpRequest.Header.Set("mchid", "application/json")
|
||
|
||
authorization, err := BuildAuthorization(
|
||
srv.MchId(),
|
||
srv.CertificateSerialNo(),
|
||
srv.PrivateKey(),
|
||
method,
|
||
path,
|
||
reqBody,
|
||
)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
httpRequest.Header.Set("Authorization", authorization)
|
||
|
||
client := &http.Client{}
|
||
httpResponse, err := client.Do(httpRequest)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
respBody, err := ExtractResponseBody(httpResponse)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
//hs, _ := json.Marshal(httpRequest.Header)
|
||
//fmt.Printf("\npath=%s\nreqBody=%s\nheaders=%s\n", path, string(reqBody), string(hs))
|
||
//fmt.Printf("\nrespBody=%s\n", string(respBody))
|
||
|
||
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
|
||
// 2XX 成功,验证应答签名
|
||
err = ValidateResponse(
|
||
srv.WechatPayPublicKeyId(),
|
||
srv.WechatPayPublicKey(),
|
||
&httpResponse.Header,
|
||
respBody,
|
||
)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return respBody, nil
|
||
}
|
||
|
||
return nil, &ApiException{
|
||
HttpStatusCode: httpResponse.StatusCode,
|
||
HttpHeader: httpResponse.Header,
|
||
HttpBody: respBody,
|
||
}
|
||
}
|
||
|
||
func (srv *MchConfig) Verify(request *http.Request) (string, error) {
|
||
|
||
respBody, err := io.ReadAll(request.Body)
|
||
if err != nil {
|
||
return "", fmt.Errorf("read request HttpBody err:[%s]", err.Error())
|
||
}
|
||
|
||
err = ValidateResponse(
|
||
srv.WechatPayPublicKeyId(),
|
||
srv.WechatPayPublicKey(),
|
||
&request.Header,
|
||
respBody,
|
||
)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
return EncryptOAEPWithPublicKey(string(respBody), srv.wechatPayPublicKey)
|
||
}
|
||
|
||
func (srv *MchConfig) GetDecodeBody(headers *http.Header, respBody []byte) (*WxNotifyBody, string, error) {
|
||
|
||
if respBody == nil {
|
||
return nil, "", fmt.Errorf("request HttpBody is nil")
|
||
}
|
||
|
||
err := ValidateResponse(
|
||
srv.WechatPayPublicKeyId(),
|
||
srv.WechatPayPublicKey(),
|
||
headers,
|
||
respBody,
|
||
)
|
||
if err != nil {
|
||
return nil, "", err
|
||
}
|
||
|
||
var wxNotifyBody WxNotifyBody
|
||
if err = json.Unmarshal(respBody, &wxNotifyBody); err != nil {
|
||
return nil, "", err
|
||
}
|
||
|
||
aesUtil, err := NewAesUtil(srv.aesKey)
|
||
if err != nil {
|
||
return nil, "", err
|
||
}
|
||
|
||
decryptedText, err := aesUtil.DecryptToString(wxNotifyBody.Resource.AssociatedData, wxNotifyBody.Resource.Nonce, wxNotifyBody.Resource.Ciphertext)
|
||
if err != nil {
|
||
return nil, "", err
|
||
}
|
||
|
||
return &wxNotifyBody, decryptedText, nil
|
||
}
|
||
|
||
// BuildSortedQueryString 函数接受一个 map,返回按照字段名排序后的 URL 键值对格式字符串
|
||
func BuildSortedQueryString(params map[string]any) string {
|
||
// 创建一个字符串切片,用于保存所有的键名
|
||
var keys []string
|
||
for key := range params {
|
||
keys = append(keys, key)
|
||
}
|
||
|
||
// 对键名进行 ASCII 字典顺序排序
|
||
sort.Strings(keys)
|
||
|
||
// 构建一个 URL 键值对字符串
|
||
var queryStrings []string
|
||
for _, key := range keys {
|
||
// 拼接 key=value
|
||
queryStrings = append(queryStrings, fmt.Sprintf("%s=%v", key, params[key]))
|
||
}
|
||
|
||
// 使用 & 连接所有的 key=value 对
|
||
return strings.Join(queryStrings, "&")
|
||
}
|
||
|
||
func Sha1(data string) string {
|
||
// 创建一个 SHA-1 哈希对象
|
||
hash := sha1.New()
|
||
// 写入数据
|
||
hash.Write([]byte(data))
|
||
// 计算并获取加密后的结果
|
||
hashBytes := hash.Sum(nil)
|
||
// 将结果转换为十六进制字符串
|
||
hashString := hex.EncodeToString(hashBytes)
|
||
// 打印加密后的 SHA-1 值
|
||
return hashString
|
||
}
|