vue合集

axios封装,无感刷新token

2021-12-08  本文已影响0人  Baby_ed6e

主要原理

1、当第一个请求发现token过期,创建一个Promise对象开始请求api刷新token
2、这个刷新过程中再发现token过期,或者再触发新的接口请求,都是给这个Promise 对象增加then方法(then链)。
3、相当于收集后面排队的请求,统一等刷新完毕后依次放行!

业务要求

1、token过期无感刷新token,没有提示等
2、页面性能过得去,不能用setTimeout setInterval
3、可能同时请求不同接口,分别发现token过期,两次触发非常接近,属于并发,第一个发现token过期的接口刚知道,另一个也已经发现了token过期,所以要将它们收集并挂起,在刷新token后自动再次请求一次接口,把第二次接口返回内容返回给第一次请求。这种情况接口会执行两次,一次是发现,一次是刷新后重新执行

4、如果追求性能细节,那么在刷新的时候,后面触发接口不应该直接请求api,因为就算请求了也是401,不如暂时挂起,等待刷新后再用新的token执行,这种情况只执行一次

以下是代码实现过程

实现过程

1、创建一个刷新token的类

/**
 * token刷新方法,
 * 1、当第一次出现401的时候,立即从api里请求token,并将“异步体”保存为 refreshPromise
 * 2、后续的请求相当于都是给 apiRefreshToken 添加then
 * 3、当token刷新完毕,refreshPromise 应当清空为下次做准备
 */
class RefreshToken {
    constructor() {
        this.refreshPromise = undefined;
    }
    apiRefreshToken() {
        if (!this.refreshPromise) {
            this.refreshPromise = new Promise((resolve) => {
                // 获取到token,存放到本地缓存,此处忽略
                setTimeout(() => {
                    this.refreshPromise = undefined;
                    resolve();
                }, 8000);
            });
        }
        return this.refreshPromise;
    }
    refreshIsLoading() {
        return Boolean(this.refreshPromise);
    }
}
export default new RefreshToken();

2、封装axios

import axios from "axios";
import { useRouter } from "vue-router";
import tokenServer from "./tokenServer";
import { ElMessage, ElMessageBox } from "element-plus";

// 创建axios实例
const service = axios.create({
    baseURL: import.meta.env.VITE_BASE_API,
    timeout: 5000,
});

// 请求头拦截器,组合token
service.interceptors.request.use(
    (config) => {
        config.headers["Content-Type"] = "application/json;charset=UTF-8";
        config.headers["X-Token"] = "abc"; // 这里在本地缓存拿token
        return config;
    },
    (error) => Promise.reject(error)
);

// 请求结果拦截器
service.interceptors.response.use(
    async (response) => {
        const { ret_code, result, ret_msg } = response.data;
        if (ret_code == "200") {
            return Promise.resolve(result);
        } else if (ret_code == 401) {
            // 过期执行刷新token
            // 1、apiRefreshToken 执行刷新并返回正在刷新token的Promise对象, 且同时只有一个。
            // 2、每次执行 apiRefreshToken,相当于给Promise对象添加 then() 链, 所以不必担心多个接口同时发现token过期,
            // 3、等刷新完毕后再次执行api请求。
            try {
                await tokenServer.apiRefreshToken();
                return Promise.resolve(service(response.config._original));
            } catch {
                const router = useRouter();
                ElMessageBox.alert("登录已失效,请重新登录!", "提示", {
                    confirmButtonText: "去登录",
                    callback: () => {
                        router.push({
                            path: "/login",
                        });
                    },
                });
                return Promise.reject();
            }
        } else {
            ElMessage.error(ret_msg);
            return Promise.reject(response.data);
        }
    },
    (error) => {
        ElMessage.error("服务器链接失败,请重试!");
        return Promise.reject(error.response);
    }
);

// 如果正在刷新token时,需等待token刷新完毕再触发接口请求(暂时挂起)
// 拓展入参_original,保留原始请求的入参,目的是为了拦击里再次请求接口所需参数
const request = async (params) => {
    let refreshIsLoading = tokenServer.refreshIsLoading();
    if (refreshIsLoading) {
        await tokenServer.apiRefreshToken();
    }
    params._original = JSON.parse(JSON.stringify(params));
    return service(params);
};

export default request;

上一篇 下一篇

猜你喜欢

热点阅读