ts + axios token无感刷新,及重新请求后页面不刷新问题
最近上班遇到的新需求,token无感刷新,参考了很多博客,也看了渡一老师的视频,功能是实现了,但是发现重新请求后页面数据没有更新
遇到相同问题的先理清代码执行顺序和Promise,看看执行结果有没有resolve()出去。
话不多说,直接上代码,因为自己封装的请求和大家的不一样,仅供参考
无感刷新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()