1 Star 0 Fork 0

扒拉扒拉 / wechat-sdk

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
access_token_server.go 5.99 KB
一键复制 编辑 原始数据 按行查看 历史
夏雨天 提交于 2023-05-12 08:37 . init
package component
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"gitee.com/vibly/wechat-sdk/mp/core"
"math/rand"
"net/http"
"net/url"
"strconv"
"sync/atomic"
"time"
"unsafe"
"gitee.com/vibly/wechat-sdk/internal/debug/api"
"gitee.com/vibly/wechat-sdk/util"
)
var _ core.AccessTokenServer = (*DefaultComponentAccessTokenServer)(nil)
// DefaultComponentAccessTokenServer 实现了 AccessTokenServer 开放平台接口.
// NOTE:
// 1. 用于单进程环境.
// 2. 因为 DefaultComponentAccessTokenServer 同时也是一个简单的中控服务器, 而不是仅仅实现 AccessTokenServer 接口,
// 所以整个系统只能存在一个 DefaultComponentAccessTokenServer 实例!
type DefaultComponentAccessTokenServer struct {
componentAppId string
componentAppSecret string
httpClient *http.Client
refreshTokenRequestChan chan string // chan currentToken
refreshTokenResponseChan chan refreshTokenResult // chan {token, err}
tokenCache unsafe.Pointer // *accessToken
getVerifyTicket func(appId string) string // get verify ticket func
}
// NewDefaultAccessTokenServer 创建一个新的 DefaultAccessTokenServer, 如果 httpClient == nil 则默认使用 util.DefaultHttpClient.
func NewDefaultComponentAccessTokenServer(componentAppId, componentAppSecret string, getVerifyTicket func(appId string) string, httpClient *http.Client) (srv *DefaultComponentAccessTokenServer) {
if httpClient == nil {
httpClient = util.DefaultHttpClient
}
srv = &DefaultComponentAccessTokenServer{
componentAppId: url.QueryEscape(componentAppId),
componentAppSecret: url.QueryEscape(componentAppSecret),
httpClient: httpClient,
refreshTokenRequestChan: make(chan string),
refreshTokenResponseChan: make(chan refreshTokenResult),
getVerifyTicket: getVerifyTicket,
}
go srv.tokenUpdateDaemon(time.Hour * 24 * time.Duration(100+rand.Int63n(200)))
return
}
func (srv *DefaultComponentAccessTokenServer) IID01332E16DF5011E5A9D5A4DB30FED8E1() {}
func (srv *DefaultComponentAccessTokenServer) Token() (token string, err error) {
if p := (*componentAccessToken)(atomic.LoadPointer(&srv.tokenCache)); p != nil {
return p.Token, nil
}
return srv.RefreshToken("")
}
type refreshTokenResult struct {
token string
err error
}
func (srv *DefaultComponentAccessTokenServer) RefreshToken(currentToken string) (token string, err error) {
srv.refreshTokenRequestChan <- currentToken
rslt := <-srv.refreshTokenResponseChan
return rslt.token, rslt.err
}
func (srv *DefaultComponentAccessTokenServer) tokenUpdateDaemon(initTickDuration time.Duration) {
tickDuration := initTickDuration
NEW_TICK_DURATION:
ticker := time.NewTicker(tickDuration)
for {
select {
case currentToken := <-srv.refreshTokenRequestChan:
{
accessToken, cached, err := srv.updateToken(currentToken)
if err != nil {
srv.refreshTokenResponseChan <- refreshTokenResult{err: err}
break
}
srv.refreshTokenResponseChan <- refreshTokenResult{token: accessToken.Token}
if !cached {
tickDuration = time.Duration(accessToken.ExpiresIn) * time.Second
ticker.Stop()
goto NEW_TICK_DURATION
}
}
case <-ticker.C:
{
accessToken, _, err := srv.updateToken("")
if err != nil {
break
}
newTickDuration := time.Duration(accessToken.ExpiresIn) * time.Second
if abs(tickDuration-newTickDuration) > time.Second*5 {
tickDuration = newTickDuration
ticker.Stop()
goto NEW_TICK_DURATION
}
}
}
}
}
func abs(x time.Duration) time.Duration {
if x >= 0 {
return x
}
return -x
}
type componentAccessToken struct {
Token string `json:"component_access_token"`
ExpiresIn int64 `json:"expires_in"`
}
// updateToken 从微信服务器获取新的 access_token 并存入缓存, 同时返回该 access_token.
func (srv *DefaultComponentAccessTokenServer) updateToken(currentToken string) (token *componentAccessToken, cached bool, err error) {
if currentToken != "" {
if p := (*componentAccessToken)(atomic.LoadPointer(&srv.tokenCache)); p != nil && currentToken != p.Token {
return p, true, nil // 无需更改 p.ExpiresIn 参数值, cached == true 时用不到
}
}
ticket := srv.getVerifyTicket(srv.componentAppId)
if ticket == "" {
err = errors.New("no verify ticket")
return
}
uri := "https://api.weixin.qq.com/cgi-bin/component/api_component_token"
api.DebugPrintGetRequest(uri)
body, _ := json.Marshal(map[string]interface{}{
"component_appid": srv.componentAppId,
"component_appsecret": srv.componentAppSecret,
"component_verify_ticket": ticket,
})
httpResp, err := srv.httpClient.Post(uri, "application/json; charset=utf-8", bytes.NewReader(body))
if err != nil {
atomic.StorePointer(&srv.tokenCache, nil)
return
}
defer httpResp.Body.Close()
if httpResp.StatusCode != http.StatusOK {
atomic.StorePointer(&srv.tokenCache, nil)
err = fmt.Errorf("http.Status: %s", httpResp.Status)
return
}
var result struct {
core.Error
componentAccessToken
}
if err = api.DecodeJSONHttpResponse(httpResp.Body, &result); err != nil {
atomic.StorePointer(&srv.tokenCache, nil)
return
}
if result.ErrCode != core.ErrCodeOK {
atomic.StorePointer(&srv.tokenCache, nil)
err = &result.Error
return
}
// 由于网络的延时, access_token 过期时间留有一个缓冲区
switch {
case result.ExpiresIn > 31556952: // 60*60*24*365.2425
atomic.StorePointer(&srv.tokenCache, nil)
err = errors.New("expires_in too large: " + strconv.FormatInt(result.ExpiresIn, 10))
return
case result.ExpiresIn > 60*60:
result.ExpiresIn -= 60 * 10
case result.ExpiresIn > 60*30:
result.ExpiresIn -= 60 * 5
case result.ExpiresIn > 60*5:
result.ExpiresIn -= 60
case result.ExpiresIn > 60:
result.ExpiresIn -= 10
default:
atomic.StorePointer(&srv.tokenCache, nil)
err = errors.New("expires_in too small: " + strconv.FormatInt(result.ExpiresIn, 10))
return
}
tokenCopy := result.componentAccessToken
atomic.StorePointer(&srv.tokenCache, unsafe.Pointer(&tokenCopy))
token = &tokenCopy
return
}
1
https://gitee.com/vibly/wechat-sdk.git
git@gitee.com:vibly/wechat-sdk.git
vibly
wechat-sdk
wechat-sdk
v1.0.0

搜索帮助