大家使用
axios
最多的情况应该是和vue
配套开发,所以在实际开发的时候我们是可以做一套axios
封装的。方便我们管理http
请求部分的代码。
参考:axios如何利用promise无痛刷新token vue前端刷新token
写在前面
因为公司有保密软件,所以代码是不允许复制的,然后博客中的代码时我对照手敲的,可能存在一些变量啥的或者什么东西落下的(因为只是针对博客出代码,自己项目的代码不可能全部对照敲出来的,只能说我的代码是实际运用在项目中的)希望大家可以指出来,我会实时修改的。
├── src
├── request
├── urls.js
├── http.js
├── api.js
urls.js
:这个是我用来管理所有的接口地址的,大家可以根据自己的需求来决定需不需要
export default {
base: {
/**登录相关 */
signIn: '/ums/web/login',//登录
signOut: '/ums/web/logout',//退出
refresh: '/ums/web/token/refresh',//刷新token
..........
}
}
http.js
:这个就是最主要的封装文件
/**axios封装
* 请求拦截、相应拦截、错误统一处理
*/
import axios from 'axios';
import router from '@/router'
// 创建axios实例
var instance = axios.create({
timeout: 1000 * 12
});
// 设置post请求头
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
/**
* 跳转登录页
* 携带当前页面路由,以期在登录页面完成登录后返回当前页面
*/
const toLogin = () =>{
// 清除用户登录信息
window.localStorage.clear();
router.replace({
path: '/login'
});
}
/**
* 请求失败后的错误统一处理
* @param {Number} status 请求失败的状态码
*/
const errorHandle = (status, other) = >{
switch (status) {
// 401: 未登录状态,跳转登录页
case 401:
toLogin();
break;
case 403:
setTimeout(() =>{
toLogin();
},
1000);
break;
ase 404 : break;
default:
console.log(other);
}
}
//请求拦截器
instance.interceptors.request.use(req =>{
//这里可以根据项目需求在请求前进行拦截处理并返回req
//例子(为所有请求加上token)
let token = localStorage.getItem('token') || '';
req.headers.common['authorization'] = token
return req;
})
// 响应拦截器
instance.interceptors.response.use(
//http请求200
res =>{
if (res.status === 200) {
//这里是我和后台定的状态码
if (res.data.code == 3000 || res.data.code == 2000) {
return Promise.resolve(res)
} else {
//退出成功,未认证
if (res.data.code == 2006 || res.data.code == 2001) {
toLogin();
} else {//未知错误
return Promise.reject(res)
}
}
} else {
return Promise.reject(res)
}
},
// 请求失败
error =>{
const {
response
} = error;
if (response) {
// 请求已发出,但是不在2xx的范围
errorHandle(response.status, response.data.message);
return Promise.reject(response);
} else {
// 处理断网的情况
return Promise.reject(` {
status: -1,
message: '断网了',
data: null
}`);
}
});
export default instance;
api.js
:这里是我们调用封装后的axios
然后暴露方法出去的地方
// 导入http中创建的axios实例
import axios from './http';
// urls接口地址
import urls from './urls'
const base = urls.base;
//登录
const login = {
signIn: (params) => { return axios.post(`${base.signIn}`, params) },
signOut: (params) => { return axios.get(`${base.signOut}`, params) },
refreshToken: (params) => { return axios.get(`${base.refresh}`, { params: params }) }
}
export default {
login,
}
main.js
import api from '@/request/api' // 导入api接口
Vue.prototype.$api = api; // 将api挂载到vue的原型上
login.vue
methods:{
login(){
const _this = this;
this.$api.login
.signIn({
username:this.username,
password:this.password
})
.then(res=>{
//存储token和失效时间
localStorage.setItem("isLogin", true); //是否登录状态
localStorage.setItem("token", res.data.data.authorization); //token
localStorage.setItem(
"expired_time",
res.data.data.expired_time
); //过期时间
_this.$router.push({ path: "/home" });
})
}
}
因为token存在失效时间(我们定义的token是登录的时候就定死了失效时间,所以过了失效时间后是需要刷新重新获取新的token和新的token失效时间)。所以就存在可能用户断断续续操作了一段时间再点击就提示token失效了,这肯定是不是很友好,所以我就需要token静默刷新,让用户体验更好一点。
我也是网上找了很多资料才弄好的并且理解了这种做法(请原谅我的渣),前面我也贴出了我的参考博客,虽然有照抄嫌疑,但是我刚开始只是照搬发现不对,然后后面慢慢实验并且理解他的写法之后才成功的。
这里我是将检测是否token失效放在了请求拦截器中,我觉得这里更好控制,也有博主放在响应拦截器中,我上面也贴了博客地址。
http.js
(token刷新)
import urls from './urls'
const base = urls.base;
const base = urls.base;
// 判断token是否失效
const checkToken = () =>{
let expired_time = localStorage.getItem('expired_time') || '';
let key = false;
if (expired_time) {
let nowTime = new Date();
let eprTime = new Date(expired_time);
key = nowTime.getTime() > eprTime.getTime();
}
return key;
}
let isRefreshToken = false; //防止同一时间段多次刷新
/*存储请求的数组*/
let refreshSubscribers = []
/*将所有的请求都push到数组中,其实数组是[function(token){}, function(token){},...]*/
function subscribeTokenRefresh(cb) {
refreshSubscribers.push(cb);
}
/*数组中的请求得到新的token之后自执行,用新的token去请求数据*/
function onRrefreshed(token) {
refreshSubscribers.map(cb =>cb(token));
}
// 刷新token
function refreshToken() {
return new Promise((resolve, reject) =>{
axios.get(base.refresh, {
params: {
token: localStorage.getItem('token')
},
headers: {
'authorization': localStorage.getItem('token')
} //设置header信息
}).then(res =>{
if (res.data.code == 3000) {
// 重新存储token和失效时间
localStorage.setItem('token', res.data.data.authorization);
localStorage.setItem("expired_time", res.data.data.expired_time);
resolve();
} else {
if (res.data.code == 2001) { //未认证
toLogin();
}
reject()
}
}).
catch(error =>{
reject()
})
})
}
// 请求拦截器
instance.interceptors.request.use((req) =>{
// 登录状态
let isLogin = localStorage.getItem('isLogin');
let token = localStorage.getItem('token') || '';
req.headers.common['authorization'] = token
let isExpired = checkToken();
if (isLogin && isExpired) { //已登录的话判断是否token是否过期
if (!isRefreshToken) { //是否正在刷新
isRefreshToken = true;
refreshToken().then(res =>{
// 重新设置token
let token = localStorage.getItem('token') || '';
onRrefreshed(token) refreshSubscribers = []
}).
catch(error =>{ //请求失败的话重新登陆
// console.log(error)
refreshSubscribers = [] toLogin();
}).
finally(() =>{
isRefreshToken = false;
});
}
/*将请求挂起 callback回调函数执行 等待token刷新完成return promise对象*/
let retry = new Promise((resolve, reject) =>{
subscribeTokenRefresh((token) =>{
req.headers.common['authorization'] = token;
resolve(req)
})
});
return retry;
} else {
// return req;
return Promise.resolve(req);
}
}
这里主要是运用了promise
和callback
。使得请求能够在进来后使用promise
让请求挂起(pending状态
)然后刷新token
后通过callback
重新发起请求并且将promise
状态变为resolve
。
因为请求拦截器返回出来的是一个promise对象,所以return Promise resolve(req)
和return req
是一样的,这样就保证了下面retry
函数返回出去后可以保证后续执行。
这里也保证了同一时间段(token
失效)不管进来多少请求都会被挂起,只有等到token
重新刷新完成后才会逐个重启请求。
也不知道我的理解有没有问题。