Koa2微信公众号开发 获取access_token
Posted JavaScript之禅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Koa2微信公众号开发 获取access_token相关的知识,希望对你有一定的参考价值。
一、简介
二、封装消息回复模块
上节中,我们把所有消息处理的代码都写在了一起,这样代码有点混乱。这节开始之前我们先开优化下我们的代码把消息回复模块给封装好。
好代码都是改出来的
新建一个wechat文件夹,在这个目录下建立一个 wechat.js
文件
'use strict'
const crypto = require('crypto')
const getRawBody = require('raw-body')
const xml2js = require('xml2js')
const ejs = require('ejs')
function getSignature(timestamp, nonce, token) {
...
}
function parseXML(xml) {
...
}
const tpl = `
<xml>
<ToUserName><![CDATA[<%-toUsername%>]]></ToUserName>
<FromUserName><![CDATA[<%-fromUsername%>]]></FromUserName>
...
...
<Content><![CDATA[<%-content%>]]></Content>
<% } %>
</xml>`
// ejs编译
const compiled = ejs.compile(tpl)
function reply(content, fromUsername, toUsername) {
...
}
function wechat(config, handle) {
return async (ctx) => {
const { signature, timestamp, nonce, echostr } = ctx.query
const TOKEN = config.wechat.token
if (ctx.method === 'GET') {
if (signature === getSignature(timestamp, nonce, TOKEN)) {
return ctx.body = echostr
}
ctx.status = 401
ctx.body = 'Invalid signature'
} else if (ctx.method === 'POST') {
if (signature !== getSignature(timestamp, nonce, TOKEN)) {
ctx.status = 401
return ctx.body = 'Invalid signature'
}
// 取原始数据
const xml = await getRawBody(ctx.req, {
length: ctx.request.length,
limit: '1mb',
encoding: ctx.request.charset || 'utf-8'
})
const formatted = await parseXML(xml)
// 业务逻辑处理handle
const content = await handle(formatted, ctx)
if (!content) {
return ctx.body = 'success'
}
const replyMessageXml = reply(content, formatted.ToUserName, formatted.FromUserName)
ctx.type = 'application/xml'
return ctx.body = replyMessageXml
}
}
}
module.exports = wechat
然后改我们的 app.js
'use strict'
const Koa = require('koa')
const app = new Koa()
const wechat = require('./wechat/wechat')
const config = require('./config')
app.use(wechat(config, async (message, ctx) => {
// TODO
return 'javascript之禅'
}))
app.listen(7001)
到此,我们只需要在 // TODO
这儿处理我们自己的各种业务逻辑即可, wechat.js
只用来处理与微信的交互,代码立马变得整洁干净了。
三、获取access_token
3.1 access_token概览
有效期为2小时(7200s),过期自动失效,需要重新获取
只要更新了accesstoken,之前的accesstoken自动失效
从这儿我们可以发现我们所需要解决的问题是:每两个小时去获取access_token,并把它存在一个唯一的地方方便我们的使用。
/*
https请求方式: GET
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
grant_type 是 获取access_token填写client_credential
appid 是 第三方用户唯一凭证
secret 是 第三方用户唯一凭证密钥,即appsecret
*/
{"access_token":"ACCESS_TOKEN","expires_in":7200}
/*
access_token 获取到的凭证
expires_in 凭证有效时间,单位:秒
*/
官方文档介绍可见:https://mp.weixin.qq.com/wiki
3.2 获取并保存access_token
现在开始编码实现我们之前所讲的获取access_token的流程,我们使用了es6的类,如果不了解es6可以去看看阮老师的ECMAScript 6 入门
这儿我们用到axios 这个网络请求的库,随着Vue社区对他的强烈推荐,看着它从几千star到了几万star。我们也赶赶时髦在这儿使用它来发送我们的所有请求。
npm install axios --save
建一个API 类,在这我们需要做的是发起网络请求获取access_token,将它保存在 access_token.txt
文件中,从文件中获取 access_token
并验证有效性,如果失效就请求新的。
通过axios获取access_token
class API {
constructor(appid, appsecret) {
this.appid = appid
this.appsecret = appsecret
this.prefix = 'https://api.weixin.qq.com/cgi-bin/'
}
async getAccessToken() {
const response = await axios.get(`${this.prefix}token?grant_type=client_credential&appid=${this.appid}&secret=${this.appsecret}`)
console.log(response.data)
}
}
const api = new API(appid, appsecret)
api.getAccessToken()
// {"access_token":"ACCESS_TOKEN","expires_in":7200}
储存access_token到文件中
前面我们获取回来了access_token但是每次都去请求太浪费资源了。而且每天有请求次数限制(2000次/天),所以我们需要存在文件中两小时获取一次新的,这儿我们将用到fs-extra这个文件操作模块方便我们使用Async/Await
npm install fs-extra --save
引入 fs-extra
,实现文件保存功能,我们需要保存access_token以及过期的时间。
class API {
constructor(appid, appsecret) {
this.appid = appid
this.appsecret = appsecret
this.prefix = 'https://api.weixin.qq.com/cgi-bin/'
// 保存access_token
this.saveToken = async function (token) {
await fs.writeFile('access_token.txt', JSON.stringify(token))
}
}
// 从https接口获取access_token
async getAccessToken() {
let token = {}
const response = await axios.get(`${this.prefix}token?grant_type=client_credential&appid=${this.appid}&secret=${this.appsecret}`)
// 过期时间,因网络延迟等,将实际过期时间提前20秒,以防止临界点
const expireTime = Date.now() + (data.data.expires_in - 20) * 1000
token.accessToken = response.data.access_token
token.expireTime = expireTime
await this.saveToken(token)
return token
}
}
const api = new API(appid, appsecret)
api.getAccessToken()
看看你的目录下有没有多出一个access_token.txt文件,如果有并且里面已经写入数据,那么恭喜你这步没有出错。(实际开发中你可能会犯各种小错误:敲错字母等)。既然存进去了,我们当然还需要从文件中读出来
class API {
constructor(appid, appsecret) {
this.appid = appid
this.appsecret = appsecret
this.prefix = 'https://api.weixin.qq.com/cgi-bin/'
// 保存access_token到文件
this.saveToken = async function (token) {
await fs.writeFile('access_token.txt', JSON.stringify(token))
}
// 从文件获取读取数据
this.getToken = async function () {
const txt = await fs.readFile('access_token.txt', 'utf8')
return JSON.parse(txt)
}
}
// 从https接口获取access_token
async getAccessToken() {
...
}
}
到此我们已经解决了一大半的问题了,我们只需要再来写个验证accesstoken是否过期的方法,同时实现一个输出accesstoken的方法,方便我们在写其他功能时获取到这个全局唯一的access_token
class API {
constructor(appid, appsecret) {
this.appid = appid
this.appsecret = appsecret
this.prefix = 'https://api.weixin.qq.com/cgi-bin/'
// 保存access_token到文件
this.saveToken = async function (token) {
await fs.writeFile('access_token.txt', JSON.stringify(token))
}
// 从文件获取读取数据
this.getToken = async function () {
const txt = await fs.readFile('access_token.txt', 'utf8')
return JSON.parse(txt)
}
}
// 从https接口获取access_token
async getAccessToken() {
...
}
// 读取文件获取token,读取失败重新请求接口
async ensureAccessToken() {
let token = {}
try {
token = await this.getToken()
} catch (e) {
token = await this.getAccessToken()
}
if(token && (this.isValid(token.accessToken, token.expireTime))) {
return token
}
return this.getAccessToken()
}
// 验证access_token是否过期
isValid(accessToken, expireTime) {
return !!accessToken && Date.now() < expireTime
}
}
现在本篇教程的重点获取access_token就讲完了。最后我们来调用下自定义菜单接口验证下之前所写的代码
四、创建自定义菜单创建接口
在API类中新加一个 createMenu
方法
class API {
...
// 创建菜单
async createMenu(menu) {
const { accessToken } = await this.ensureAccessToken()
let url = this.prefix + 'menu/create?access_token=' + accessToken
const response = await axios.post(url, menu)
return response.data
}
}
接着我们就可以调用这个方法试试了
const api = new API(config.wechat.appid, config.wechat.appsecret)
const menu = {
"button":[
{
"type":"click",
"name":"今日歌曲",
"key":"V1001_TODAY_MUSIC"
},
{
"name":"菜单",
"sub_button":[
{
"type":"view",
"name":"搜索",
"url":"http://www.soso.com/"
},
{
"type":"click",
"name":"赞一下我们",
"key":"V1001_GOOD"
}]
}]
}
app.use(async (ctx) => {
// TODO
const result = await api.createMenu(menu)
console.log(result)
})
app.listen(7001)
运行app.js,命令行将打印出如下信息。恭喜。
{ errcode: 0, errmsg: 'ok' }
如果,你很不幸的得到了错误信息,那就慢慢找错吧,哈哈哈
明天我们接着讲自定义菜单接口的开发
参考链接
ECMAScript 6 入门:http://es6.ruanyifeng.com/
axios: https://github.com/axios/axios
fs-extra: https://github.com/jprichardson/node-fs-extra
左手代码右手砖,抛砖引玉
往期精彩回顾
以上是关于Koa2微信公众号开发 获取access_token的主要内容,如果未能解决你的问题,请参考以下文章