修改微信扫码登录 access_token 问题
This commit is contained in:
@ -178,7 +178,7 @@ func (s *gamelifeClient) GetUrl(ctx context.Context, popenID, appName, nickname
|
||||
if !isBound {
|
||||
rootURL = s.unBoundURLMap[s.config.Mode]
|
||||
}
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, popenID, appName, nickname, bindType)
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, popenID, appName, nickname, bindType, isBound)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -319,7 +319,7 @@ func (s *gamelifeClient) RequestActivity(ctx context.Context, in *model.QQNetbar
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("获取游戏编码失败")
|
||||
}
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, in.PopenId, value.String(), in.NickName, in.BindType)
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, in.PopenId, value.String(), in.NickName, in.BindType, true)
|
||||
in.TaskParam.BrandId = s.config.BrandID
|
||||
var result model.GameTaskResponse
|
||||
resp, err := client.R().
|
||||
@ -332,11 +332,11 @@ func (s *gamelifeClient) RequestActivity(ctx context.Context, in *model.QQNetbar
|
||||
}
|
||||
return &result, nil
|
||||
case consts.QueryUserRoleList:
|
||||
value, err := dao.Games.Ctx(ctx).Where(do.Games{GameId: in.QueryUserGoodsDetailParam.Gid}).Fields(dao.Games.Columns().GameCode).Value()
|
||||
value, err := dao.Games.Ctx(ctx).Where(do.Games{GameId: in.UserRoleParam.Gid}).Fields(dao.Games.Columns().GameCode).Value()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("获取游戏编码失败")
|
||||
}
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, in.PopenId, value.String(), in.NickName, in.BindType)
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, in.PopenId, value.String(), in.NickName, in.BindType, true)
|
||||
var result model.UserRoleListResponse
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
@ -346,13 +346,14 @@ func (s *gamelifeClient) RequestActivity(ctx context.Context, in *model.QQNetbar
|
||||
if err != nil || resp.IsError() {
|
||||
return nil, ecode.Fail.Sub("请求出现异常")
|
||||
}
|
||||
|
||||
return &result.RoleList, nil
|
||||
case consts.GetGift:
|
||||
value, err := dao.Games.Ctx(ctx).Where(do.Games{GameId: in.QueryUserGoodsDetailParam.Gid}).Fields(dao.Games.Columns().GameCode).Value()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("获取游戏编码失败")
|
||||
}
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, in.PopenId, value.String(), in.NickName, in.BindType)
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, in.PopenId, value.String(), in.NickName, in.BindType, true)
|
||||
var result model.GiftResponse
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
@ -368,7 +369,7 @@ func (s *gamelifeClient) RequestActivity(ctx context.Context, in *model.QQNetbar
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("获取游戏编码失败")
|
||||
}
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, in.PopenId, value.String(), in.NickName, in.BindType)
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, in.PopenId, value.String(), in.NickName, in.BindType, true)
|
||||
var result model.GoodsResponse
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
@ -384,7 +385,7 @@ func (s *gamelifeClient) RequestActivity(ctx context.Context, in *model.QQNetbar
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("获取游戏编码失败")
|
||||
}
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, in.PopenId, value.String(), in.NickName, in.BindType)
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, in.PopenId, value.String(), in.NickName, in.BindType, true)
|
||||
var result model.ExchangeGoodsResponse
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
@ -400,7 +401,7 @@ func (s *gamelifeClient) RequestActivity(ctx context.Context, in *model.QQNetbar
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("获取游戏编码失败")
|
||||
}
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, in.PopenId, value.String(), in.NickName, in.BindType)
|
||||
cache, err := s.ensureUserCacheWithParams(ctx, in.PopenId, value.String(), in.NickName, in.BindType, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -421,7 +422,7 @@ func (s *gamelifeClient) RequestActivity(ctx context.Context, in *model.QQNetbar
|
||||
|
||||
// ensureUserCacheWithParams ensures user cache exists and contains a valid Params string.
|
||||
// It handles cache retrieval, fallback refresh, Params generation, and cache update.
|
||||
func (s *gamelifeClient) ensureUserCacheWithParams(ctx context.Context, popenId, appname, nickname string, bindType int) (*model.UserGamelifeCache, error) {
|
||||
func (s *gamelifeClient) ensureUserCacheWithParams(ctx context.Context, popenId, appname, nickname string, bindType int, isBound bool) (*model.UserGamelifeCache, error) {
|
||||
cacheKey := fmt.Sprintf(consts.GameLifeUserKey, popenId)
|
||||
cacheData, err := g.Redis().Get(ctx, cacheKey)
|
||||
if err != nil {
|
||||
@ -441,7 +442,7 @@ func (s *gamelifeClient) ensureUserCacheWithParams(ctx context.Context, popenId,
|
||||
}
|
||||
|
||||
if cache.Params == "" {
|
||||
cache.Params, err = s.buildQueryParams(ctx, popenId, cache, appname, nickname, bindType, true)
|
||||
cache.Params, err = s.buildQueryParams(ctx, popenId, cache, appname, nickname, bindType, isBound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
102
utility/mqtt/emqx/emqx.go
Normal file
102
utility/mqtt/emqx/emqx.go
Normal file
@ -0,0 +1,102 @@
|
||||
package emqx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
mqttlib "github.com/eclipse/paho.mqtt.golang"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"server/utility/mqtt"
|
||||
)
|
||||
|
||||
type emqxClient struct {
|
||||
client mqttlib.Client
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// New 创建并连接 EMQX 客户端实例,连接成功才返回
|
||||
func New(broker, clientID, username, password string) mqtt.MqttClient {
|
||||
opts := mqttlib.NewClientOptions().
|
||||
AddBroker(broker).
|
||||
SetClientID(clientID).
|
||||
SetUsername(username).
|
||||
SetPassword(password).
|
||||
SetAutoReconnect(true).
|
||||
SetConnectTimeout(5 * time.Second)
|
||||
|
||||
c := &emqxClient{
|
||||
client: mqttlib.NewClient(opts),
|
||||
}
|
||||
|
||||
// 立即连接
|
||||
err := c.connect()
|
||||
if err != nil {
|
||||
glog.Errorf(context.Background(), "连接EMQX失败: %v", err)
|
||||
panic(fmt.Sprintf("连接EMQX失败: %v", err))
|
||||
}
|
||||
glog.Infof(context.Background(), "EMQX客户端连接成功")
|
||||
return c
|
||||
}
|
||||
|
||||
// connect 内部只调用一次连接
|
||||
func (e *emqxClient) connect() error {
|
||||
var err error
|
||||
e.once.Do(func() {
|
||||
token := e.client.Connect()
|
||||
if token.Wait() && token.Error() != nil {
|
||||
err = fmt.Errorf("EMQX连接失败: %w", token.Error())
|
||||
glog.Errorf(context.Background(), err.Error())
|
||||
return
|
||||
}
|
||||
glog.Infof(context.Background(), "EMQX连接成功")
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Publish 实现接口 Publish
|
||||
func (e *emqxClient) Publish(topic string, payload []byte) error {
|
||||
token := e.client.Publish(topic, 0, false, payload)
|
||||
token.Wait()
|
||||
err := token.Error()
|
||||
if err != nil {
|
||||
glog.Errorf(context.Background(), "发布消息失败,topic=%s,错误:%v", topic, err)
|
||||
} else {
|
||||
glog.Infof(context.Background(), "成功发布消息,topic=%s,消息大小=%d字节", topic, len(payload))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Subscribe 实现接口 Subscribe
|
||||
func (e *emqxClient) Subscribe(topic string, handler func(topic string, payload []byte)) error {
|
||||
token := e.client.Subscribe(topic, 0, func(client mqttlib.Client, msg mqttlib.Message) {
|
||||
handler(msg.Topic(), msg.Payload())
|
||||
})
|
||||
token.Wait()
|
||||
err := token.Error()
|
||||
if err != nil {
|
||||
glog.Errorf(context.Background(), "订阅失败,topic=%s,错误:%v", topic, err)
|
||||
} else {
|
||||
glog.Infof(context.Background(), "成功订阅主题,topic=%s", topic)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// init 注册emqx客户端
|
||||
func init() {
|
||||
ctx := context.Background()
|
||||
cfg := g.Config()
|
||||
host := cfg.MustGet(ctx, "mqtt.emqx.host").String()
|
||||
port := cfg.MustGet(ctx, "mqtt.emqx.port").Int()
|
||||
username := cfg.MustGet(ctx, "mqtt.emqx.username").String()
|
||||
password := cfg.MustGet(ctx, "mqtt.emqx.password").String()
|
||||
clientId := cfg.MustGet(ctx, "mqtt.emqx.clientId").String()
|
||||
|
||||
broker := fmt.Sprintf("tcp://%s:%d", host, port)
|
||||
client := New(broker, clientId, username, password)
|
||||
|
||||
mqtt.Register("emqx", client)
|
||||
glog.Infof(ctx, "EMQX客户端注册完成,broker=%s, clientID=%s", broker, clientId)
|
||||
}
|
||||
23
utility/mqtt/mqtt.go
Normal file
23
utility/mqtt/mqtt.go
Normal file
@ -0,0 +1,23 @@
|
||||
package mqtt
|
||||
|
||||
// MqttClient 定义了通用 MQTT 客户端接口
|
||||
type MqttClient interface {
|
||||
// Publish 发布消息到指定 topic
|
||||
Publish(topic string, payload []byte) error
|
||||
// Subscribe 订阅指定 topic,接收回调
|
||||
Subscribe(topic string, handler func(topic string, payload []byte)) error
|
||||
}
|
||||
|
||||
// 全局注册表
|
||||
var clients = make(map[string]MqttClient)
|
||||
|
||||
// Register 注册一个 MQTT 客户端实现
|
||||
func Register(name string, client MqttClient) {
|
||||
clients[name] = client
|
||||
}
|
||||
|
||||
// GetClient 获取已注册的客户端
|
||||
func GetClient(name string) (MqttClient, bool) {
|
||||
client, ok := clients[name]
|
||||
return client, ok
|
||||
}
|
||||
@ -43,6 +43,7 @@ func init() {
|
||||
// 任务
|
||||
enforcer.AddPolicy("guest", "/x/task/ranking", "GET", "获取排行榜")
|
||||
enforcer.AddPolicy("guest", "/x/task/getNonLoginTaskList", "GET", "未登录获取任务列表")
|
||||
enforcer.AddPolicy("guest", "/x/reward/callback", "POST", "")
|
||||
|
||||
// 游戏列表
|
||||
enforcer.AddPolicy("guest", "/x/game", "GET", "获取游戏列表")
|
||||
|
||||
@ -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] 成功写入 Redis,expires_in: %ds", result.ExpiresIn)
|
||||
}
|
||||
|
||||
glog.Infof(ctx, "[getAccessToken] 成功获取 access_token: %s,expires_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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user