ts + axios token无感刷新,及重新请求后页面不刷新问题

发布时间 2023-11-09 11:09:11作者: 初生土豆
最近上班遇到的新需求,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()