forked from chanxuehong/wechat
/
client_token.go
149 lines (136 loc) · 4.8 KB
/
client_token.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package wechat
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
// 从本地缓存获取 access token.
func (c *Client) Token() (token string, err error) {
c.tokenCache.rwmutex.RLock()
token = c.tokenCache.token
err = c.tokenCache.err
c.tokenCache.rwmutex.RUnlock()
return
}
// see Client.TokenRefresh() and Client.tokenService()
func (c *Client) update(token string, err error) {
c.tokenCache.rwmutex.Lock()
c.tokenCache.token = token
c.tokenCache.err = err
c.tokenCache.rwmutex.Unlock()
}
// 从微信服务器获取 access token, 并更新本地缓存.
// NOTE: 正常情况下无需调用该函数, 请使用 Client.Token() 获取 access token.
func (c *Client) TokenRefresh() (token string, err error) {
resp, err := c.getNewToken()
switch {
case err != nil:
c.update("", err)
return
case resp.ExpiresIn > 10: // 正常情况
c.update(resp.Token, nil)
token = resp.Token
// 通知 goroutine tokenService() 重置定时器
// 考虑到网络延时, 提前 10 秒过期
c.resetRefreshTickChan <- time.Duration(resp.ExpiresIn-10) * time.Second
return
case resp.ExpiresIn > 0: // 正常情况下不会出现
c.update(resp.Token, nil)
token = resp.Token
// 通知 goroutine tokenService() 重置定时器
c.resetRefreshTickChan <- time.Duration(resp.ExpiresIn) * time.Second
return
default: // resp.ExpiresIn <= 0, 正常情况下不会出现
err = fmt.Errorf("TokenRefresh: access token 过期时间应该是正整数: %d", resp.ExpiresIn)
c.update("", err)
return
}
}
// 负责定时更新本地缓存的 access token.
// 使用这种复杂的实现是减少 time.Now() 的调用, 不然每次都要比较 time.Now().
func (c *Client) tokenService() {
const defaultTickDuration = time.Minute // 设置 44 秒以上就不会超过限制(2000次/日 的限制)
// 当前定时器的时间间隔, 正常情况下等于当前的 access token 的过期时间减去 10 秒;
// 异常情况下就要尝试不断的获取, 时间间隔就是 defaultTickDuration.
currentTickDuration := defaultTickDuration
var tk *time.Ticker
NewTickDuration:
for {
tk = time.NewTicker(currentTickDuration)
for {
select {
case currentTickDuration = <-c.resetRefreshTickChan: // 在别的地方成功获取了 access token, 重置定时器.
tk.Stop()
break NewTickDuration
case <-tk.C:
resp, err := c.getNewToken()
switch {
case err != nil:
c.update("", err)
// 出错则重置到 defaultTickDuration
if currentTickDuration != defaultTickDuration { // 这个判断的目的是避免重置定时器开销
tk.Stop()
currentTickDuration = defaultTickDuration
break NewTickDuration
}
case resp.ExpiresIn > 10: // 正常情况
c.update(resp.Token, nil)
// 根据返回的过期时间来重新设置定时器
// 设置新的 currentTickDuration, 考虑到网络延时, 提前 10 秒过期
nextTickDuration := time.Duration(resp.ExpiresIn-10) * time.Second
if currentTickDuration != nextTickDuration { // 这个判断的目的是避免重置定时器开销
tk.Stop()
currentTickDuration = nextTickDuration
break NewTickDuration
}
case resp.ExpiresIn > 0: // 正常情况下不会出现
c.update(resp.Token, nil)
// 根据返回的过期时间来重新设置定时器
nextTickDuration := time.Duration(resp.ExpiresIn) * time.Second
if currentTickDuration != nextTickDuration { // 这个判断的目的是避免重置定时器开销
tk.Stop()
currentTickDuration = nextTickDuration
break NewTickDuration
}
default: // resp.ExpiresIn <= 0, 正常情况下不会出现
c.update("", fmt.Errorf("tokenService: access token 过期时间应该是正整数: %d", resp.ExpiresIn))
// 出错则重置到 defaultTickDuration
if currentTickDuration != defaultTickDuration { // 这个判断的目的是避免重置定时器开销
tk.Stop()
currentTickDuration = defaultTickDuration
break NewTickDuration
}
}
}
}
}
}
// 从服务器获取 acces_token 成功时返回的消息格式
type clientTokenResponse struct {
Token string `json:"access_token"` // 获取到的凭证
ExpiresIn int64 `json:"expires_in"` // 凭证有效时间,单位:秒
}
// 从微信服务器获取新的 access_token
func (c *Client) getNewToken() (*clientTokenResponse, error) {
_url := clientTokenGetURL(c.appid, c.appsecret)
resp, err := c.httpClient.Get(_url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("getNewToken: getNewToken: %s", resp.Status)
}
var result struct {
clientTokenResponse
Error
}
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
if result.ErrCode != 0 {
return nil, &result.Error
}
return &result.clientTokenResponse, nil
}