一、需求
此项目为小程序。小程序完成第一版token刷新设计思路是:根据接口调用返回的errorCode来判断当前用户的token和refreshToken是否过期。根据不同的errorCode,前端去调用接口完成token的刷新或者跳转到登录页面重新登录。
由于小程序的用户功能权限可以在后台管理系统中配置,但是小程序的用户权限变化后,没有办法通知到小程序去更新token或者提示用户重新登录。所以跟后端配合,需要前端增加一个定时调用刷新token接口的需求。比如:后端设置的token是10h过期,那前端就可以每隔5h去调用刷新接口去更新token。新的token接口会返回当前用户最新的权限。从而实现用户功能权限的更新。
二、实现思路
1、第一版代码(未增加定时刷新功能)接口封装如下
// 封装请求
const request = function(url, params) {
if (!url) {
console.log('请传入url......')
return
}
return new Promise((resolve, reject) => {
// to do
// 接口调用前 参数params封装
// 调用uniapp 请求接口
uni.request({
url: basePath + url, // 仅为示例,并非真实接口地址。
data: params.data,
header: {
'token': store.getters.getToken,
'content-type': params.contentType || 'application/x-www-form-urlencoded',
'refreshToken': store.getters.getRefreshToken,
},
method: params.method || 'POST',
}).then((uniData) => {
// uniData为一个数组
// 数组第一项为错误信息 即为 fail 回调
// 第二项为返回数据
const [err, uniRes] = uniData;
if (err) {
console.log('uni err-------------------:', err)
uni.showToast({
icon: 'none',
title: '服务异常,请稍后重试!'
})
return
}
const res = uniRes.data || {}
if (res.success) {
let resultData = res.data || {}
resolve(resultData)
} else {
const code = res.code
// to do
// 根据code处理异常
reject()
}
})
})
}
2. 实现定时刷新思路
2.1、记录用户信息刷新的时间(使用的时间戳),存vuex:timeOfUserInfo
在一切重新获取了用户权限信息(一定是token变更了)的接口处,记录当前的时间(记录的是用户本地的时间,也是用用户本地的时间去计算的时间差,当本地时间有误的时候,可能会出现接口没有调用的问题)。
2.2、判断是否调用refresh接口
接口A调用时,根据当前时间与timeOfUserInfo时间差,【1】如果超过3h,则先去调用refresh接口,同时挂起接口A.【2】如果没有超过3h,则直接调用
这里面存在2个问题
(1)、如果页面刷新调用接口时,有多个接口同时调用,那么就会refresh接口就会调用多次
这时,我们可以用一个变量isRefreshing(默认为false)来记录当前是否在调用刷新接口,调用refresh接口时,isRefreshing设置为true。refresh接口调用成功后,isRefreshing设置为false。
(2)、如何挂起promise
由于uni.request本来就是一个promise,我们可以直接设置他为pendding状态,放入队列中。等待refresh接口调用成功后,再充队列中将promise取出来,执行
代码如下:
1.模仿axios做了个,请求拦截器,返回拦截器
(1)、请求拦截器
beforeRequest(options = {}) {
const $this = this
return new Promise((resolve, reject) => {
options.url =
options.url.indexOf('http') === 0
? options.url
: this.baseURL + options.url
options.method = options.method || 'POST'
options.data = options.data || {}
// 封装自己的请求头
options.header = {
Authorization: store.getters.getToken,
refreshToken: store.getters.getRefreshToken
}
if (!options.isFile) {
// 非文件上传
options.header['content-type'] =
options.contentType || 'application/x-www-form-urlencoded'
}
$this.options = options
// 排除不需要走refresh token的接口
if (refreshTokenByTime.excludeUrls.indexOf(options.url) !== -1) {
resolve(options)
return
}
// 刷新时间到了后
if (refreshTokenByTime.needRefresh()) {
// 如果需要刷新token 并且当前token没有在刷新中,则去调用刷新接口
if (!refreshTokenByTime.isRefresh) {
// 触发refresh接口的接口,需要暂存起来
pendingRequests.push({
resolved: resolve,
options: options
})
refreshTokenByTime.isRefresh = true
refreshTokenByTime.refreshToken().then(() => {
// 接口调用成功后,将request中的接口都调用一遍
// 重点: 用于存储数据
pendingRequests.forEach(item => {
// 由于已经更新了token,则需要传新的token了
item.options.header.Authorization = store.getters.getToken
item.options.header.refreshToken = store.getters.getRefreshToken
item.resolved(item.options)
})
// 执行完成后,清空队列
pendingRequests = []
})
} else {
// 这里存储了resolve,变相的实现请求的挂起
//(只要没有resolved或rejected,请求就会一直处于pedding状态)
// 并将Promise状态的改变放到了外部一个对象来控制 ,
// 待定池缓存这个对象即可,待需要执行后续被拦截请求,
// 只需要利用这个对象引用的 resolved 来改变Promise状态即可实现请求挂起的放行
pendingRequests.push({
resolved: resolve,
options: options
})
return pendingRequests
}
} else {
if (options.isShowLoading) {
uni.showLoading({
title: options.loadingText || '加载中...',
mask: true
})
}
resolve(options)
}
})
},
(2)、响应拦截器
响应拦截器文章来源:https://www.toymoban.com/news/detail-493690.html
// 响应拦截器
handleResponse(data) {
const $this = this
uni.hideLoading()
return new Promise((resolve, reject) => {
const [err, uniRes] = data
if (err) {
console.log('uni err-------------------:', err)
uni.showToast({
icon: 'none',
title: '服务异常,请稍后重试!'
})
reject()
return
}
let res = uniRes.data || {}
if (typeof res === 'string') {
res = JSON.parse(res)
}
const options = {
url: $this.options.url,
resolve,
reject
}
// 由于传了options,里面包含了resolve和reject,requestCallback方法就是处理接口返回的一
// 些异常信息。同样是返回的promise
return requestCallback(res, options)
})
}
主要工作量在:请求拦截器中,如何做多接口的挂起操作。文章来源地址https://www.toymoban.com/news/detail-493690.html
三、代码实现
/**
* description: 普通请求接口封装
*/
import config from '@/common/base-config.js'
import store from '@/store/index'
import { requestCallback, refreshTokenByTime } from './requestCallback.js'
const basePath = config.url + config.gateway
let pendingRequests = [] // 接口排队队列
function requestObj(options) {
this.config = {
baseURL: basePath,
options: options,
// 请求拦截器
beforeRequest(options = {}) {
const $this = this
return new Promise((resolve, reject) => {
options.url =
options.url.indexOf('http') === 0
? options.url
: this.baseURL + options.url
options.method = options.method || 'POST'
options.data = options.data || {}
// 封装自己的请求头
options.header = {
Authorization: store.getters.getToken,
refreshToken: store.getters.getRefreshToken
}
if (!options.isFile) {
// 非文件上传
options.header['content-type'] =
options.contentType || 'application/x-www-form-urlencoded'
}
$this.options = options
// 排除不需要走refresh token的接口
if (refreshTokenByTime.excludeUrls.indexOf(options.url) !== -1) {
resolve(options)
return
}
// 刷新时间到了后
if (refreshTokenByTime.needRefresh()) {
// 如果需要刷新token 并且当前token没有在刷新中,则去调用刷新接口
if (!refreshTokenByTime.isRefresh) {
// 触发refresh接口的接口,需要暂存起来
pendingRequests.push({
resolved: resolve,
options: options
})
refreshTokenByTime.isRefresh = true
refreshTokenByTime.refreshToken().then(() => {
// 接口调用成功后,将request中的接口都调用一遍
// 重点: 用于存储数据
pendingRequests.forEach(item => {
// 由于已经更新了token,则需要传新的token了
item.options.header.Authorization = store.getters.getToken
item.options.header.refreshToken = store.getters.getRefreshToken
item.resolved(item.options)
})
// 执行完成后,清空队列
pendingRequests = []
})
} else {
// 这里存储了resolve,变相的实现请求的挂起
//(只要没有resolved或rejected,请求就会一直处于pedding状态)
// 并将Promise状态的改变放到了外部一个对象来控制 ,
// 待定池缓存这个对象即可,待需要执行后续被拦截请求,
// 只需要利用这个对象引用的 resolved 来改变Promise状态即可实现请求挂起的放行
pendingRequests.push({
resolved: resolve,
options: options
})
return pendingRequests
}
} else {
if (options.isShowLoading) {
uni.showLoading({
title: options.loadingText || '加载中...',
mask: true
})
}
resolve(options)
}
})
},
// 响应拦截器
handleResponse(data) {
const $this = this
uni.hideLoading()
return new Promise((resolve, reject) => {
const [err, uniRes] = data
if (err) {
console.log('uni err-------------------:', err)
uni.showToast({
icon: 'none',
title: '服务异常,请稍后重试!'
})
reject()
return
}
let res = uniRes.data || {}
if (typeof res === 'string') {
res = JSON.parse(res)
}
const options = {
url: $this.options.url,
resolve,
reject
}
return requestCallback(res, options)
})
}
}
// request 请求
// 下面最主要的一段代码,利用了promise的特性,当调用request方法时,先经过
// 请求拦截可以拿到请求参数,在请求之前做处理,请求函数会把处理之后的参数作为结果抛出
// 给了uni.request进行请求,uni.request执行完再次return了promise,接着执行响应
// 拦截.then中的 response函数,response函数接收到uni请求响应的结果作为res传递给
// 响应拦截(这里为什么能拿到uni的响应结果因为uni返回的也是promise),在response
// 就可以对响应的数据处理后再进行promise的返回
this.request = function request(options = {}) {
return this.config
.beforeRequest(options)
.then(opt => {
return uni.request(opt)
})
.then(res => this.config.handleResponse(res))
}
this.requestFile = function requestFile(options = {}) {
return this.config
.beforeRequest(options)
.then(opt => {
opt.formData = opt.data
return uni.uploadFile(opt)
})
.then(res => this.config.handleResponse(res))
}
}
module.exports = requestObj
到了这里,关于uniapp 前端定时刷新token,接口排队等待,promise 接口封装的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!