关键思路
accesstoken=短token; refreshtoken=长token; 白名单接口不携带token,不参与刷新; 401时拦截请求,长token去换短token
白名单
// 不需要携带 AccessToken 的接口
const whiteList = [
"/login",
"/refresh_token"
]
function isWhiteList(url?: string) {
if (!url) return false
return whiteList.some(item => url.includes(item))
}
关键变量
let isRefreshing = false
let requests: Array<{
resolve: (token: string) => void
reject: (err: any) => void
}> = []
请求拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const reqConfig = config as RequestConfig
if (reqConfig.showLoading ?? requestConfig.showLoading) {
showLoading()
}
// 白名单接口不携带AccessToken
if (!isWhiteList(config.url as string)) {
const token = getToken()
if (token) {
config.headers.set("Authorization",`Bearer ${token}`)
}
}
return config
},
error => Promise.reject(error)
)
响应拦截器
service.interceptors.response.use(
(response: AxiosResponse<ApiResponse>) => {
const config = response.config as RequestConfig
if (config.showLoading ?? requestConfig.showLoading) {
hideLoading()
}
const { code, msg } = response.data
// 后端业务失败(不是401)
if (code !== 200) {
if (config.showErrorMessage ?? requestConfig.showErrorMessage) {
ElMessage.error(msg || '请求失败')
}
return Promise.reject(response.data)
}
// 成功提示
if (config.showSuccessMessage ?? requestConfig.showSuccessMessage) {
ElMessage.success(msg || '操作成功')
}
return response
},
async (error: any) => {
const config = (error?.config || {}) as RequestConfig
if (config.showLoading ?? requestConfig.showLoading) {
hideLoading()
}
const status = error?.response?.status
// 非401
if (status !== 401) {
handleHttpError(status, config.showErrorMessage ?? requestConfig.showErrorMessage)
return Promise.reject(error)
}
/**
* refresh接口自己401
* 或 login接口401
* 不允许再次刷新
*/
if (isWhiteList(config.url as string)) {
handleLogout()
return Promise.reject(error)
}
/**
* 第一个401
*/
if (!isRefreshing) {
isRefreshing = true
try {
const { accessToken, refreshToken: newRefreshToken } = await refreshToken()
setToken(accessToken)
setRefreshToken(newRefreshToken)
/**
* 唤醒所有等待请求
*/
requests.forEach((item) => {
item.resolve(accessToken)
})
requests = []
/**
* 当前请求重新发送
*/
config.headers = config.headers || {}
config.headers.Authorization = `Bearer ${accessToken}`
return service.request(config)
} catch (e) {
/**
* 通知所有等待请求失败
*/
requests.forEach((item) => {
item.reject(e)
})
requests = []
handleLogout()
return Promise.reject(e)
} finally {
isRefreshing = false
}
}
/**
* 已经有人在刷新Token
* 当前请求进入等待队列
*/
return new Promise((resolve, reject) => {
requests.push({
resolve: (token: string) => {
config.headers = config.headers || {}
config.headers.Authorization = `Bearer ${token}`
resolve(service.request(config))
},
reject,
})
})
},
)
封装函数
const ACCESS_TOKEN = "token"
const REFRESH_TOKEN = "refreshToken"
function getToken() {
return localStorage.getItem(ACCESS_TOKEN)
}
function getRefreshToken() {
return localStorage.getItem(REFRESH_TOKEN)
}
function setToken(token: string) {
localStorage.setItem(ACCESS_TOKEN, token)
}
function setRefreshToken(token: string) {
localStorage.setItem(REFRESH_TOKEN, token)
}
/**
* 刷新Token
*/
async function refreshToken() {
const refreshToken = getRefreshToken()
return service.post("/refresh_token", {
refreshToken
})
}
logout
function handleLogout() {
localStorage.removeItem(ACCESS_TOKEN)
localStorage.removeItem(REFRESH_TOKEN)
router.replace({
name: "login"
})
}
HTTP错误
这里不用处理401。
function handleHttpError(
status: number,
showError: boolean
) {
const map: Record<number, string> = {
400: "请求错误",
403: "拒绝访问",
404: "接口不存在",
500: "服务器错误"
}
if (showError) {
ElMessage.error(
map[status] || "网络错误"
)
}
}
