axios+ token +refreshToken
需求
前端登录后,后端返回token和refresh_token,当token值过期的时候就用refresh_token去获得新的token
思路
为了防止多次刷新token值,如果refreshToken的接口还没有返回,此时如果再有一个请求进来,会再次执行refreshToken ,这样就会导致多次执行刷新token的接口,因此需要防止这个问题,我们可以在request.js中使用一个参数例如flag来标记当前是否为正在刷新的状态,如果正在刷新则不在调用refresh这个刷新token值的方法
为了解决同时发起两个或者两个以上的请求的时候,我们先将请求存到一个数组的队列中,让其出于等待中,等到token值得到后,在重新将队列发起队列的请求 如何解决?
我们可以借助Promise,将请求存进队列中,同时返回一个Promise ,让这个Promise一直出于Pending状态,(即不调用resolve),此时这个请求就会一直等啊等,只要我们不执行resolve,这个请求就会一直在等待。当刷新请求的接口返回来后,我们再调用resolve。
import axios from 'axios'
import { Loading, Message, MessageBox } from 'element-ui'
import api from './api'
import { getToken, setToken, removeToken, getRefreshToken } from '../utils/cookies'
let UserModule = {
RefreshToken: (data) => {
setToken('Bearer ' + data.access_token, data.refresh_token)
}
}
// 是否正在刷新的标记
let isRefreshing = false
// 重试队列,每一项将是一个待执行的函数形式
let retryRequests = []
const request = axios.create({
baseURL: api.baseUrl,
timeout: 50000,
withCredentials: true // cookie跨域必备
})
// http request 拦截器 Request
request.interceptors.request.use(
(config) => {
if (getToken()) {
config.headers['Authorization'] = getToken()
}
return config
},
(error) => {
Promise.reject(error)
}
)
// http response 拦截器 Response
request.interceptors.response.use(
(response) => {
// code == 0: 成功
const res = response.data
if (res.code !== 0) {
if (res.message) {
Message({
message: res.message,
type: 'error',
duration: 5 * 1000
})
}
return Promise.reject(res)
} else {
return response.data
}
},
(error) => {
if (!error.response) return Promise.reject(error)
// 根据refreshtoken重新获取token
// 1004访问此资源需要完全的身份验证
// 1001access_token无效
// 1002refresh_token无效
if (error.response.data.code === 1004 || error.response.data.code === 1001) {
const config = error.config
if (!isRefreshing) {
isRefreshing = true
return getRefreshTokenFunc()
.then((res) => {
// 重新设置token
UserModule.RefreshToken(res.data.data)
config.headers['Authorization'] = getToken()
// 已经刷新了token,将所有队列中的请求进行重试
retryRequests.forEach((cb) => cb(getToken()))
// 重试完清空这个队列
retryRequests = []
// 这边不需要baseURL是因为会重新请求url,url中已经包含baseURL的部分了
config.baseURL = ''
return request(config)
})
.catch(() => {
resetLogin()
})
.finally(() => {
isRefreshing = false
})
} else {
// 正在刷新token,返回一个未执行resolve的promise
return new Promise((resolve) => {
// 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
// @ts-ignore
retryRequests.push((token: any) => {
config.baseURL = ''
config.headers['Authorization'] = token
resolve(request(config))
})
})
}
} else if (error.response.data.code === 1002) {
resetLogin()
} else {
Message({
message: error.response.data.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
}
)
// 刷新token的请求方法
function getRefreshTokenFunc() {
let params = {
refresh_token: getRefreshToken() || ''
}
return axios.post(api.baseUrl + 'auth-center/auth/refresh_token', params)
}
function resetLogin(title = '身份验证失败,请重新登录!') {
if (window.location.href.indexOf('/login') === -1) {
MessageBox.confirm(title, '退出', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
removeToken()
location.reload() // To prevent bugs from vue-router
})
}
}
/**
* []请求
* @param params 参数
* @param operation 接口
*/
function customRequest(url: string, method: any, data: any) {
// service.defaults.headers['Content-Type']=contentType
let datatype = method.toLocaleLowerCase() === 'get' ? 'params' : 'data'
return request({
url: url,
method: method,
[datatype]: data
})
}
export { request, customRequest }