@@ -27,10 +27,12 @@ import (
2727 "time"
2828)
2929
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+ )
3436
3537/*
3638HttpClient 用于向公众号接口发送请求
@@ -41,36 +43,104 @@ type Client struct {
4143
4244// HTTPGet GET 请求
4345func (client * Client ) HTTPGet (uri string ) (resp []byte , err error ) {
44- uri , err = client .applyAccessToken (uri )
46+ newUrl , err : = client .applyAccessToken (uri )
4547 if err != nil {
4648 return
4749 }
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 )
5252 if err != nil {
5353 return
5454 }
55- defer response . Body . Close ()
56- return responseFilter ( response )
55+
56+ return client . httpDo ( req )
5757}
5858
5959//HTTPPost POST 请求
6060func (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 )
6262 if err != nil {
6363 return
6464 }
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+
6580 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 )
6782 }
68- response , err := http .Post (WXServerUrl + uri , contentType , payload )
83+
84+ response , err := http .DefaultClient .Do (req )
6985 if err != nil {
7086 return
7187 }
7288 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
74144}
75145
76146/*
@@ -116,11 +186,23 @@ func responseFilter(response *http.Response) (resp []byte, err error) {
116186 return
117187 }
118188
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+
119202 if errorResponse .Errcode != 0 {
120203 err = errors .New (string (resp ))
121204 return
122205 }
123-
124206 return
125207}
126208
@@ -148,17 +230,12 @@ func GetAccessToken(ctx *Miniprogram) (accessToken string, err error) {
148230 return
149231 }
150232
151- if ctx .Logger != nil {
152- ctx .Logger .Printf ("refreshAccessTokenFromWXServer appid = %s secret = %s\n " , ctx .Config .Appid , ctx .Config .Secret )
153- }
154-
155233 accessToken , expiresIn , err := refreshAccessTokenFromWXServer (ctx .Config .Appid , ctx .Config .Secret )
156234 if err != nil {
157235 return
158236 }
159237
160- // 提前过期 提供冗余时间
161- expiresIn = int (0.9 * float64 (expiresIn ))
238+ // 本地缓存 access_token
162239 d := time .Duration (expiresIn ) * time .Second
163240 _ = ctx .AccessToken .Cache .Save (ctx .Config .Appid , accessToken , d )
164241
@@ -169,6 +246,20 @@ func GetAccessToken(ctx *Miniprogram) (accessToken string, err error) {
169246 return
170247}
171248
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+
172263/*
173264从微信服务器获取新的 AccessToken
174265
0 commit comments