微信的文档有个很有意思的地方,刚开始你顺着文档看,就可以一步一步完成自己需要的功能,但走着走着,就感到有些混乱,你再顺着文档做,就掉坑里了。
我们在使用golang快速开发微信公众平台(一)微信验证服务器通过后,就要开始着手获取accessToken,这个东西很重要,在菜单、客服、支付等操作中都需要用到
在文档中,有以下2点很重要:
- access_token每日限额获取2000次,相当于允许你以43秒的频率来取,但明显这样做会显得很傻。
- access_token有效时间是7200秒
以get请求获取一段json并解析来获取access_token,看起来很简单对不对?但应用到实际中就稍微有些麻烦了:
- 需要找个什么东西来存,可以是文件、可以是数据库、甚至可以直接就扔到全局变量里。嗯,我选数据库。
- 定时发送这个get请求,更新数据库。
- 这个get请求是可能失败的 是可能失败的 是可能失败的,虽然这个概率极低,但对于如此重要的参数,一旦在2个小时的空档期内都无法调用,会产生极其灾难性的后果,别问我怎么知道的
好,思路捋一下,3件事:拼接get请求json解析,建个表来存取更新,定时任务
开始搓代码
access_token的存取
WxAccTokenUtil.go
package WxPlatUtil
import (
"strings"
"net/http"
"fmt"
"io/IoUtil"
"bytes"
"encoding/json"
"mlshop/shopUtils/logUtils"
"mlshop/models"
)
type AccessTokenResponse struct {
AccessToken string `json:"access_token"`
ExpiresIn float64 `json:"expires_in"`
}
//type AccessTokenErrorResponse struct {
// Errcode float64
// Errmsg string
//}
//获取wx_AccessToken 拼接get请求 解析返回json结果 返回 AccessToken和err
func FetchAccessToken(appID,appSecret,accessTokenFetchUrl string) (string,error) {
requestLine := strings.Join([]string{accessTokenFetchUrl,"?grant_type=client_credential&appid=",appID,"&secret=",appSecret},"")
resp,err := http.Get(requestLine)
if err != nil || resp.StatusCode != http.StatusOK {
fmt.Println("发送get请求获取 atoken 错误",err)
logUtils.GetLog().Error("发送get请求获取 atoken 错误",err)
return "",err
}
defer resp.Body.Close()
body,err := IoUtil.ReadAll(resp.Body)
if err != nil {
fmt.Println("发送get请求获取 atoken 读取返回body错误",err)
logUtils.GetLog().Error("发送get请求获取 atoken 读取返回body错误",err
}
if bytes.Contains(body,[]byte("access_token")) {
atr := AccessTokenResponse{}
err = json.Unmarshal(body,&atr)
if err != nil {
fmt.Println("发送get请求获取 atoken 返回数据json解析错误",err)
logUtils.GetLog().Error("发送get请求获取 atoken 返回数据json解析错误",err)
return "",err
}
return atr.AccessToken,atr.ExpiresIn,nil
} else {
fmt.Println("发送get请求获取 微信返回 err")
ater := models.AccessTokenErrorResponse{}
err = json.Unmarshal(body,&ater)
fmt.Printf("发送get请求获取 微信返回 的错误信息 %+v\n",ater)
if err != nil {
return "",err
}
return "",fmt.Errorf("%s",ater.Errmsg)
}
}
//type WxAccessToken struct {
// Id int `orm:"auto"`
// AccessToken string
//}
//微信公众平台的参数
//type WxBase struct {
// Id int `orm:"auto"`
// AppID string
// AppSecret string
// Token string
// EncodingAESKey string
//}
//数据库存取更新access_token
func GetAndUpdateDBWxAToken(o orm.Ormer) error {
at := models.WxAccessToken{Id: 1}
o.ReadOrCreate(&at,"id")
wxBase := models.WxBase{Id:1}
err := orm.NewOrm().Read(&wxBase)
if err != nil {
fmt.Println("从数据库查询WxBase失败",err)
return err
}
//向微信服务器发送获取accessToken的get请求
accessToken,err := WxPlatUtil.FetchAccessToken(wxBase.AppID,wxBase.AppSecret,"https://api.weixin.qq.com/cgi-bin/token")
if err != nil {
fmt.Println("向微信服务器发送获取accessToken的get请求失败",err)
logUtils.GetLog().Error("向微信服务器发送获取accessToken的get请求失败",err)
return err
}
at.AccessToken = accessToken
o.Update(&at,"access_token")
return nil
}
得益于go语言方便的json与struct转换和beego的orm,第一个任务很快就可以完成。
接下来,我们要考虑定时任务的问题。
定时任务
定时的话time包倒是有个ticker,出现在脑子里的第一个思路是把时间段时间点都转化为秒数,然后开始倒计时。。。好傻
可以搜到很多关于golang的time使用方法,比如下面这个
func startTimer(f func()) {
go func() {
for {
f()
now := time.Now()
// 计算下一个零点
next := now.Add(time.Hour * 24)
next = time.Date(next.Year(),next.Month(),next.Day(),0,next.Location())
t := time.NewTimer(next.Sub(now))
<-t.C
}
}()
}
嗯 看起来还是很不错的,群里讨论的时候,大家都喜欢在当前时间上+24小时获取明天,然后取0点,然后加上自己想要的时间段
但是 GRD的需求是会变来变去的
今天要求是明天2点,后天要求是明天2点半,大后天又变成了9个小时来一次。结合咱们的定时任务,要是既能传时间段,又能传时间点就好了。
github上go的time库倒是有,可我一个都没用到。嗯 因为懒。
这就是为啥我推荐大家用pycharm、PHPstrom、webstrom来写go,看库多方便,而且还自带ssh ftp工具,是人都知道在Win Mac 上找不同的ftp工具是多么的痛苦。表用IDEA…等你用了就知道为啥。
beego自带的定时任务模块很强,而且使用简单,虽然传参很丑,不适合装逼,丑到啥地步呢:
如果是集团军作战,传这样的参数肯定是被骂的,但咱是单兵作战,好用即可。到这里,咱们的定时任务也可以出炉了:
//从task_test.go文件里找使用方法
func shopTiMetask(o orm.Ormer) {
timeStr2 := "0 */60 * * * *" // 获取wx token
t2 := toolBox.NewTask("getAtoken",timeStr2,func() error {
err := WxPlatUtil.GetAndUpdateDBWxAToken(o)
if err != nil{
//todo 向微信请求access_token失败 结合业务逻辑处理
}
return nil
})
toolBox.AddTask("tk2",t2)
toolBox.StartTask()
}
唉咋知道怎么用的?test文件啊;唉这命名真不规范,我还有数据库备份也用代码写的。。。 ok,就这么几句,23步咱们都干完了,第三步其实大家自行处理就好。虽然说的很严重,但是只要有处理就不会那么惨。。。