@@ -27,10 +27,12 @@ import (
27
27
"time"
28
28
)
29
29
30
- /*
31
- 微信 api 服务器地址
32
- */
33
- var WXServerUrl = "https://api.weixin.qq.com"
30
+ var (
31
+ WXServerUrl = "https://api.weixin.qq.com" // 微信 api 服务器地址
32
+ UserAgent = "fastwego/miniprogram"
33
+ ErrorAccessTokenExpire = errors .New ("access token expire" )
34
+ ErrorSystemBusy = errors .New ("system busy" )
35
+ )
34
36
35
37
/*
36
38
HttpClient 用于向公众号接口发送请求
@@ -41,36 +43,104 @@ type Client struct {
41
43
42
44
// HTTPGet GET 请求
43
45
func (client * Client ) HTTPGet (uri string ) (resp []byte , err error ) {
44
- uri , err = client .applyAccessToken (uri )
46
+ newUrl , err : = client .applyAccessToken (uri )
45
47
if err != nil {
46
48
return
47
49
}
48
- if client .Ctx .Logger != nil {
49
- client .Ctx .Logger .Printf ("GET %s" , uri )
50
- }
51
- response , err := http .Get (WXServerUrl + uri )
50
+
51
+ req , err := http .NewRequest (http .MethodGet , WXServerUrl + newUrl , nil )
52
52
if err != nil {
53
53
return
54
54
}
55
- defer response . Body . Close ()
56
- return responseFilter ( response )
55
+
56
+ return client . httpDo ( req )
57
57
}
58
58
59
59
//HTTPPost POST 请求
60
60
func (client * Client ) HTTPPost (uri string , payload io.Reader , contentType string ) (resp []byte , err error ) {
61
- uri , err = client .applyAccessToken (uri )
61
+ newUrl , err : = client .applyAccessToken (uri )
62
62
if err != nil {
63
63
return
64
64
}
65
+
66
+ req , err := http .NewRequest (http .MethodPost , WXServerUrl + newUrl , payload )
67
+ if err != nil {
68
+ return
69
+ }
70
+
71
+ req .Header .Add ("Content-Type" , contentType )
72
+
73
+ return client .httpDo (req )
74
+ }
75
+
76
+ //httpDo 执行 请求
77
+ func (client * Client ) httpDo (req * http.Request ) (resp []byte , err error ) {
78
+ req .Header .Add ("User-Agent" , UserAgent )
79
+
65
80
if client .Ctx .Logger != nil {
66
- client .Ctx .Logger .Printf ("POST %s" , uri )
81
+ client .Ctx .Logger .Printf ("%s %s Headers %v " , req . Method , req . URL . String (), req . Header )
67
82
}
68
- response , err := http .Post (WXServerUrl + uri , contentType , payload )
83
+
84
+ response , err := http .DefaultClient .Do (req )
69
85
if err != nil {
70
86
return
71
87
}
72
88
defer response .Body .Close ()
73
- return responseFilter (response )
89
+
90
+ resp , err = responseFilter (response )
91
+
92
+ // 发现 access_token 过期
93
+ if err == ErrorAccessTokenExpire {
94
+
95
+ // 主动 通知 access_token 过期
96
+ err = client .Ctx .AccessToken .NoticeAccessTokenExpireHandler (client .Ctx )
97
+ if err != nil {
98
+ return
99
+ }
100
+
101
+ // 通知到位后 access_token 会被刷新,那么可以 retry 了
102
+ var accessToken string
103
+ accessToken , err = client .Ctx .AccessToken .GetAccessTokenHandler (client .Ctx )
104
+ if err != nil {
105
+ return
106
+ }
107
+
108
+ // 换新
109
+ q := req .URL .Query ()
110
+ q .Set ("access_token" , accessToken )
111
+ req .URL .RawQuery = q .Encode ()
112
+
113
+ if client .Ctx .Logger != nil {
114
+ client .Ctx .Logger .Printf ("%v retry %s %s Headers %v" , ErrorAccessTokenExpire , req .Method , req .URL .String (), req .Header )
115
+ }
116
+
117
+ response , err = http .DefaultClient .Do (req )
118
+ if err != nil {
119
+ return
120
+ }
121
+ defer response .Body .Close ()
122
+
123
+ resp , err = responseFilter (response )
124
+ }
125
+
126
+ // -1 系统繁忙,此时请开发者稍候再试
127
+ // 重试一次
128
+ if err == ErrorSystemBusy {
129
+
130
+ if client .Ctx .Logger != nil {
131
+ client .Ctx .Logger .Printf ("%v : retry %s %s Headers %v" , ErrorSystemBusy , req .Method , req .URL .String (), req .Header )
132
+ }
133
+
134
+ response , err = http .DefaultClient .Do (req )
135
+ if err != nil {
136
+ return
137
+ }
138
+ defer response .Body .Close ()
139
+
140
+ resp , err = responseFilter (response )
141
+ }
142
+
143
+ return
74
144
}
75
145
76
146
/*
@@ -116,11 +186,23 @@ func responseFilter(response *http.Response) (resp []byte, err error) {
116
186
return
117
187
}
118
188
189
+ // 40001(覆盖刷新超过5min后,使用旧 access_token 报错) 获取 access_token 时 AppSecret 错误,或者 access_token 无效。请开发者认真比对 AppSecret 的正确性,或查看是否正在为恰当的公众号调用接口
190
+ // 42001(超过 7200s 后 报错) - access_token 超时,请检查 access_token 的有效期,请参考基础支持 - 获取 access_token 中,对 access_token 的详细机制说明
191
+ if errorResponse .Errcode == 42001 || errorResponse .Errcode == 40001 {
192
+ err = ErrorAccessTokenExpire
193
+ return
194
+ }
195
+
196
+ // -1 系统繁忙,此时请开发者稍候再试
197
+ if errorResponse .Errcode == - 1 {
198
+ err = ErrorSystemBusy
199
+ return
200
+ }
201
+
119
202
if errorResponse .Errcode != 0 {
120
203
err = errors .New (string (resp ))
121
204
return
122
205
}
123
-
124
206
return
125
207
}
126
208
@@ -148,17 +230,12 @@ func GetAccessToken(ctx *Miniprogram) (accessToken string, err error) {
148
230
return
149
231
}
150
232
151
- if ctx .Logger != nil {
152
- ctx .Logger .Printf ("refreshAccessTokenFromWXServer appid = %s secret = %s\n " , ctx .Config .Appid , ctx .Config .Secret )
153
- }
154
-
155
233
accessToken , expiresIn , err := refreshAccessTokenFromWXServer (ctx .Config .Appid , ctx .Config .Secret )
156
234
if err != nil {
157
235
return
158
236
}
159
237
160
- // 提前过期 提供冗余时间
161
- expiresIn = int (0.9 * float64 (expiresIn ))
238
+ // 本地缓存 access_token
162
239
d := time .Duration (expiresIn ) * time .Second
163
240
_ = ctx .AccessToken .Cache .Save (ctx .Config .Appid , accessToken , d )
164
241
@@ -169,6 +246,20 @@ func GetAccessToken(ctx *Miniprogram) (accessToken string, err error) {
169
246
return
170
247
}
171
248
249
+ /*
250
+ NoticeAccessTokenExpire 只需将本地存储的 access_token 删除,即完成了 access_token 已过期的 主动通知
251
+
252
+ retry 请求的时候,会发现本地没有 access_token ,从而触发refresh
253
+ */
254
+ func NoticeAccessTokenExpire (ctx * Miniprogram ) (err error ) {
255
+ if ctx .Logger != nil {
256
+ ctx .Logger .Println ("NoticeAccessTokenExpire" )
257
+ }
258
+
259
+ err = ctx .AccessToken .Cache .Delete (ctx .Config .Appid )
260
+ return
261
+ }
262
+
172
263
/*
173
264
从微信服务器获取新的 AccessToken
174
265
0 commit comments