186 lines
4.5 KiB
Go
186 lines
4.5 KiB
Go
|
|
package user
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"crypto/sha1"
|
|||
|
|
"encoding/json"
|
|||
|
|
"fmt"
|
|||
|
|
"io"
|
|||
|
|
"net/http"
|
|||
|
|
"net/url"
|
|||
|
|
"sort"
|
|||
|
|
"strings"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"hm-server/app/main/api/internal/svc"
|
|||
|
|
"hm-server/app/main/api/internal/types"
|
|||
|
|
"hm-server/common/xerr"
|
|||
|
|
|
|||
|
|
"github.com/pkg/errors"
|
|||
|
|
"github.com/zeromicro/go-zero/core/logx"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
type GetSignatureLogic struct {
|
|||
|
|
logx.Logger
|
|||
|
|
ctx context.Context
|
|||
|
|
svcCtx *svc.ServiceContext
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func NewGetSignatureLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSignatureLogic {
|
|||
|
|
return &GetSignatureLogic{
|
|||
|
|
Logger: logx.WithContext(ctx),
|
|||
|
|
ctx: ctx,
|
|||
|
|
svcCtx: svcCtx,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (l *GetSignatureLogic) GetSignature(req *types.GetSignatureReq) (resp *types.GetSignatureResp, err error) {
|
|||
|
|
// 1. 获取access_token
|
|||
|
|
accessToken, err := l.getAccessToken()
|
|||
|
|
if err != nil {
|
|||
|
|
l.Errorf("获取access_token失败: %v", err)
|
|||
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取access_token失败: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 获取jsapi_ticket
|
|||
|
|
jsapiTicket, err := l.getJsapiTicket(accessToken)
|
|||
|
|
if err != nil {
|
|||
|
|
l.Errorf("获取jsapi_ticket失败: %v", err)
|
|||
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取jsapi_ticket失败: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 生成签名
|
|||
|
|
timestamp := time.Now().Unix()
|
|||
|
|
nonceStr := l.generateNonceStr(16)
|
|||
|
|
signature := l.generateSignature(jsapiTicket, nonceStr, timestamp, req.Url)
|
|||
|
|
|
|||
|
|
// 4. 返回完整的JS-SDK配置信息
|
|||
|
|
return &types.GetSignatureResp{
|
|||
|
|
AppId: l.svcCtx.Config.WechatH5.AppID,
|
|||
|
|
Timestamp: timestamp,
|
|||
|
|
NonceStr: nonceStr,
|
|||
|
|
Signature: signature,
|
|||
|
|
}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// getAccessToken 获取微信公众号access_token
|
|||
|
|
func (l *GetSignatureLogic) getAccessToken() (string, error) {
|
|||
|
|
appID := l.svcCtx.Config.WechatH5.AppID
|
|||
|
|
appSecret := l.svcCtx.Config.WechatH5.AppSecret
|
|||
|
|
|
|||
|
|
url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appID, appSecret)
|
|||
|
|
|
|||
|
|
resp, err := http.Get(url)
|
|||
|
|
if err != nil {
|
|||
|
|
return "", err
|
|||
|
|
}
|
|||
|
|
defer resp.Body.Close()
|
|||
|
|
|
|||
|
|
body, err := io.ReadAll(resp.Body)
|
|||
|
|
if err != nil {
|
|||
|
|
return "", err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var result struct {
|
|||
|
|
AccessToken string `json:"access_token"`
|
|||
|
|
ExpiresIn int `json:"expires_in"`
|
|||
|
|
ErrCode int `json:"errcode"`
|
|||
|
|
ErrMsg string `json:"errmsg"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if err = json.Unmarshal(body, &result); err != nil {
|
|||
|
|
return "", err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if result.ErrCode != 0 {
|
|||
|
|
return "", fmt.Errorf("获取access_token失败: errcode=%d, errmsg=%s", result.ErrCode, result.ErrMsg)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result.AccessToken, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// getJsapiTicket 获取jsapi_ticket
|
|||
|
|
func (l *GetSignatureLogic) getJsapiTicket(accessToken string) (string, error) {
|
|||
|
|
url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi", accessToken)
|
|||
|
|
|
|||
|
|
resp, err := http.Get(url)
|
|||
|
|
if err != nil {
|
|||
|
|
return "", err
|
|||
|
|
}
|
|||
|
|
defer resp.Body.Close()
|
|||
|
|
|
|||
|
|
body, err := io.ReadAll(resp.Body)
|
|||
|
|
if err != nil {
|
|||
|
|
return "", err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var result struct {
|
|||
|
|
Ticket string `json:"ticket"`
|
|||
|
|
ExpiresIn int `json:"expires_in"`
|
|||
|
|
ErrCode int `json:"errcode"`
|
|||
|
|
ErrMsg string `json:"errmsg"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if err = json.Unmarshal(body, &result); err != nil {
|
|||
|
|
return "", err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if result.ErrCode != 0 {
|
|||
|
|
return "", fmt.Errorf("获取jsapi_ticket失败: errcode=%d, errmsg=%s", result.ErrCode, result.ErrMsg)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result.Ticket, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// generateNonceStr 生成随机字符串
|
|||
|
|
func (l *GetSignatureLogic) generateNonceStr(length int) string {
|
|||
|
|
chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|||
|
|
result := make([]byte, length)
|
|||
|
|
for i := 0; i < length; i++ {
|
|||
|
|
result[i] = chars[i%len(chars)]
|
|||
|
|
}
|
|||
|
|
return string(result)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// generateSignature 生成签名
|
|||
|
|
func (l *GetSignatureLogic) generateSignature(jsapiTicket, nonceStr string, timestamp int64, urlStr string) string {
|
|||
|
|
// 对URL进行解码,避免重复编码
|
|||
|
|
decodedURL, err := url.QueryUnescape(urlStr)
|
|||
|
|
if err != nil {
|
|||
|
|
decodedURL = urlStr
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 构建签名字符串
|
|||
|
|
params := map[string]string{
|
|||
|
|
"jsapi_ticket": jsapiTicket,
|
|||
|
|
"noncestr": nonceStr,
|
|||
|
|
"timestamp": fmt.Sprintf("%d", timestamp),
|
|||
|
|
"url": decodedURL,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 对参数进行字典序排序
|
|||
|
|
keys := make([]string, 0, len(params))
|
|||
|
|
for k := range params {
|
|||
|
|
keys = append(keys, k)
|
|||
|
|
}
|
|||
|
|
sort.Strings(keys)
|
|||
|
|
|
|||
|
|
// 拼接字符串
|
|||
|
|
var signStr strings.Builder
|
|||
|
|
for i, k := range keys {
|
|||
|
|
if i > 0 {
|
|||
|
|
signStr.WriteString("&")
|
|||
|
|
}
|
|||
|
|
signStr.WriteString(k)
|
|||
|
|
signStr.WriteString("=")
|
|||
|
|
signStr.WriteString(params[k])
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SHA1加密
|
|||
|
|
h := sha1.New()
|
|||
|
|
h.Write([]byte(signStr.String()))
|
|||
|
|
signature := fmt.Sprintf("%x", h.Sum(nil))
|
|||
|
|
|
|||
|
|
return signature
|
|||
|
|
}
|