| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- import axios, {
- AxiosError,
- type AxiosInstance,
- type AxiosResponse,
- type InternalAxiosRequestConfig,
- } from "axios";
- import { ElMessage, ElMessageBox, ElNotification } from "element-plus";
- import { useUserStoreWithOut } from "@/stores/useUserStore";
- import qs from "qs";
- import { config } from "@/config/axios/config";
- import {
- getAccessToken,
- getRefreshToken,
- removeToken,
- setToken,
- } from "@utils/auth";
- import errorCode from "./errorCode";
- import { resetRouter } from "@/router";
- import { deleteUserCache } from "@hooks/useCache";
- const { result_code, base_url, request_timeout } = config;
- // 需要忽略的提示。忽略后,自动 Promise.reject('error')
- const ignoreMsgs = [
- "无效的刷新令牌", // 刷新令牌被删除时,不用提示
- "刷新令牌已过期", // 使用刷新令牌,刷新获取新的访问令牌时,结果因为过期失败,此时需要忽略。否则,会导致继续 401,无法跳转到登出界面
- ];
- // 是否显示重新登录
- export const isRelogin = { show: false };
- export const reloginCancelKey = "reloginCancel";
- export const manualLogoutKey = "manualLogout";
- // Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现
- // 请求队列
- let requestList: any[] = [];
- // 是否正在刷新中
- let isRefreshToken = false;
- // 请求白名单,无须token的接口
- const whiteList: string[] = ["/login", "/refresh-token"];
- // 创建axios实例
- const service: AxiosInstance = axios.create({
- baseURL: base_url, // api 的 base_url
- timeout: request_timeout, // 请求超时时间
- withCredentials: false, // 禁用 Cookie 等信息
- // 自定义参数序列化函数
- paramsSerializer: (params) => {
- return qs.stringify(params, { allowDots: true });
- },
- });
- // request拦截器
- service.interceptors.request.use(
- (config: InternalAxiosRequestConfig) => {
- config.headers["tenant-id"] = 1;
- // 是否需要设置 token
- let isToken = (config!.headers || {}).isToken === false;
- whiteList.some((v) => {
- if (config.url && config.url.indexOf(v) > -1) {
- return (isToken = false);
- }
- });
- if (getAccessToken() && !isToken) {
- config.headers.Authorization = "Bearer " + getAccessToken(); // 让每个请求携带自定义token
- }
- const method = config.method?.toUpperCase();
- // 防止 GET 请求缓存
- if (method === "GET") {
- config.headers["Cache-Control"] = "no-cache";
- config.headers["Pragma"] = "no-cache";
- }
- // 自定义参数序列化函数
- else if (method === "POST") {
- const contentType =
- config.headers["Content-Type"] || config.headers["content-type"];
- if (contentType === "application/x-www-form-urlencoded") {
- if (config.data && typeof config.data !== "string") {
- config.data = qs.stringify(config.data);
- }
- }
- }
- return config;
- },
- (error: AxiosError) => {
- // Do something with request error
- console.log(error); // for debug
- return Promise.reject(error);
- },
- );
- // response 拦截器
- service.interceptors.response.use(
- async (response: AxiosResponse<any>) => {
- let { data } = response;
- const config = response.config;
- if (!data) {
- // 返回“[HTTP]请求没有返回值”;
- throw new Error();
- }
- // 未设置状态码则默认成功状态
- // 二进制数据则直接返回,例如说 Excel 导出
- if (
- response.request.responseType === "blob" ||
- response.request.responseType === "arraybuffer"
- ) {
- // 注意:如果导出的响应为 json,说明可能失败了,不直接返回进行下载
- if (response.data.type !== "application/json") {
- return response.data;
- }
- data = await new Response(response.data).json();
- }
- const code = data.code || result_code;
- // 获取错误信息
- const msg = data.msg || (errorCode as any)[code] || errorCode.default;
- if (ignoreMsgs.indexOf(msg) !== -1) {
- // 如果是忽略的错误码,直接返回 msg 异常
- return Promise.reject(msg);
- } else if (code === 401) {
- // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
- if (!isRefreshToken) {
- isRefreshToken = true;
- // 1. 如果获取不到刷新令牌,则只能执行登出操作
- if (!getRefreshToken()) {
- return handleAuthorized();
- }
- // 2. 进行刷新访问令牌
- try {
- const refreshTokenRes = await refreshToken();
- // 2.1 刷新成功,则回放队列的请求 + 当前请求
- setToken((await refreshTokenRes).data.data);
- config.headers!.Authorization = "Bearer " + getAccessToken();
- requestList.forEach((cb: any) => {
- cb();
- });
- requestList = [];
- return service(config);
- } catch (e) {
- // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
- // 2.2 刷新失败,只回放队列的请求
- requestList.forEach((cb: any) => {
- cb();
- });
- // 提示是否要登出。即不回放当前请求!不然会形成递归
- return handleAuthorized();
- } finally {
- requestList = [];
- isRefreshToken = false;
- }
- } else {
- // 添加到队列,等待刷新获取到新的令牌
- return new Promise((resolve) => {
- requestList.push(() => {
- config.headers!.Authorization = "Bearer " + getAccessToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
- resolve(service(config));
- });
- });
- }
- } else if (code === 500) {
- ElMessage.error(msg);
- return Promise.reject(new Error(msg));
- } else if (code === 901) {
- ElMessage.error({
- offset: 300,
- dangerouslyUseHTMLString: true,
- message: "演示模式,无法进行写操作!",
- });
- return Promise.reject(new Error(msg));
- } else if (code !== 200) {
- if (msg === "无效的刷新令牌") {
- // hard coding:忽略这个提示,直接登出
- console.log(msg);
- return handleAuthorized();
- } else {
- ElNotification.error({ title: msg });
- }
- return Promise.reject("error");
- } else {
- return data;
- }
- },
- (error: AxiosError) => {
- console.log("err" + error); // for debug
- let { message } = error;
- if (message === "Network Error") {
- message = "操作失败,系统异常!";
- } else if (message.includes("timeout")) {
- message = "接口请求超时,请刷新页面重试!";
- } else if (message.includes("Request failed with status code")) {
- message = "请求出错,请稍候重试" + message.substr(message.length - 3);
- }
- ElMessage.error(message);
- return Promise.reject(error);
- },
- );
- const refreshToken = async () => {
- axios.defaults.headers.common["tenant-id"] = 1;
- return await axios.post(
- base_url +
- "/admin-api/system/auth/refresh-token?refreshToken=" +
- getRefreshToken(),
- );
- };
- const handleAuthorized = () => {
- const isManualLogout = sessionStorage.getItem(manualLogoutKey) === "true";
- const isReloginCanceled = sessionStorage.getItem(reloginCancelKey) === "true";
- const ua = window.navigator.userAgent.toLowerCase();
- if (isManualLogout || isReloginCanceled) {
- deleteUserCache();
- removeToken();
- if (!window.location.href.includes("login")) {
- if (ua.includes("dingtalk") || ua.includes("dingtalkwork")) {
- window.location.href = "/";
- } else {
- window.location.href = "/login";
- }
- }
- return Promise.reject("登录超时,请重新登录");
- }
- if (!isRelogin.show) {
- if (window.location.href.includes("login")) {
- return Promise.reject("登录超时,请重新登录");
- }
- isRelogin.show = true;
- ElMessageBox.confirm("登录超时,请重新登录", "确定", {
- showCancelButton: false,
- closeOnClickModal: false,
- showClose: false,
- closeOnPressEscape: false,
- confirmButtonText: "重新登录",
- cancelButtonText: "取消",
- type: "warning",
- })
- .then(async () => {
- sessionStorage.removeItem(reloginCancelKey);
- deleteUserCache(); // 删除用户缓存
- removeToken();
- isRelogin.show = false;
- window.location.href = "/login";
- })
- .catch(() => {
- sessionStorage.setItem(reloginCancelKey, "true");
- deleteUserCache(); // 删除用户缓存
- removeToken();
- isRelogin.show = false; // 重置显示状态
- if (ua.includes("dingtalk") || ua.includes("dingtalkwork")) {
- window.location.href = "/";
- } else {
- window.location.href = "/login";
- }
- });
- }
- return Promise.reject("登录超时,请重新登录");
- };
- export { service };
|