和我一起学习微信小程序(三),封装网络请求

任何一款软件,网络请求是必不可少的。这里我提一下,如果你之前有项目,比如web等,必须是前后端分离的架构,否则小程序是无法请求你现有项目的数据,除非你单独另外再为小程序开发一套API。这也是所以为什么现在前后端分离技术那么重要,一个API,可以对应web、小程序、移动端、app等。

我的个人网站,使用node开发的后端API,所以小程序自然也是请求这一套后端程序了。

官方的wx.request函数,不支持promise,始终不明白为什么不支持?非要我们自己封装。

wx.request({
  url: 'example.php', //仅为示例,并非真实的接口地址
  data: {
    x: '',
    y: ''
  },
  header: {
    'content-type': 'application/json' // 默认值
  },
  success (res) {
    console.log(res.data)
  }
})

当请求成功后,会在回调函数success里执行,如果多层请求,那么就会形成回调地狱,网上封装promise的方法很多,我也是大致借鉴了一点,然后结合自己的项目,进行了一些修改。

首先utits文件夹,新建http.js和refushtoken.js两个文件,前者就是封装wx.request的,后者是我根据自己项目需求,实现刷新token的。

和我一起学习微信小程序(三),封装网络请求

由于我项目的后端API,需要通过header添加token来验证合法性,但此token会保存在服务端的redis数据库中,因为会去比较请求的token是否和redis保存的token一致,所以同一个用户只能同时存在一个token。而且此token有过期时间,所以必须定期刷新token,详见:本网站的架构(三)API接口

另外,正因为同一个用户ID(此用户非微信登录用户,指的是小程序这个客户端本身,相对于后端来说就是一个用户ID)只能保存一个token,那么此时可能很多人跟我想得一样,小程序有数据缓存啊,通过wx.setStorageSync

可以把token缓存到客户端啊,这样每次从缓存读取token就行了啊?

其实完全不行,因为我的token是需要访问后端api来生成的,每次生成的都不同的,如果张三访问后生成的token是‘aaaa’,那么李四访问后生成的token是‘bbbb’,那么后者token就会覆盖前者token,导致张三不能再访问。

所以总结一下,只能把token放在一个公共媒介里,所以用户访问小程序,都是使用这个token。

其实以上这些原理可能比较难理解,其实我网站的前台部分,用.netcore开发的.netcore这个客户端访问后端api的时候,也是相对于一个用户ID,我把生成的token保存在一个静态变量里,那么无论多少网友访问网站,.net都会从静态变量里取出这个token去访问后端。

但是小程序不存在这样的一个公共媒介,后来研究了一下,云数据库可以实现,即把请求来的token都保存在云数据库里,当有网友打开小程序时,直接从云数据库里获得token,即可。

大致的逻辑就是这样,可能并不是一个最好的解决方案,如果大家有什么好的建议,也可以给我留言,以下是代码:

import config from '../config'
import refushtoken from 'refushtoken'
const {
  pubUrl,
  databasetokenID
} = config 
//这是我要请求的数据接口的公共部分
let requestTimes = 0
const http = (options) => {
  requestTimes++
  wx.showLoading({
    title: '加载中',
    mask: true
  })
  return new Promise((resolve, reject) => {
    const db = wx.cloud.database()
    db.collection('localtoken').doc(databasetokenID).get()
      .then((dbres) => {
        // 先从云数据库获取token
        // 之前手动在云数据库中添加了一条token
        // 所以_id是写死在config里了
        const expiresIn = Number.parseInt(dbres.data.expiresIn)
        const createtime = Number.parseInt(dbres.data.createtime)
        const currtime = Math.floor(Date.now() / 1000)
        // console.log(expiresIn + createtime)
        // console.log(currtime + expiresIn)
        if (expiresIn + createtime < currtime + expiresIn / 2) {
          // 如果离过期时间还有一半,则刷新token
          refushtoken()
        }
        return dbres.data.access_token
      })
      .then((token) => {
        // console.log(token)
        wx.request({
          url: pubUrl + options.url,
          method: options.method || 'get',
          data: options.data || {},
          header: {
            'authorization': token
          },
          success(res) {
            if (res.statusCode === 200) {
              //如果状态正确,则返回数据
              resolve(res.data)
            } else if (res.data.code === 10006) {
              refushtoken()
              reject(res)
            } else {
              reject(res)
            }
          },
          fail(error) {
            reject(error)
          },
          complete() {
            requestTimes--
            if (requestTimes == 0) {
              wx.hideLoading({
                success: (res) => {}
              })
            }
          }
        })
      })
      .catch((err) => {
        console.log(err)
      })
  })
}
module.exports = http

当token时间只有一半的时候,或者发生后端特殊错误代码时,就会重新刷新token,refushtoken.js代码如下:

import config from '../config'
const {
  pubUrl,
  apikey,
  userID,
  databasetokenID
} = config 
//这是我要请求的数据接口的公共部分

const refushtoken = () => {
  console.log('开始更新')
  wx.request({
    url: `${pubUrl}/common/token?id=${userID}&key=${apikey}`,
    success(res) {
      const {
        access_token,
        expiresIn,
        createtime
      } = res.data.data
      //console.log(createtime)
      wx.cloud.database().collection('localtoken')
        .doc(databasetokenID)
        .update({
          data: {
            access_token,
            expiresIn,
            createtime
          }
        })
        .then((res) => {
          console.log('更新token成功', res)
        })
        .catch((err) => {
          console.log('更新token失败', err)
        })
    }
  })
}
module.exports = refushtoken

好了,至此符合我项目需求的网络请求,总算封装完毕了,应该还有待优化,不过目前来看,足够使用了。

不过在更新云数据库时,会有权限的问题,官方使用云函数就能完全解决,好吧,接下去让我们改造一下吧,下一节在讲。

第四篇:和我一起学习微信小程序(四),使用云函数完善网络请求

上一篇:微信小程序实现富文本编辑器


下一篇:微信小游戏排行榜功能快速开发教程