Koa2微信公众号开发 获取access_token

Posted JavaScript之禅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Koa2微信公众号开发 获取access_token相关的知识,希望对你有一定的参考价值。

一、简介

二、封装消息回复模块

上节中,我们把所有消息处理的代码都写在了一起,这样代码有点混乱。这节开始之前我们先开优化下我们的代码把消息回复模块给封装好。

好代码都是改出来的

新建一个wechat文件夹,在这个目录下建立一个 wechat.js文件

 
   
   
 
  1. 'use strict'

  2. const crypto = require('crypto')

  3. const getRawBody = require('raw-body')

  4. const xml2js = require('xml2js')

  5. const ejs = require('ejs')

  6. function getSignature(timestamp, nonce, token) {

  7.  ...

  8. }

  9. function parseXML(xml) {

  10.  ...

  11. }

  12. const tpl = `

  13. <xml>

  14.  <ToUserName><![CDATA[<%-toUsername%>]]></ToUserName>

  15.  <FromUserName><![CDATA[<%-fromUsername%>]]></FromUserName>

  16. ...

  17. ...

  18.  <Content><![CDATA[<%-content%>]]></Content>

  19.  <% } %>

  20. </xml>`

  21. // ejs编译

  22. const compiled = ejs.compile(tpl)

  23. function reply(content, fromUsername, toUsername) {

  24.  ...

  25. }

  26. function wechat(config, handle) {

  27.  return async (ctx) => {

  28.    const { signature, timestamp, nonce, echostr } = ctx.query

  29.    const TOKEN = config.wechat.token

  30.    if (ctx.method === 'GET') {

  31.      if (signature === getSignature(timestamp, nonce, TOKEN)) {

  32.        return ctx.body = echostr

  33.      }

  34.      ctx.status = 401

  35.      ctx.body = 'Invalid signature'

  36.    } else if (ctx.method === 'POST') {

  37.      if (signature !== getSignature(timestamp, nonce, TOKEN)) {

  38.        ctx.status = 401

  39.        return ctx.body = 'Invalid signature'

  40.      }

  41.      // 取原始数据

  42.      const xml = await getRawBody(ctx.req, {

  43.        length: ctx.request.length,

  44.        limit: '1mb',

  45.        encoding: ctx.request.charset || 'utf-8'

  46.      })

  47.      const formatted = await parseXML(xml)

  48.      // 业务逻辑处理handle

  49.      const content = await handle(formatted, ctx)

  50.      if (!content) {

  51.        return ctx.body = 'success'

  52.      }

  53.      const replyMessageXml = reply(content, formatted.ToUserName, formatted.FromUserName)

  54.      ctx.type = 'application/xml'

  55.      return ctx.body = replyMessageXml

  56.    }

  57.  }

  58. }

  59. module.exports = wechat

然后改我们的 app.js

 
   
   
 
  1. 'use strict'

  2. const Koa = require('koa')

  3. const app = new Koa()

  4. const wechat = require('./wechat/wechat')

  5. const config = require('./config')

  6. app.use(wechat(config, async (message, ctx) => {

  7.  // TODO

  8.  return 'javascript之禅'

  9. }))

  10. app.listen(7001)

到此,我们只需要在 // TODO这儿处理我们自己的各种业务逻辑即可, wechat.js只用来处理与微信的交互,代码立马变得整洁干净了。

三、获取access_token

3.1 access_token概览

  • 有效期为2小时(7200s),过期自动失效,需要重新获取

  • 只要更新了accesstoken,之前的accesstoken自动失效

从这儿我们可以发现我们所需要解决的问题是:每两个小时去获取access_token,并把它存在一个唯一的地方方便我们的使用。

 
   
   
 
  1. /*

  2. https请求方式: GET

  3. https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

  4. grant_type    是   获取access_token填写client_credential

  5. appid    是   第三方用户唯一凭证

  6. secret    是   第三方用户唯一凭证密钥,即appsecret

  7. */

 
   
   
 
  1. {"access_token":"ACCESS_TOKEN","expires_in":7200}

  2. /*

  3. access_token    获取到的凭证

  4. expires_in    凭证有效时间,单位:秒

  5. */

官方文档介绍可见:https://mp.weixin.qq.com/wiki

3.2 获取并保存access_token

现在开始编码实现我们之前所讲的获取access_token的流程,我们使用了es6的类,如果不了解es6可以去看看阮老师的ECMAScript 6 入门

这儿我们用到axios 这个网络请求的库,随着Vue社区对他的强烈推荐,看着它从几千star到了几万star。我们也赶赶时髦在这儿使用它来发送我们的所有请求。

 
   
   
 
  1. npm install axios --save

建一个API 类,在这我们需要做的是发起网络请求获取access_token,将它保存在 access_token.txt文件中,从文件中获取 access_token 并验证有效性,如果失效就请求新的。

通过axios获取access_token

 
   
   
 
  1. class API {

  2.  constructor(appid, appsecret) {

  3.    this.appid = appid

  4.    this.appsecret = appsecret

  5.    this.prefix = 'https://api.weixin.qq.com/cgi-bin/'

  6.  }

  7.  async getAccessToken() {

  8.    const response = await axios.get(`${this.prefix}token?grant_type=client_credential&appid=${this.appid}&secret=${this.appsecret}`)

  9.    console.log(response.data)

  10.  }

  11. }

  12. const api = new API(appid, appsecret)

  13. api.getAccessToken()

  14. // {"access_token":"ACCESS_TOKEN","expires_in":7200}

储存access_token到文件中

前面我们获取回来了access_token但是每次都去请求太浪费资源了。而且每天有请求次数限制(2000次/天),所以我们需要存在文件中两小时获取一次新的,这儿我们将用到fs-extra这个文件操作模块方便我们使用Async/Await

 
   
   
 
  1. npm install fs-extra --save

