Files
cashier-web/src/utils/request.ts

163 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import axios, { type InternalAxiosRequestConfig, type AxiosResponse } from "axios";
import qs from "qs";
import { useUserStoreHook } from "@/store/modules/user";
import { ResultEnum } from "@/enums/ResultEnum";
import { getToken } from "@/utils/auth";
import router from "@/router";
// 创建 axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 1000 * 60 * 3,
headers: { "Content-Type": "application/json;charset=utf-8", platformType: 'WEB' },
paramsSerializer: (params) => qs.stringify(params),
});
/**
* 格式化错误信息msg
*/
function formatErrorMsg(error: string) {
// 1. 获取原始提示文本(兜底空字符串避免报错)
const originMsg = error
// 2. 定义要匹配的前缀
const exceptionPrefix = "Exception:";
// 3. 判断是否包含目标前缀
if (originMsg.includes(exceptionPrefix)) {
// 截取前缀后的内容 → 去除首尾空格 → 限制最大20个字符
return originMsg
.slice(
originMsg.indexOf(exceptionPrefix) +
exceptionPrefix.length
)
.trim()
.slice(0, 20);
} else {
// 不包含则按原逻辑截取前20个字符
return originMsg.slice(0, 20);
}
}
// 请求拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const accessToken = getToken();
// 如果 Authorization 设置为 no-auth则不携带 Token用于登录、刷新 Token 等接口
if (config.headers.Authorization !== "no-auth" && accessToken) {
config.headers.token = accessToken;
} else {
delete config.headers.token;
}
// config.headers.shopId = config.headers.shopId || localStorage.getItem("branch_shopId");
config.headers.shopId = config.headers.shopId || useUserStoreHook().userInfo.id;
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
// 如果响应是二进制流则直接返回用于下载文件、Excel 导出等
if (response.config.responseType === "blob") {
return response.data;
}
const { code, data, msg } = response.data;
if (data) {
// 处理后台返回分页相关数据为字符串类型时elementui组件警告
const kes = ['pageNumber', 'pageSize', 'totalPage', 'totalRow'];
const keys = Object.keys(data);
for (let i of kes) {
if (keys.includes(i)) {
data[i] = data[i] * 1;
}
}
}
if (code === ResultEnum.SUCCESS || code === undefined || code === null) {
return data ? data : response.data;
}
if (code === ResultEnum.ACCESS_TOKEN_INVALID) {
ElNotification({
title: "提示",
message: "您的会话已过期,请重新登录",
type: "info",
});
useUserStoreHook()
.clearUserData()
.then(() => {
router.push("/login");
});
return;
}
ElMessage.error(formatErrorMsg(msg || "Error"));
return Promise.reject(response.data);
},
async (error: any) => {
// 非 2xx 状态码处理 401、403、500 等
const { config, response } = error;
if (response) {
const { code, message } = response.data;
if (code === ResultEnum.ACCESS_TOKEN_INVALID) {
// Token 过期,刷新 Token
return handleTokenRefresh(config);
} else if (code === ResultEnum.REFRESH_TOKEN_INVALID) {
return Promise.reject(new Error(message || "Error"));
} else {
ElMessage.error(message || "系统出错");
}
}
return Promise.reject(error.message);
}
);
export default service;
// 刷新 Token 的锁
let isRefreshing = false;
// 因 Token 过期导致失败的请求队列
let requestsQueue: Array<() => void> = [];
// 刷新 Token 处理
async function handleTokenRefresh(config: InternalAxiosRequestConfig) {
return new Promise((resolve) => {
const requestCallback = () => {
config.headers.Authorization = getToken();
resolve(service(config));
};
requestsQueue.push(requestCallback);
if (!isRefreshing) {
isRefreshing = true;
// 刷新 Token
useUserStoreHook()
.refreshToken()
.then(() => {
// Token 刷新成功,执行请求队列
requestsQueue.forEach((callback) => callback());
requestsQueue = [];
})
.catch((error) => {
console.log("handleTokenRefresh error", error);
// Token 刷新失败,清除用户数据并跳转到登录
ElNotification({
title: "提示",
message: "您的会话已过期,请重新登录",
type: "info",
});
useUserStoreHook()
.clearUserData()
.then(() => {
router.push("/login");
});
})
.finally(() => {
isRefreshing = false;
});
}
});
}