用的是这个开发框架:uview-template 集成uview-ui 的基础项目开发框架 - DCloud 插件市场
逻辑:
1、请求接口发现token过期或失效。在拦截器response里通过code判断是否需要刷新token。
2、刷新token,调用刷新接口。
3、在刷新接口没有返回token之前,把其他调用的接口存到数组里,pending状态。
4、接口返回token之后,重新调用数组里的接口,完成无感刷新token。
看到网上无感刷新token的版本都是这么写的FAQ | UNI-AJAX
javascript
import ajax from 'uni-ajax'
import refreshToken from './refresh'
// 创建实例
const instance = ajax.create({
baseURL: 'https://www.example.com/api'
})
// 添加请求拦截器
instance.interceptors.request.use(config => {
// 给每条请求赋值 Token 请求头
config.header['Authorization'] = uni.getStorageSync('TOKEN')
return config
})
// 添加响应拦截器
instance.interceptors.response.use(
response => {
// 假设接口返回的 code === 401 时则需要刷新 Token
if (response.data.code === 401) {
return refreshToken().then(() => instance(response.config))
}
return response
},
error => {
return Promise.reject(error)
}
)
export default instance
对我来说有几个难点:
1、instance.interceptors.response.use()
2、response里return refreshToken().then(() => instance(response.config))
先不管,照抄。
主要就是这句代码:return refreshToken().then(() => instance(response.config))
抄过来发现response.config在uview的响应拦截里取不到。
在uview里拦截器是这么写的Http请求 | uView - 多平台快速开发的UI框架 - uni-app UI框架
javascript
// /common/http.interceptor.js
// 这里的vm,就是我们在vue文件里面的this,所以我们能在这里获取vuex的变量,比如存放在里面的token变量
const install = (Vue, vm) => {
// 此为自定义配置参数,具体参数见上方说明
Vue.prototype.$u.http.setConfig({
baseUrl: 'https://api.example.com',
loadingText: '努力加载中~',
loadingTime: 800,
// ......
});
// 请求拦截,配置Token等参数
Vue.prototype.$u.http.interceptor.request = (config) => {
config.header.token = vm.token;
// 可以对某个url进行特别处理,此url参数为this.$u.get(url)中的url值
if(config.url == '/user/login') config.header.noToken = true;
// 最后需要将config进行return
return config;
// 如果return一个false值,则会取消本次请求
// if(config.url == '/user/rest') return false; // 取消某次请求
}
// 响应拦截,判断状态码是否通过
Vue.prototype.$u.http.interceptor.response = (res) => {
if(res.code == 200) {
// res为服务端返回值,可能有code,result等字段
// 这里对res.result进行返回,将会在this.$u.post(url).then(res => {})的then回调中的res的到
// 如果配置了originalData为true,请留意这里的返回值
return res.result;
} else if(res.code == 201) {
// 假设201为token失效,这里跳转登录
vm.$u.toast('验证失败,请重新登录');
setTimeout(() => {
// 此为uView的方法,详见路由相关文档
vm.$u.route('/pages/user/login')
}, 1500)
return false;
} else {
// 如果返回false,则会调用Promise的reject回调,
// 并将进入this.$u.post(url).then().catch(res=>{})的catch回调中,res为服务端的返回值
return false;
}
}
}
export default {
install
}
然后要在main.js中引用
javascript
import httpInterceptor from '@/common/http.interceptor.js'
Vue.use(httpInterceptor, app)
这个时候有两个解决方案,一个就是按照别人的写法,把拦截器都改了。但是因为这是个已经完成的项目,要改的话,相当于重构了。放弃这个方案;
还有一个就是去看下uview封装的http,里面是怎么处理的,是不是可以自己多加个config的返回。
看了下,还真可以加,加上之后响应里就有了config,问题解决了。注意,这里只改了originalData=false的返回参数。
javascript
// http.interceptor.js
import refreshToken from './getToken'
const install = (Vue, vm) => {
// 此为自定义配置参数,具体参数见上方说明
Vue.prototype.$u.http.setConfig({
baseUrl: 'XXXX', // 请求域名
method: 'GET',
// 设置为json,返回后会对数据进行一次JSON.parse()
dataType: 'json',
showLoading, // 是否显示请求中的loading
loadingText, // 请求loading中的文字提示
loadingTime, // 在此时间内,请求还没回来的话,就显示加载中动画,单位ms
originalData: false, // 是否在拦截器中返回服务端的原始数据
loadingMask: true, // 展示loading的时候,是否给一个透明的蒙层,防止触摸穿透
// 配置请求头信息
header: {
'content-type': contentType
},
});
// 请求拦截,配置Token等参数
Vue.prototype.$u.http.interceptor.request = (config) => {
config.header[headerTokenName] = vm.token ? vm.token : '';
return config;
}
// 响应拦截,判断状态码是否通过
// config 修改uview-ui/libs/request/index/js的返回options
Vue.prototype.$u.http.interceptor.response = (res, config) => {
if (res[codeName] == successCode) {
return res.data;
} else if ( res[codeName] == invalidCode1 || res[codeName]==invalidCode2) {
let token = vm.token
if(token != '' && token != null && token != undefined){
// 更新token 重点代码!!!!!!!!
return refreshToken(vm).then( () => vm.$u.http.request(config))
}else{
vm.$u.vuex('token', '')
vm.$u.vuex('isLogin', false)
vm.$u.vuex('userInfo', '')
vm.$u.func.showToast({
title: '需要您先登录一下~',
success: () => {
vm.$u.route('/pages/login/login')
}
})
}
return false
}else{
vm.$u.toast(res.errmsg)
return false;
}
}
}
export default {
install
}
然后重点就在于实现第二步和第三步。先看下别人写的。
javascript
import request from './ajax'
let isRefreshing = false // 当前是否在请求刷新 Token
let requestQueue = [] // 将在请求刷新 Token 中的请求暂存起来,等刷新 Token 后再重新请求
// 执行暂存起来的请求
const executeQueue = error => {
requestQueue.forEach(promise => {
if (error) {
promise.reject(error)
} else {
promise.resolve()
}
})
requestQueue = []
}
// 获取 Token 请求
const getToken = () => request.post('/oauth/token')
// 刷新 Token 请求处理,参数为刷新成功后的回调函数
const refreshToken = () => {
// 如果当前是在请求刷新 Token 中,则将期间的请求暂存起来
if (isRefreshing) {
return new Promise((resolve, reject) => {
requestQueue.push({ resolve, reject })
})
}
isRefreshing = true
return new Promise((resolve, reject) => {
getToken()
// 假设请求成功接口返回的 code === 200 为刷新成功,其他情况都是刷新失败
.then(res => (res.data.code === 200 ? res : Promise.reject(res)))
.then(res => {
uni.setStorageSync('TOKEN', res.data.data)
resolve()
executeQueue(null)
})
.catch(err => {
uni.removeStorageSync('TOKEN')
reject(err)
executeQueue(err || new Error('Refresh token error'))
})
.finally(() => {
isRefreshing = false
})
})
}
export default refreshToken
uview里不是用的ajax,是用的uni-app的请求方式,改下请求方式和接口。
按照下面的代码照抄就行。
只要把请求的接口和接口返回的之后的数据处理改下就行。
区分了微信和企业微信,因为我们的小程序需要在微信和企业微信里都可使用。
javascript
let isRefreshing = false // 当前是否在请求刷新 Token
let requestQueue = [] // 将在请求刷新 Token 中的请求暂存起来,等刷新 Token 后再重新请求
// 执行暂存起来的请求
const executeQueue = error => {
requestQueue.forEach(promise => {
if (error) {
promise.reject(error)
} else {
promise.resolve()
}
})
requestQueue = []
}
// 获取 Token 请求
const getopenid = (params = {}) => uni.$u.get('/bbm/applet/auc/getInfo', params);
//企业微信登陆 获取TOKEN 企业微信调用
const OnLogin = (params = {}) => uni.$u.get('/bbm/applet/auc/getCpToken', params);
// 刷新 Token 请求处理,参数为刷新成功后的回调函数
const refreshToken = (vm) => {
// debugger
// 如果当前是在请求刷新 Token 中,则将期间的请求暂存起来
if (isRefreshing) {
return new Promise((resolve, reject) => {
// debugger
requestQueue.push({
resolve,
reject
})
console.log(requestQueue)
})
}
isRefreshing = true
return new Promise((resolve, reject) => {
debugger
uni.login({
provider: 'weixin',
success: login => {
console.log("已进入刷新token", login.code)
let that = this;
//企业微信端登陆
wx.getSystemInfo({
success(res) {
if (res.environment == 'wxwork') { //企业微信端
// uni.showModal({
// title:"开始调用wx.qy.login",
// showCancel:false
// })
wx.qy.login({
success: result => {
if (result.code) {
OnLogin({
agentid: 1000098,
code: res.code
})
.then(res => {
if (res.token) {
vm.$u.vuex('token', res.token)
vm.$u.vuex('isLogin', true)
vm.$u.vuex('userInfo', res)
// debugger
resolve()
executeQueue(null)
}
}).catch(err => {
uni.removeStorageSync('lifeData')
reject(err)
executeQueue(err || new Error('Refresh token error'))
}).finally(() => {
isRefreshing = false
})
}
}
})
} else {
// debugger
getopenid({
code: login.code
}).then(res => {
uni.hideLoading()
// debugger
if (res.token) {
vm.$u.vuex('token', res.token)
vm.$u.vuex('isLogin', true)
vm.$u.vuex('userInfo', res)
// debugger
resolve()
executeQueue(null)
}
})
.catch(err => {
uni.removeStorageSync('lifeData')
reject(err)
executeQueue(err || new Error('Refresh token error'))
})
.finally(() => {
isRefreshing = false
})
}
}
})
},
fail: () => {
this.$refs.vToast.show({
title: '登录失败,请重试',
type: 'warning',
position: 'top'
})
}
})
})
}
export default refreshToken
参考: