修改微信扫码登录 access_token 问题
This commit is contained in:
10
go.mod
10
go.mod
@ -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
4
go.sum
@ -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=
|
||||
|
||||
@ -15,3 +15,7 @@ const (
|
||||
WeChatLoginCache = "wx:login:cache:%s"
|
||||
WeChatLoginLimit = "wx:login:limit:%s"
|
||||
)
|
||||
|
||||
const (
|
||||
WechatAccessTokenKey = "wechat:access_token"
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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