166 lines
4.4 KiB
Go
166 lines
4.4 KiB
Go
package wechat
|
|
|
|
import (
|
|
"context"
|
|
"github.com/go-resty/resty/v2"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/os/glog"
|
|
"io"
|
|
"os"
|
|
"server/utility/ecode"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type weChatClient struct {
|
|
AppId string
|
|
AppSecret string
|
|
TicketExpire int
|
|
Token string
|
|
accessToken string
|
|
expiresIn int
|
|
lastUpdated time.Time
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
var (
|
|
instance *weChatClient
|
|
once sync.Once
|
|
)
|
|
|
|
func GetWeChatClient() *weChatClient {
|
|
return instance
|
|
}
|
|
func init() {
|
|
once.Do(func() {
|
|
ctx := context.Background()
|
|
instance = &weChatClient{
|
|
AppId: g.Config().MustGet(ctx, "wechat.appId").String(),
|
|
AppSecret: g.Config().MustGet(ctx, "wechat.appSecret").String(),
|
|
TicketExpire: g.Config().MustGet(ctx, "wechat.ticketExpire").Int(),
|
|
}
|
|
|
|
go instance.autoRefreshToken(ctx)
|
|
})
|
|
}
|
|
|
|
func (c *weChatClient) getAccessToken() error {
|
|
result := struct {
|
|
AccessToken string `json:"access_token"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
}{}
|
|
ctx := context.Background()
|
|
resp, err := resty.New().R().
|
|
SetQueryParams(g.MapStrStr{
|
|
"grant_type": "client_credential",
|
|
"appid": c.AppId,
|
|
"secret": c.AppSecret,
|
|
}).
|
|
SetResult(&result).
|
|
Get("https://api.weixin.qq.com/cgi-bin/token")
|
|
|
|
if err != nil {
|
|
glog.Errorf(ctx, "发起 get access_token 请求出现异常: %+v", 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 失败")
|
|
}
|
|
|
|
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)
|
|
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 分钟刷新)
|
|
c.mu.RLock()
|
|
expiresIn := c.expiresIn
|
|
c.mu.RUnlock()
|
|
|
|
refreshAfter := time.Duration(expiresIn-300) * time.Second
|
|
time.Sleep(refreshAfter)
|
|
}
|
|
}
|
|
func (c *weChatClient) GetTicket(sceneID string) (string, error) {
|
|
body := map[string]interface{}{
|
|
"expire_seconds": c.TicketExpire,
|
|
"action_name": "QR_SCENE",
|
|
"action_info": map[string]interface{}{
|
|
"scene": map[string]interface{}{
|
|
"scene_str": sceneID,
|
|
},
|
|
},
|
|
}
|
|
result := struct {
|
|
Ticket string `json:"ticket"`
|
|
ExpireSeconds int `json:"expire_seconds"`
|
|
Url string `json:"url"`
|
|
}{}
|
|
|
|
resp, err := resty.New().R().
|
|
SetQueryParams(g.MapStrStr{"access_token": c.accessToken}).
|
|
SetBody(body).
|
|
SetResult(&result).
|
|
Post("https://api.weixin.qq.com/cgi-bin/qrcode/create")
|
|
|
|
if err != nil {
|
|
glog.Errorf(context.Background(), "发起 get ticket 请求出现异常: %+v", err)
|
|
return "", ecode.Fail.Sub("发起 get ticket 请求出现异常")
|
|
}
|
|
if resp.StatusCode() != 200 {
|
|
glog.Errorf(context.Background(), "获取微信 ticket 响应异常: %+v", resp.Status())
|
|
return "", ecode.Fail.Sub("获取微信 ticket 失败")
|
|
}
|
|
|
|
glog.Infof(context.Background(), "获取微信 ticket 成功: %+v", result.Ticket)
|
|
return result.Ticket, nil
|
|
}
|
|
|
|
func (c *weChatClient) GetQrCode(ticket string, filename string) (imagePath string, err error) {
|
|
qrCodeUrl := "https://mp.weixin.qq.com/cgi-bin/showqrcode"
|
|
resp, err := resty.New().R().
|
|
SetDoNotParseResponse(true).
|
|
SetQueryParams(g.MapStrStr{"ticket": ticket}).
|
|
Get(qrCodeUrl)
|
|
|
|
if err != nil {
|
|
glog.Errorf(context.Background(), "获取二维码图片请求异常: %+v", err)
|
|
return "", ecode.Fail.Sub("获取二维码图片请求失败")
|
|
}
|
|
if resp.StatusCode() != 200 {
|
|
glog.Errorf(context.Background(), "获取二维码图片响应异常: %+v", resp.Status())
|
|
return "", ecode.Fail.Sub("获取二维码图片失败")
|
|
}
|
|
|
|
imagePath = filename
|
|
|
|
data, readErr := io.ReadAll(resp.RawBody())
|
|
if readErr != nil {
|
|
glog.Errorf(context.Background(), "读取二维码图片失败: %+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)
|
|
return "", ecode.Fail.Sub("保存二维码图片失败")
|
|
}
|
|
|
|
glog.Infof(context.Background(), "二维码图片保存成功: %s", imagePath)
|
|
return imagePath, nil
|
|
}
|