引入 fs-extra,实现文件保存功能,我们需要保存access_token以及过期的时间。

 
   
   
 
  1. class API {

  2.  constructor(appid, appsecret) {

  3.    this.appid = appid

  4.    this.appsecret = appsecret

  5.    this.prefix = 'https://api.weixin.qq.com/cgi-bin/'

  6.    // 保存access_token

  7.    this.saveToken = async function (token) {

  8.      await fs.writeFile('access_token.txt', JSON.stringify(token))

  9.    }

  10.  }

  11.  // 从https接口获取access_token

  12.  async getAccessToken() {

  13.    let token = {}

  14.    const response = await axios.get(`${this.prefix}token?grant_type=client_credential&appid=${this.appid}&secret=${this.appsecret}`)

  15.    // 过期时间,因网络延迟等,将实际过期时间提前20秒,以防止临界点

  16.    const expireTime = Date.now() + (data.data.expires_in - 20) * 1000

  17.    token.accessToken = response.data.access_token

  18.    token.expireTime = expireTime

  19.    await this.saveToken(token)

  20.    return token

  21.  }

  22. }

  23. const api = new API(appid, appsecret)

  24. api.getAccessToken()

看看你的目录下有没有多出一个access_token.txt文件,如果有并且里面已经写入数据,那么恭喜你这步没有出错。(实际开发中你可能会犯各种小错误:敲错字母等)。既然存进去了,我们当然还需要从文件中读出来

 
   
   
 
  1. class API {

  2.  constructor(appid, appsecret) {

  3.    this.appid = appid

  4.    this.appsecret = appsecret

  5.    this.prefix = 'https://api.weixin.qq.com/cgi-bin/'

  6.    // 保存access_token到文件

  7.    this.saveToken = async function (token) {

  8.      await fs.writeFile('access_token.txt', JSON.stringify(token))

  9.    }

  10.    // 从文件获取读取数据

  11.    this.getToken = async function () {

  12.      const txt = await fs.readFile('access_token.txt', 'utf8')

  13.      return JSON.parse(txt)

  14.    }

  15.  }

  16.  // 从https接口获取access_token

  17.  async getAccessToken() {

  18.    ...

  19.  }

  20. }

到此我们已经解决了一大半的问题了,我们只需要再来写个验证accesstoken是否过期的方法,同时实现一个输出accesstoken的方法,方便我们在写其他功能时获取到这个全局唯一的access_token

 
   
   
 
  1. class API {

  2.  constructor(appid, appsecret) {

  3.    this.appid = appid

  4.    this.appsecret = appsecret

  5.    this.prefix = 'https://api.weixin.qq.com/cgi-bin/'

  6.    // 保存access_token到文件

  7.    this.saveToken = async function (token) {

  8.      await fs.writeFile('access_token.txt', JSON.stringify(token))

  9.    }

  10.    // 从文件获取读取数据

  11.    this.getToken = async function () {

  12.      const txt = await fs.readFile('access_token.txt', 'utf8')

  13.      return JSON.parse(txt)

  14.    }

  15.  }

  16.  // 从https接口获取access_token

  17.  async getAccessToken() {

  18.    ...

  19.  }

  20.  // 读取文件获取token,读取失败重新请求接口

  21.  async ensureAccessToken() {

  22.    let token = {}

  23.    try {

  24.      token = await this.getToken()

  25.    } catch (e) {

  26.      token = await this.getAccessToken()

  27.    }

  28.    if(token && (this.isValid(token.accessToken, token.expireTime))) {

  29.      return token

  30.    }

  31.    return this.getAccessToken()

  32.  }

  33.  // 验证access_token是否过期

  34.  isValid(accessToken, expireTime) {

  35.    return !!accessToken && Date.now() < expireTime

  36.  }

  37. }

现在本篇教程的重点获取access_token就讲完了。最后我们来调用下自定义菜单接口验证下之前所写的代码

四、创建自定义菜单创建接口

在API类中新加一个 createMenu方法

 
   
   
 
  1. class API {

  2.  ...

  3.  // 创建菜单

  4.  async createMenu(menu) {

  5.    const { accessToken } = await this.ensureAccessToken()

  6.    let url = this.prefix + 'menu/create?access_token=' + accessToken

  7.    const response = await axios.post(url, menu)

  8.    return response.data

  9.  }

  10. }

接着我们就可以调用这个方法试试了

 
   
   
 
  1. const api = new API(config.wechat.appid, config.wechat.appsecret)

  2. const menu = {

  3.  "button":[

  4.  {

  5.       "type":"click",

  6.       "name":"今日歌曲",

  7.       "key":"V1001_TODAY_MUSIC"

  8.   },

  9.   {

  10.        "name":"菜单",

  11.        "sub_button":[

  12.        {

  13.            "type":"view",

  14.            "name":"搜索",

  15.            "url":"http://www.soso.com/"

  16.         },

  17.         {

  18.            "type":"click",

  19.            "name":"赞一下我们",

  20.            "key":"V1001_GOOD"

  21.         }]

  22.    }]

  23. }

  24. app.use(async (ctx) => {

  25.  // TODO

  26.  const result = await api.createMenu(menu)

  27.  console.log(result)

  28. })

  29. app.listen(7001)

运行app.js,命令行将打印出如下信息。恭喜。

 
   
   
 
  1. { 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的主要内容,如果未能解决你的问题,请参考以下文章

微信公众号开发--接入

微信开发在Pc端调用公众号粉丝发送过来的图片素材

微信公众号开发及时获取当前用户Openid及注意事项

微信公众号开发 - 静默授权获取用户信息

微信公众号开发及时获取当前用户Openid及注意事项

微信公众平台如何获取用户基本信息 java