修改微信扫码登录 access_token 问题

This commit is contained in:
2025-06-23 11:42:08 +08:00
parent 33b040065f
commit 03c7939a81
13 changed files with 346 additions and 60 deletions

View File

@ -2,8 +2,10 @@ package wechat
import (
"context"
"github.com/gogf/gf/v2/encoding/gjson"
"io"
"os"
"server/internal/consts"
"server/utility/ecode"
"sync"
"time"
@ -30,6 +32,35 @@ var (
once sync.Once
)
func (c *weChatClient) loadCachedToken(ctx context.Context) bool {
val, err := g.Redis().Get(ctx, consts.WechatAccessTokenKey)
if err != nil || val.IsEmpty() {
return false
}
var data struct {
Token string `json:"token"`
ExpiresAt int64 `json:"expires_at"`
}
if err := gjson.DecodeTo(val.String(), &data); err != nil {
return false
}
// 如果当前时间超过过期时间 - 5分钟则视为即将过期
if time.Now().Unix() >= data.ExpiresAt-300 {
return false
}
c.mu.Lock()
c.accessToken = data.Token
c.expiresIn = int(data.ExpiresAt - time.Now().Unix())
c.lastUpdated = time.Now()
c.mu.Unlock()
glog.Infof(ctx, "[loadCachedToken] 成功加载 Redis 中的 access_token: %s", data.Token)
return true
}
func GetWeChatClient() *weChatClient {
return instance
}
@ -48,11 +79,15 @@ func init() {
}
func (c *weChatClient) getAccessToken() error {
ctx := context.Background()
result := struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
ExpiresIn int `json:"expires_in"` // 一般为 7200 秒
}{}
ctx := context.Background()
url := "https://api.weixin.qq.com/cgi-bin/token"
resp, err := resty.New().R().
SetQueryParams(g.MapStrStr{
"grant_type": "client_credential",
@ -60,44 +95,79 @@ func (c *weChatClient) getAccessToken() error {
"secret": c.AppSecret,
}).
SetResult(&result).
Get("https://api.weixin.qq.com/cgi-bin/token")
Get(url)
if err != nil {
glog.Errorf(ctx, "发起 get access_token 请求出现异常: %+v", err)
glog.Errorf(ctx, "[getAccessToken] 请求异常, URL: %s, err: %+v", url, err)
return ecode.Fail.Sub("发起 get access_token 请求出现异常")
}
if resp.StatusCode() != 200 {
glog.Errorf(ctx, "获取微信 access_token 响应异常: %+v", resp.Status())
return ecode.Fail.Sub("获取微信 access_token 失败")
if resp.IsError() {
glog.Errorf(ctx, "[getAccessToken] 响应错误, 状态: %s, 内容: %s", resp.Status(), resp.String())
return ecode.Fail.Sub("获取 access_token 失败")
}
// 写入内存
c.mu.Lock()
defer c.mu.Unlock()
c.accessToken = result.AccessToken
c.expiresIn = result.ExpiresIn
c.lastUpdated = time.Now()
glog.Infof(ctx, "获取微信 access_token 成功: %+v", c.accessToken)
c.mu.Unlock()
// 写入 Redis
expiresAt := time.Now().Unix() + int64(result.ExpiresIn)
cacheData := g.Map{
"token": result.AccessToken,
"expires_at": expiresAt,
}
jsonStr, _ := gjson.Encode(cacheData)
ttl := result.ExpiresIn - 300
if ttl < 60 {
ttl = 60
}
err = g.Redis().SetEX(ctx, consts.WechatAccessTokenKey, jsonStr, int64(ttl))
if err != nil {
glog.Warningf(ctx, "[getAccessToken] Redis 缓存失败: %+v", err)
} else {
glog.Infof(ctx, "[getAccessToken] 成功写入 Redisexpires_in: %ds", result.ExpiresIn)
}
glog.Infof(ctx, "[getAccessToken] 成功获取 access_token: %sexpires_in: %ds", result.AccessToken, result.ExpiresIn)
return nil
}
func (c *weChatClient) autoRefreshToken(ctx context.Context) {
for {
// 初次获取
if err := c.getAccessToken(); err != nil {
time.Sleep(1 * time.Minute)
continue
}
// 等待 expiresIn - 300 秒(即提前 5 分钟刷新)
func (c *weChatClient) autoRefreshToken(ctx context.Context) {
if c.loadCachedToken(ctx) {
glog.Infof(ctx, "[autoRefreshToken] 成功加载缓存 token")
} else {
if err := c.getAccessToken(); err != nil {
glog.Errorf(ctx, "[autoRefreshToken] 初次获取 token 失败: %+v", err)
time.Sleep(1 * time.Minute)
}
}
for {
c.mu.RLock()
expiresIn := c.expiresIn
c.mu.RUnlock()
refreshAfter := time.Duration(expiresIn-300) * time.Second
if refreshAfter <= 0 {
refreshAfter = 5 * time.Minute
}
time.Sleep(refreshAfter)
if err := c.getAccessToken(); err != nil {
glog.Errorf(ctx, "[autoRefreshToken] 刷新 token 失败: %+v", err)
time.Sleep(1 * time.Minute)
}
}
}
func (c *weChatClient) GetTicket(sceneId string) (string, error) {
ctx := context.Background()
body := map[string]interface{}{
"expire_seconds": c.TicketExpire,
"action_name": "QR_STR_SCENE",
@ -120,49 +190,51 @@ func (c *weChatClient) GetTicket(sceneId string) (string, error) {
Post("https://api.weixin.qq.com/cgi-bin/qrcode/create")
if err != nil {
glog.Errorf(context.Background(), "发起 get ticket 请求出现异常: %+v", err)
glog.Errorf(ctx, "[GetTicket] 请求异常: %+v", err)
return "", ecode.Fail.Sub("发起 get ticket 请求出现异常")
}
if resp.StatusCode() != 200 {
glog.Errorf(context.Background(), "获取微信 ticket 响应异常: %+v", resp.Status())
if resp.IsError() {
glog.Errorf(ctx, "[GetTicket] 响应错误,状态: %s内容: %s", resp.Status(), resp.String())
return "", ecode.Fail.Sub("获取微信 ticket 失败")
}
glog.Infof(context.Background(), "获取微信 ticket 成功: %+v", result.Ticket)
glog.Infof(ctx, "[GetTicket] 成功获取 ticket: %s, 过期时间: %ds", result.Ticket, result.ExpireSeconds)
return result.Ticket, nil
}
func (c *weChatClient) GetQrCode(ticket string) (imagePath string, err error) {
qrCodeUrl := "https://mp.weixin.qq.com/cgi-bin/showqrcode"
func (c *weChatClient) GetQrCode(ticket string) (string, error) {
ctx := context.Background()
url := "https://mp.weixin.qq.com/cgi-bin/showqrcode"
resp, err := resty.New().R().
SetDoNotParseResponse(true).
SetQueryParams(g.MapStrStr{"ticket": ticket}).
Get(qrCodeUrl)
Get(url)
if err != nil {
glog.Errorf(context.Background(), "获取二维码图片请求异常: %+v", err)
glog.Errorf(ctx, "[GetQrCode] 请求失败: %+v", err)
return "", ecode.Fail.Sub("获取二维码图片请求失败")
}
if resp.StatusCode() != 200 {
glog.Errorf(context.Background(), "获取二维码图片响应异常: %+v", resp.Status())
if resp.IsError() {
glog.Errorf(ctx, "[GetQrCode] 响应异常, 状态: %s", resp.Status())
return "", ecode.Fail.Sub("获取二维码图片失败")
}
imagePath = uuid.New().String() + ".jpg"
imagePath := uuid.New().String() + ".jpg"
data, readErr := io.ReadAll(resp.RawBody())
defer resp.RawBody().Close()
if readErr != nil {
glog.Errorf(context.Background(), "读取二维码图片失败: %+v", readErr)
glog.Errorf(ctx, "[GetQrCode] 读取图片失败: %+v", readErr)
return "", ecode.Fail.Sub("读取二维码图片失败")
}
defer resp.RawBody().Close()
writeErr := os.WriteFile(imagePath, data, 0644)
if writeErr != nil {
glog.Errorf(context.Background(), "保存二维码图片失败: %+v", writeErr)
glog.Errorf(ctx, "[GetQrCode] 保存图片失败: %+v", writeErr)
return "", ecode.Fail.Sub("保存二维码图片失败")
}
glog.Infof(context.Background(), "二维码图片保存成功: %s", imagePath)
glog.Infof(ctx, "[GetQrCode] 二维码保存成功: %s", imagePath)
return imagePath, nil
}