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