微信小程序的绝大多数的后台接口都需使用 access_token,本片文章主要说明一下golang的基础功能实现,不做业务场景分析。
首先贴上获取token的官方文档。https://developers.weixin.qq.com/miniprogram/dev/api-backend/getAccessToken.html
官方给出的token的请求地址:
GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
可以看到请求方式是get,地址为https://api.weixin.qq.com/cgi-bin/token
所需参数:
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
grant_type | string | 是 | 填写 client_credential,固定值 | |
appid | string | 是 | 小程序唯一凭证,即 AppID,可在「微信公众平台 - 设置 - 开发设置」页中获得。(需要已经成为开发者,且帐号没有异常状态) | |
secret | string | 是 | 小程序唯一凭证密钥,即 AppSecret,获取方式同 appid |
返回参数:
属性 | 类型 | 说明 |
---|---|---|
access_token | string | 获取到的凭证 |
expires_in | number | 凭证有效时间,单位:秒。目前是7200秒之内的值。 |
errcode | number | 错误码 |
errmsg | string | 错误信息 |
代码如下:
package wechat
import (
"encoding/json"
"errors"
"io/ioutil"
"log"
"net/http"
"net/url"
)
func requestToken(appid, secret string) (string, error) {
u, err := url.Parse("https://api.weixin.qq.com/cgi-bin/token")
if err != nil {
log.Fatal(err)
}
paras := &url.Values{}
//设置请求参数
paras.Set("appid", appid)
paras.Set("secret", secret)
paras.Set("grant_type", "client_credential")
u.RawQuery = paras.Encode()
resp, err := http.Get(u.String())
//关闭资源
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
if err != nil {
return "", errors.New("request token err :" + err.Error())
}
jMap := make(map[string]interface{})
err = json.NewDecoder(resp.Body).Decode(&jMap)
if err != nil {
return "", errors.New("request token response json parse err :" + err.Error())
}
if jMap["errcode"] == nil || jMap["errcode"] == 0 {
accessToken, _ := jMap["access_token"].(string)
return accessToken, nil
} else {
//返回错误信息
errcode := jMap["errcode"].(string)
errmsg := jMap["errmsg"].(string)
err = errors.New(errcode + ":" + errmsg)
return "", err
}
}
关于accessToken刷新的说明:
access_token
的存储至少要保留 512 个字符空间;access_token
的有效期目前为 2 个小时,需定时刷新,重复获取将导致上次获取的 access_token
失效;access_token
,其他业务逻辑服务器所使用的 access_token
均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致 access_token
覆盖而影响业务;access_token
的有效期通过返回的 expire_in
来传达,目前是7200秒之内的值,中控服务器需要根据这个有效时间提前去刷新。在刷新过程中,中控服务器可对外继续输出的老 access_token
,此时公众平台后台会保证在5分钟内,新老 access_token
都可用,这保证了第三方业务的平滑过渡;access_token
的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新 access_token
的接口,这样便于业务服务器在API调用获知 access_token
已超时的情况下,可以触发 access_token
的刷新流程。以上官方说明可以看出,目前有效时间是7200秒,为了保证业务的平滑新token获取的时候,老token保持5分钟。
我们可以使用go的ticker来设置7000秒请求一次来保证token的持续可用
func init() {
appid := ""
secret := ""
freshTokenTicker := time.NewTicker(7000 * time.Second)
//requestToken()
go func() {
for range freshTokenTicker.C {
accessToken, err := requestToken(appid, secret)
if err != nil {
//TODO 错误处理
}
log.Printf("token refresh :%s", accessToken)
}
}()
}
小程序的登陆功能是通过前端获取js_code传入后台,然后后台来调code2session接口获取用户信息。
接口文档链接:https://developers.weixin.qq.com/miniprogram/dev/api-backend/code2Session.html
请求地址:
https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
请求参数:
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
appid | string | 是 | 小程序 appId | |
secret | string | 是 | 小程序 appSecret | |
js_code | string | 是 | 登录时获取的 code,前端获取 | |
grant_type | string | 是 | 授权类型,此处只需填写 authorization_code,固定值 |
返回参数:
属性 | 类型 | 说明 |
---|---|---|
openid | string | 用户唯一标识 |
session_key | string | 会话密钥 |
unionid | string | 用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回,详见 UnionID 机制说明。 |
errcode | number | 错误码 |
errmsg | string | 错误信息 |
可以看到,所需要的参数都在返回值当中,解析之后,返回给业务层来处理
代码:
//返回的map可以替换为专门的结构体
func WechatLogin(js_code, appid, secret string) (map[string]interface{}, error) {
Code2SessURL := "https://api.weixin.qq.com/sns/jscode2session?appid={appid}&secret={secret}&js_code={code}&grant_type=authorization_code"
Code2SessURL = strings.Replace(Code2SessURL, "{appid}", appid, -1)
Code2SessURL = strings.Replace(Code2SessURL, "{secret}", secret, -1)
Code2SessURL = strings.Replace(Code2SessURL, "{code}", js_code, -1)
resp, err := http.Get(Code2SessURL)
//关闭资源
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, errors.New("WechatLogin request err :" + err.Error())
}
var jMap map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&jMap)
if err != nil {
return nil, errors.New("request token response json parse err :" + err.Error())
}
if _,ok:=jMap["errcode"] ;!ok || jMap["errcode"] == 0 {
return jMap, nil
} else {
//返回错误信息
errcode := jMap["errcode"].(string)
errmsg := jMap["errmsg"].(string)
err = errors.New(errcode + ":" + errmsg)
return nil, err
}
}