单个接口的刷新token很好解决,难点是多接口并发
首先将token过期后的请求存到一个数组队列中,想办法让这个请求处于等待中,一直等到刷新token后再逐个重试清空请求队列。
那么如何做到让这个请求处于等待中呢?为了解决这个问题,我们得借助Promise。将请求存进队列中后,同时返回一个Promise,让这个Promise一直处于Pending状态(即不调用resolve或reject),此时这个请求就会一直等啊等,只要我们不执行resolve或reject,这个请求就会一直在等待。当刷新请求的接口返回来后,我们再调用resolve或reject,逐个重试
基于nuxt框架,$axios模块,伪代码如下:
import { Message, Loading } from "element-ui"
import Vue from "vue"
export default ({ $axios, store, app, redirect, route, req, res }) => {
let [loadingCount, currencyCode, currentLanguage, isRefreshing, token] = [
0,
"",
"",
true,
""
]
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (error.response && code === 401) {
if (!token) {
if (error.response.config.headers.GoLogin) {
goLogin()
return
}
return
}
if (isRefreshing) {
// 刷新token
refreshToken(error.response.config.headers.GoLogin)
}
isRefreshing = false
// 将请求挂起(返回一个pending状态的promise,等待刷新接口返回结果后,再调用resolve或reject)
const retryOriginalRequest = new Promise((resolve, reject) => {
addSubscriber(token => {
if (!token) {
/* eslint-disable */
return reject("token refresh error")
/* eslint-enable */
}
if (error.response && token) {
const config = error.response.config
config.headers.Authorization = `Bearer ${token}`
$axios.request(config).then(res => {
resolve(res)
})
}
})
})
return retryOriginalRequest
}
return Promise.reject(error)
})
async function refreshToken(config) {
let host = ""
if (process.server) {
host = store.state.host || ""
} else {
host = ""
}
const data = await $axios
.$post(`${host}/account/refresh-token`, {}, { baseURL: "" })
.catch(err => {
// 清空store里面的用户信息
store.commit("setUser", {})
// reject
onAccessTokenFetched("")
if (config) {
goLogin()
}
return Promise.reject(err)
})
let expireIn = ""
if (data && data.code === 0) {
token = data.data.token
expireIn = data.data.expires_in
if (process.server) {
const stringObject = req.headers.cookie
req.headers.cookie = replaceParamVal(stringObject, "token", token)
res.setHeader(
"Set-Cookie",
`token=${token};Domain=.heavengifts.com;Path=/;Max-Age=${expireIn}`
)
}
// 刷新token成功,执行数组里的函数,重新发起被挂起的请求(resolve)
onAccessTokenFetched(token)
} else {
// 清空store里面的用户信息
store.commit("setUser", {})
// reject
onAccessTokenFetched("")
if (config) {
goLogin()
}
}
// app.$cookies.set("token", token)
isRefreshing = true
}
// 替换新token
function replaceParamVal(stringObject, paramName, replaceWith) {
const str = stringObject.replace(/\s/g, "")
/* eslint-disable */
const re = eval('/('+ paramName+'=)([^;]*)/gi')
/* eslint-enable */
const newParam = str.replace(re, paramName + "=" + replaceWith)
return newParam
}
// 被挂起的请求数组
let subscribers = []
// push所有请求到数组中
function addSubscriber(callback) {
subscribers.push(callback)
}
// 刷新请求(subscribers数组中的请求得到新的token之后会自执行,用新的token去请求数据)
function onAccessTokenFetched(token) {
subscribers.forEach(callback => {
callback(token)
})
subscribers = []
}
function goLogin() {
if (process.server) {
redirect(`/account/login`)
} else {
window.location.href = `/account/login`
}
}
}