修改微信扫码登录 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

10
go.mod
View File

@ -3,10 +3,15 @@ module server
go 1.24.2
require (
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11
github.com/alibabacloud-go/dysmsapi-20170525/v4 v4.1.3
github.com/alibabacloud-go/tea v1.2.2
github.com/alibabacloud-go/tea-utils/v2 v2.0.6
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/aliyun/credentials-go v1.4.6
github.com/bwmarrin/snowflake v0.3.0
github.com/casbin/casbin/v2 v2.105.0
github.com/eclipse/paho.mqtt.golang v1.5.0
github.com/go-resty/resty/v2 v2.16.5
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.0
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.0
@ -20,14 +25,10 @@ require (
require (
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11 // indirect
github.com/alibabacloud-go/debug v1.0.1 // indirect
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
github.com/alibabacloud-go/openapi-util v0.1.1 // indirect
github.com/alibabacloud-go/tea v1.2.2 // indirect
github.com/alibabacloud-go/tea-utils/v2 v2.0.6 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/credentials-go v1.4.6 // indirect
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
github.com/casbin/govaluate v1.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
@ -57,6 +58,7 @@ require (
go.opentelemetry.io/otel/sdk v1.32.0 // indirect
go.opentelemetry.io/otel/trace v1.32.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.6.0 // indirect

4
go.sum
View File

@ -77,6 +77,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o=
github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -244,6 +246,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -15,3 +15,7 @@ const (
WeChatLoginCache = "wx:login:cache:%s"
WeChatLoginLimit = "wx:login:limit:%s"
)
const (
WechatAccessTokenKey = "wechat:access_token"
)

View File

@ -2,11 +2,14 @@ package storeDesktopSetting
import (
"context"
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/errors/gerror"
"server/internal/dao"
"server/internal/model"
"server/internal/model/do"
"server/internal/service"
"server/utility/mqtt"
)
type sStoreDesktopSetting struct {
@ -60,6 +63,19 @@ func (s *sStoreDesktopSetting) Save(ctx context.Context, in model.SaveDesktopSet
if err != nil {
return nil, err
}
client, b := mqtt.GetClient("emqx")
if !b {
return nil, gerror.New("获取MQTT客户端失败")
}
marshal, err := json.Marshal(in)
if err != nil {
return nil, err
}
err = client.Publish(fmt.Sprintf("/desktop/%d", in.StoreId), marshal)
if err != nil {
return nil, err
}
return &model.SaveDesktopSettingOut{
Success: true,
}, nil

View File

@ -131,7 +131,13 @@ type GiftResponse struct {
}
type gameRoleInfo struct {
// FIXME
RoleIdx string `json:"roleIdx"`
RoleBaseInfo roleBaseInfo `json:"RoleBaseInfo"`
}
type roleBaseInfo struct {
Gid int `json:"gid"`
RoleIdx string `json:"roleidx"`
}
type userAddress struct {
@ -139,13 +145,24 @@ type userAddress struct {
}
type waterExtraInfo struct {
// FIXME
EchangeWaterLog echangeWaterLog `json:"echange_water_log"`
PremiumPrivInfo premiumPrivInfo `json:"premium_priv_info"`
SendOrderQuantity int `json:"send_order_quantity"`
}
type goodsType struct {
// FIXME
type echangeWaterLog struct {
SrcChannel string `json:"src_channel"`
SrcModule string `json:"src_module"`
Source string `json:"source"`
CreateBrandId string `json:"create_brand_id"`
}
type premiumPrivInfo struct {
CustomInfo string `json:"custom_info"` // 字符串 JSON可视需要二次解析
}
type goodsType int64
type goodsDisplayType struct {
// FIXME
}
@ -153,9 +170,44 @@ type goodsDisplayType struct {
type merchantInfo struct {
// FIXME
}
type olCouponCfg struct {
CouponReq couponReq `json:"coupon_req"`
}
type couponReq struct {
ReqContent reqContent `json:"reqContent"`
}
type reqContent struct {
CouponContent couponContent `json:"couponContent"`
}
type couponContent struct {
CouponNum string `json:"couponNum"`
}
type goodsBrandInfo struct {
BrandName string `json:"brand_name"`
JumpStatus int `json:"jump_status"`
BrandId string `json:"brand_id"`
BrandLogo string `json:"brand_logo"`
}
type expireConfig struct {
ExpireDays int `json:"expire_days"`
}
type privCoupon struct {
AppId string `json:"appid"`
PrizeChannelId string `json:"prize_channel_id"`
PrizeId string `json:"prize_id"`
PrizeType int `json:"prize_type"`
Num int `json:"num"`
Url string `json:"url"`
}
type goodsExtraInfo struct {
// FIXME
OlCouponCfg olCouponCfg `json:"olcoupon_cfg"`
GoodsBrandInfo goodsBrandInfo `json:"goods_brand_info"`
ExpireType int `json:"expire_type"`
ExpireConfig expireConfig `json:"expire_config"`
MprocJmpCfg map[string]any `json:"mproc_jmp_cfg"` // 空对象可用 map[string]any 表示
ApplyGoods string `json:"apply_goods"`
NewlyBuilt bool `json:"newly_built"`
PrivCoupon privCoupon `json:"priv_coupon"`
}
type water struct {

View File

@ -19,9 +19,9 @@ type StoreGetDesktopSettingOut struct {
}
type SaveDesktopSettingIn struct {
StoreId int64
TopComponentVisible int // 1显示2 隐藏
RightComponentVisible int // 1显示2 隐藏
StoreId int64 `json:"storeId"`
TopComponentVisible int `json:"topComponentVisible"` // 1显示2 隐藏
RightComponentVisible int `json:"rightComponentVisible"` // 1显示2 隐藏
}
type SaveDesktopSettingOut struct {
Success bool

View File

@ -3,11 +3,12 @@ package packed
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
_ "server/utility/gamelife"
//_ "server/utility/gamelife"
//_ "server/utility/mqtt/emqx"
_ "server/utility/myCasbin"
_ "server/utility/oss/aliyun"
_ "server/utility/rsa"
_ "server/utility/sms/aliyun"
//_ "server/utility/oss/aliyun"
//_ "server/utility/rsa"
//_ "server/utility/sms/aliyun"
_ "server/utility/snowid"
_ "server/utility/wechat"
)

View File

@ -48,3 +48,11 @@ gamelife:
secret: "LqPQ2gbF"
mode: "test"
rsaKey: "./manifest/config/全游-测试环境密钥.txt"
mqtt:
emqx:
host: "127.0.0.1"
clientId: "server"
port: 1883
username: "areanx_server"
password: "areanx_server"

View File

@ -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
View 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
View 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
}

View File

@ -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", "获取游戏列表")

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
}