Merge branch 'master' into master

This commit is contained in:
Ray Hao
2025-02-19 13:43:33 +08:00
committed by GitHub
78 changed files with 1906 additions and 2145 deletions

View File

@@ -3,11 +3,11 @@ const ACCESS_TOKEN_KEY = "access_token";
// 刷新 token 缓存的 key
const REFRESH_TOKEN_KEY = "refresh_token";
function getToken(): string {
function getAccessToken(): string {
return localStorage.getItem(ACCESS_TOKEN_KEY) || "";
}
function setToken(token: string) {
function setAccessToken(token: string) {
localStorage.setItem(ACCESS_TOKEN_KEY, token);
}
@@ -24,4 +24,4 @@ function clearToken() {
localStorage.removeItem(REFRESH_TOKEN_KEY);
}
export { getToken, setToken, clearToken, getRefreshToken, setRefreshToken };
export { getAccessToken, setAccessToken, clearToken, getRefreshToken, setRefreshToken };

View File

@@ -2,7 +2,7 @@ import axios, { type InternalAxiosRequestConfig, type AxiosResponse } from "axio
import qs from "qs";
import { useUserStoreHook } from "@/store/modules/user";
import { ResultEnum } from "@/enums/ResultEnum";
import { getToken } from "@/utils/auth";
import { getAccessToken } from "@/utils/auth";
import router from "@/router";
// 创建 axios 实例
@@ -16,10 +16,10 @@ const service = axios.create({
// 请求拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const accessToken = getToken();
const accessToken = getAccessToken();
// 如果 Authorization 设置为 no-auth则不携带 Token用于登录、刷新 Token 等接口
if (config.headers.Authorization !== "no-auth" && accessToken) {
config.headers.Authorization = accessToken;
config.headers.Authorization = `Bearer ${accessToken}`;
} else {
delete config.headers.Authorization;
}
@@ -44,7 +44,8 @@ service.interceptors.response.use(
ElMessage.error(msg || "系统出错");
return Promise.reject(new Error(msg || "Error"));
},
async (error: any) => {
async (error) => {
console.error("request error", error); // for debug
// 非 2xx 状态码处理 401、403、500 等
const { config, response } = error;
if (response) {
@@ -64,20 +65,21 @@ service.interceptors.response.use(
export default service;
// 刷新 Token 的锁
// 是否正在刷新标识,避免重复刷新
let isRefreshing = false;
// 因 Token 过期导致失败的请求队列
let requestsQueue: Array<() => void> = [];
// 因 Token 过期导致的请求等待队列
const waitingQueue: Array<() => void> = [];
// 刷新 Token 处理
async function handleTokenRefresh(config: InternalAxiosRequestConfig) {
return new Promise((resolve) => {
const requestCallback = () => {
config.headers.Authorization = getToken();
// 封装需要重试的请求
const retryRequest = () => {
config.headers.Authorization = getAccessToken();
resolve(service(config));
};
requestsQueue.push(requestCallback);
waitingQueue.push(retryRequest);
if (!isRefreshing) {
isRefreshing = true;
@@ -86,13 +88,13 @@ async function handleTokenRefresh(config: InternalAxiosRequestConfig) {
useUserStoreHook()
.refreshToken()
.then(() => {
// Token 刷新成功,执行请求队列
requestsQueue.forEach((callback) => callback());
requestsQueue = [];
// 依次重试队列中所有请求, 重试后清空队列
waitingQueue.forEach((callback) => callback());
waitingQueue.length = 0;
})
.catch((error) => {
.catch((error: any) => {
console.log("handleTokenRefresh error", error);
// Token 刷新失败,清除用户数据并跳转登录
// 刷新 Token 失败,跳转登录
ElNotification({
title: "提示",
message: "您的会话已过期,请重新登录",

View File

@@ -1,93 +0,0 @@
import { Client } from "@stomp/stompjs";
import { getToken } from "@/utils/auth";
class WebSocketManager {
private client: Client | null = null;
private messageHandlers: Map<string, ((message: string) => void)[]> = new Map();
private reconnectAttempts = 0;
private maxReconnectAttempts = 3; // 自定义最大重试次数
private reconnectDelay = 5000; // 重试延迟(单位:毫秒)
// 初始化 WebSocket 客户端
setupWebSocket() {
const endpoint = import.meta.env.VITE_APP_WS_ENDPOINT;
// 如果没有配置 WebSocket 端点或显式关闭,直接返回
if (!endpoint) {
console.log("WebSocket 已被禁用,如需打开请在配置文件中配置 VITE_APP_WS_ENDPOINT");
return;
}
if (this.client && this.client.connected) {
console.log("客户端已存在并且连接正常");
return this.client;
}
this.client = new Client({
brokerURL: endpoint,
connectHeaders: {
Authorization: getToken(),
},
heartbeatIncoming: 30000,
heartbeatOutgoing: 30000,
reconnectDelay: 0, // 设置为 0 禁用重连
onConnect: () => {
console.log(`连接到 WebSocket 服务器: ${endpoint}`);
this.reconnectAttempts = 0; // 重置重连计数
this.messageHandlers.forEach((handlers, topic) => {
handlers.forEach((handler) => {
this.subscribeToTopic(topic, handler);
});
});
},
onStompError: (frame) => {
console.error(`连接错误: ${frame.headers["message"]}`);
console.error(`错误详情: ${frame.body}`);
},
onDisconnect: () => {
console.log(`WebSocket 连接已断开: ${endpoint}`);
this.reconnectAttempts++;
if (this.reconnectAttempts < this.maxReconnectAttempts) {
console.log(`正在尝试重连... 尝试次数: ${this.reconnectAttempts}`);
} else {
console.log("重连次数已达上限,停止重连");
this.client?.deactivate();
}
},
});
this.client.activate();
}
// 订阅主题
public subscribeToTopic(topic: string, onMessage: (message: string) => void) {
console.log(`正在订阅主题: ${topic}`);
if (!this.client || !this.client.connected) {
this.setupWebSocket();
}
if (this.messageHandlers.has(topic)) {
this.messageHandlers.get(topic)?.push(onMessage);
} else {
this.messageHandlers.set(topic, [onMessage]);
}
if (this.client?.connected) {
this.client.subscribe(topic, (message) => {
const handlers = this.messageHandlers.get(topic);
handlers?.forEach((handler) => handler(message.body));
});
}
}
// 断开 WebSocket 连接
public disconnect() {
if (this.client) {
console.log("断开 WebSocket 连接");
this.client.deactivate();
this.client = null;
}
}
}
export default new WebSocketManager();