最近上班遇到的新需求,token无感刷新,参考了很多博客,也看了渡一老师的视频,功能是实现了,但是发现重新请求后页面数据没有更新
遇到相同问题的先理清代码执行顺序和Promise,看看执行结果有没有resolve()出去。
话不多说,直接上代码,因为自己封装的请求和大家的不一样,仅供参考
无感刷新token在请求拦截这里
import type {
AxiosInstance,
AxiosRequestConfig,
AxiosResponse,
InternalAxiosRequestConfig,
RawAxiosResponseHeaders
} from 'axios'
import axios from "axios";
import {ElMessage} from "element-plus";
import type {responseDataType} from "@/types/common";
import {ElLoading} from 'element-plus'
import {refreshToken} from "@/api/system/role";
import {logout} from "@/api/login";
const BASE_URL = import.meta.env.VITE_API_BASE_URL
class Request {
baseConfig: AxiosRequestConfig = {
baseURL: BASE_URL,
timeout: 60000
}
// 是否正在刷新中
isRefreshToken = false
// 需要忽略的提示。忽略后,自动 Promise.reject('error')
ignoreMsg = [
'无效的刷新令牌', // 刷新令牌被删除时,不用提示
'刷新令牌已过期' // 使用刷新令牌,刷新获取新的访问令牌时,结果因为过期失败,此时需要忽略。否则,会导致继续 401,无法跳转到登出界面
]
// 请求队列
requestList: any[] = []
header: RawAxiosResponseHeaders | null = null
private async handleLogout() {
const {data} = await logout()
if (data) {
setTimeout(function () {
localStorage.clear()
sessionStorage.clear()
location.href = '/'
}, 1000)
}
}
private httpRequest<T>(config: AxiosRequestConfig): Promise<T> {
const instance: AxiosInstance = axios.create()
//请求拦截
instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
const token = localStorage.getItem("token")
if (config.data instanceof FormData) {
config.headers['Content-Type'] = 'multipart/form-data'
} else {
config.headers['Content-Type'] = 'application/json;charset=utf-8';
}
if (token) {
config.headers!.Authorization = `Bearer ${token}`;
}
return config
}, (error: any) => {
return Promise.reject(error);
})
//响应拦截、无感刷新在这
instance.interceptors.response.use((response: AxiosResponse) => {
// 未设置状态码则默认成功状态
const code = response.data.code || 0
// 获取错误信息
const msg = response.data.msg
return new Promise(async (resolve, reject) => {
if (code === 0) {
this.header = response.headers
resolve(response.data)
} else {
if (code === 401) {
if (!this.isRefreshToken) {
this.isRefreshToken = true
const token = localStorage.getItem('refresh_token')
if (token === null) {
ElMessage.error('认证失败,请重新登录')
return this.handleLogout()
} else {
try {
const {code, data} = await refreshToken(token)
if (code !== 0) return this.handleLogout()
// 刷新成功,更新 token 并重新发送请求
localStorage.setItem('token', data.accessToken);
localStorage.setItem('refresh_token', data.refreshToken)
const newConfig = {...response.config};
newConfig.headers.Authorization = `Bearer ${data.accessToken}`;
this.requestList.forEach((cb: any) => {
cb()
})
this.requestList = []
return instance(newConfig)
} catch (e) {
// 2.2 刷新失败,只回放队列的请求
this.requestList.forEach((cb: any) => {
cb()
})
// 提示是否要登出。即不回放当前请求!不然会形成递归
return this.handleLogout()
} finally {
this.requestList = []
this.isRefreshToken = false
}
}
} else {
// 添加到队列,等待刷新获取到新的令牌
return this.requestList.push(() => {
const newConfig = {...response.config};
newConfig.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
resolve(instance(newConfig));
});
}
} else if (code === 1003005001) {
ElMessage.error('学生导入失败')
reject(response.data)
} else if (this.ignoreMsg.indexOf(msg) !== -1) {
// 如果是忽略的错误码,直接返回 msg 异常
return Promise.reject(msg)
} else {
ElMessage.error(msg)
reject(response.data)
}
}
})
}, error => {
let {message} = error
if (message === 'Network Error') {
message = '请求异常,请联系系统管理员或请稍后重试'
} else if (message.includes('Unable to find')) {
message = '服务未找到,请稍后重试'
} else if (message.includes('code 404')) {
message = '请求接口不存在,请联系管理员'
} else if (message.includes('timeout')) {
message = '系统接口请求超时,请稍后重试'
} else if (message.includes('Request failed with status code')) {
message = error.response.data.message
}
ElMessage.error(message)
return Promise.reject(error)
})
return instance(Object.assign({}, config, this.baseConfig))
}
public get<T>(url: string, options?: any): Promise<responseDataType<T>> {
return this.httpRequest<responseDataType<T>>({
url: url,
method: 'GET',
params: options
})
}
public post<T>(url: string, options?: any): Promise<responseDataType<T>> {
return this.httpRequest<responseDataType<T>>({
url: url,
method: 'POST',
data: options
})
}
public put<T>(url: string, options?: any): Promise<responseDataType<T>> {
return this.httpRequest<responseDataType<T>>({
url: url,
method: 'PUT',
...(typeof options === "string" ? {data: options} : {params: options})
})
}
public delete<T>(url: string, options?: any): Promise<responseDataType<T>> {
return this.httpRequest<responseDataType<T>>({
url: url,
method: 'DELETE',
...(typeof options === 'string' ? {data: options} : {params: options})
})
}
/**
*@Date:2023-09-21 20:56:52
*@description:下载文件的公共方法
*@param{*} fileUrl 下载路径
*@param{*} methods 请求方法
*@param{*} params 请求参数
*@param{*} fileName 文件名
*/
public download(fileUrl: string, methods?: string, params?: any, fileName?: string) {
const loading = ElLoading.service({
lock: true,
text: '文件打包中,请稍候...',
})
this.httpRequest<Blob>({
method: methods || 'get',
url: fileUrl,
responseType: 'blob', // 设置响应数据类型为二进制对象
...(methods === 'get' ? {params: params} : {data: params})
}).then((response: Blob) => {
if (response.size <= 0) {
loading.close()
return ElMessage.error('无可下载数据')
}
// 处理下载的二进制文件流
const blob = response;
// 创建一个虚拟的 <a> 元素用于触发下载
const a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
// 指定文件名并设置下载属性
if (fileName) {
a.setAttribute('download', fileName)
}
// 模拟点击事件触发下载
const clickEvent = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: false,
});
a.dispatchEvent(clickEvent);
loading.close()
// 下载成功后解析 Promise
})
.catch((error) => {
console.error('下载文件失败:', error);
loading.close()
});
}
}
export const hrRequest = new Request()