任何一款软件,网络请求是必不可少的。这里我提一下,如果你之前有项目,比如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
好了,至此符合我项目需求的网络请求,总算封装完毕了,应该还有待优化,不过目前来看,足够使用了。
不过在更新云数据库时,会有权限的问题,官方使用云函数就能完全解决,好吧,接下去让我们改造一下吧,下一节在讲。
第四篇:和我一起学习微信小程序(四),使用云函数完善网络请求