任务模块

This commit is contained in:
yuhawu 2025-07-16 18:29:53 +08:00
parent ceef525878
commit d01304460e
5 changed files with 252 additions and 42 deletions

View File

@ -12,6 +12,7 @@ export function setupRequestInterceptors(instance) {
// 返回修改后的请求配置,继续发送请求
return config
}, error => {
console.log("请求错误",error)
// 将错误以 Promise reject 的方式抛出,供调用方捕获处理
return Promise.reject(error)
})

View File

@ -1,7 +1,8 @@
//响应拦截器
import axios from 'axios'; // 引入axios用于创建新请求
import { ElMessage } from 'element-plus'; // 引入Element Plus的消息组件
import { ElMessage, ElMessageBox } from 'element-plus'; // 引入Element Plus的消息组件
import store from '../../store'; // 引入store用于统一处理登出
import axiosInstance from '../../utils/axios'; // 引入已配置的axios实例
export function setupResponseInterceptors(instance) {
// 用于存储正在刷新token的Promise
@ -11,16 +12,25 @@ export function setupResponseInterceptors(instance) {
// 统一处理登录过期的函数
const handleTokenExpired = (message = '登录已过期,请重新登录') => {
// 显示提示消息
ElMessage.error(message);
// 使用store的logout action清除认证状态
store.dispatch('logout');
// 延迟跳转,让用户有时间看到提示
setTimeout(() => {
// 显示确认对话框
ElMessageBox.confirm(
message,
'提示',
{
confirmButtonText: '重新登录',
type: 'warning',
showCancelButton: false,
center: true
}
).then(() => {
// 用户点击确认按钮后执行
// 使用store的logout action清除认证状态
store.dispatch('logout');
// 跳转到登录页
window.location.href = '/login';
}, 1500);
}).catch(() => {
// 用户关闭对话框,不做任何操作
});
};
instance.interceptors.response.use(
@ -36,8 +46,6 @@ export function setupResponseInterceptors(instance) {
return data;
},
async error => {
console.log('响应错误:', error);
// 获取原始请求配置
const originalRequest = error.config;
@ -51,10 +59,10 @@ export function setupResponseInterceptors(instance) {
const errorResponse = error.response;
console.log('错误响应:', errorResponse);
const errorData = errorResponse.data || {};
const errorMessage = errorData.message || errorData.error || errorResponse.statusText || '';
const errorMessage = errorData.status || errorData.message || errorData.error || errorResponse.statusText || '';
const errorStack = errorData.stack || '';
const statusCode = errorResponse.status;
console.log('错误响应:', statusCode);
// 处理403错误权限不足
if (errorResponse.status === "403") {
// 显示权限不足提示,但不重定向
@ -63,21 +71,12 @@ export function setupResponseInterceptors(instance) {
}
// 检查是否为令牌错误(检查多种可能的错误信息格式)
const isTokenError =
statusCode === 401 ||
(statusCode === 500 && (
errorMessage.includes('令牌') ||
errorMessage.includes('token') ||
errorMessage.includes('Token') ||
errorStack.includes('TokenIllegal') ||
errorStack.includes('TheTokenIllegalException') ||
(typeof errorData === 'string' && errorData.includes('令牌'))
));
const isTokenError = statusCode === 500
// 如果是令牌错误,尝试刷新令牌或直接跳转登录页
if (isTokenError) {
// 如果是401错误且没有在刷新中尝试刷新令牌
if (statusCode === 401 && !originalRequest._retry) {
if (statusCode === 401 || statusCode === 500 || statusCode === 403 && !originalRequest._retry ) {
// 标记该请求已尝试过重试
originalRequest._retry = true;
@ -88,7 +87,6 @@ export function setupResponseInterceptors(instance) {
try {
// 从localStorage获取refreshToken
const refreshToken = localStorage.getItem('refreshToken');
if (!refreshToken) {
// 无refreshToken跳转到登录页
handleTokenExpired();
@ -99,20 +97,26 @@ export function setupResponseInterceptors(instance) {
const formData = new FormData();
formData.append('refreshToken', refreshToken);
// 调用刷新token接口
const response = await axios.post('/admin/getAccessToken', formData);
// 调用刷新token接口使用配置好的axios实例
const response = await axiosInstance.post('/admin/getAccessToken', formData);
// 获取新的token
const { accessToken, refreshToken: newRefreshToken } = response.data.data;
// 根据实际返回的数据结构获取token
const responseData = response.data;
const accessToken = responseData.accessToken;
const newRefreshToken = responseData.refreshToken;
// 更新本地存储
localStorage.setItem('accessToken', accessToken);
if (newRefreshToken) {
localStorage.setItem('refreshToken', newRefreshToken);
}
localStorage.setItem('refreshToken', newRefreshToken);
// 更新当前请求的Authorization头
originalRequest.headers.Authorization = accessToken;
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
// 更新全局axios和axiosInstance的默认Authorization头确保后续所有请求都带上新token
axios.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
console.log('新token设置成功:', accessToken);
// 执行队列中的所有请求
requests.forEach(cb => cb(accessToken));

View File

@ -1,5 +1,6 @@
import { createStore } from 'vuex'
import { adminApi } from '../api/index.js'
import webSocketService from '../utils/webSocket.js'
export default createStore({
state: {
@ -31,6 +32,9 @@ export default createStore({
localStorage.removeItem('refreshToken')
// 清除本地缓存
localStorage.removeItem('userInfo')
// 断开WebSocket连接
webSocketService.disconnect()
},
SET_USER_INFO(state, userInfo) {
// 初始化 用户信息
@ -44,7 +48,7 @@ export default createStore({
try {
// 调用登录接口
const response = await adminApi.login(data)
console.log("响应",response)
// 检查响应状态
if (response.code !== 200) {
throw new Error(response.message || '登录失败')
@ -53,6 +57,17 @@ export default createStore({
// 设置 状态
commit('SET_TOKEN', response.data)
// 添加更多日志确保token存在
console.log("登录成功准备连接WebSocket")
console.log("accessToken值:", response.data.accessToken)
// 登录成功后连接WebSocket
if (response.data.accessToken) {
webSocketService.connect(response.data.accessToken)
} else {
console.error("无法连接WebSocket: accessToken不存在")
}
try {
// 调用查询接口获取用户信息
const admin = await adminApi.getAdmin()

181
src/utils/webSocket.js Normal file
View File

@ -0,0 +1,181 @@
/**
* WebSocket连接工具类
*/
// WebSocket URL
const WS_URL = 'ws://146.56.192.164:9090/ws';
class WebSocketService {
constructor() {
this.socket = null;
this.isConnected = false;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectInterval = 3000; // 3秒
this.listeners = new Map();
}
/**
* 初始化WebSocket连接
*/
connect() {
if (this.socket && (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING)) {
console.log('WebSocket已连接或正在连接中');
return;
}
try {
// 获取存储在localStorage中的token
const token = localStorage.getItem('accessToken');
if (!token) {
console.error('WebSocket连接失败: 未找到accessToken');
return;
}
// 创建WebSocket连接通过传递header中的token
this.socket = new WebSocket(WS_URL+'?token='+token);
// 由于浏览器WebSocket API不允许直接设置headers我们需要在连接建立后的第一条消息中发送token
this.socket.onopen = () => {
console.log('WebSocket连接已建立');
this.isConnected = true;
this.reconnectAttempts = 0;
// 连接后立即发送认证信息
this.sendMessage({
type: 'auth',
token: token
});
// 触发已注册的连接事件监听器
this.triggerEvent('connect');
};
this.socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
console.log('收到WebSocket消息:', data);
this.triggerEvent('message', data);
} catch (error) {
console.error('解析WebSocket消息失败:', error);
this.triggerEvent('error', error);
}
};
this.socket.onclose = (event) => {
console.log('WebSocket连接已关闭:', event);
this.isConnected = false;
this.triggerEvent('disconnect');
// 尝试重连
this.attemptReconnect();
};
this.socket.onerror = (error) => {
console.error('WebSocket错误:', error);
this.triggerEvent('error', error);
};
} catch (error) {
console.error('初始化WebSocket失败:', error);
this.triggerEvent('error', error);
}
}
/**
* 尝试重新连接
*/
attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.log('达到最大重连次数');
return;
}
this.reconnectAttempts++;
console.log(`尝试第 ${this.reconnectAttempts} 次重连...`);
setTimeout(() => {
this.connect();
}, this.reconnectInterval);
}
/**
* 发送消息到WebSocket服务器
* @param {Object} data - 要发送的数据
*/
sendMessage(data) {
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
console.error('WebSocket未连接无法发送消息');
return false;
}
try {
const message = typeof data === 'string' ? data : JSON.stringify(data);
this.socket.send(message);
return true;
} catch (error) {
console.error('发送WebSocket消息失败:', error);
return false;
}
}
/**
* 关闭WebSocket连接
*/
disconnect() {
if (this.socket) {
this.socket.close();
this.socket = null;
this.isConnected = false;
}
}
/**
* 注册事件监听器
* @param {string} event - 事件名称
* @param {Function} callback - 回调函数
*/
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
/**
* 注销事件监听器
* @param {string} event - 事件名称
* @param {Function} callback - 回调函数
*/
off(event, callback) {
if (!this.listeners.has(event)) return;
const callbacks = this.listeners.get(event);
const index = callbacks.indexOf(callback);
if (index !== -1) {
callbacks.splice(index, 1);
}
}
/**
* 触发事件
* @param {string} event - 事件名称
* @param {*} data - 事件数据
*/
triggerEvent(event, data) {
if (!this.listeners.has(event)) return;
const callbacks = this.listeners.get(event);
callbacks.forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`执行${event}事件监听器出错:`, error);
}
});
}
}
// 创建单例实例
const webSocketService = new WebSocketService();
export default webSocketService;

View File

@ -29,7 +29,8 @@
import { ref, reactive, getCurrentInstance, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import store from '../../store'
import store from '../../store/Index.js'
import { nextTick } from 'vue'
//
const global = getCurrentInstance()?.appContext.config.globalProperties.$global
@ -81,13 +82,21 @@
//
sendForm.append('password', form.password)
sendForm.append('code', form.captcha) //
console.log('准备调用store.dispatch("login")', sendForm)
//
await store.dispatch('login', sendForm)
console.log('store.dispatch("login")调用成功')
//
const redirect = router.currentRoute.value.query.redirect || '/'
//
router.replace(redirect)
ElMessage.success('登录成功')
const redirect = router.currentRoute.value.query.redirect || '/welcome'
console.log("跳转地址",redirect)
nextTick(() => {
window.location.href = redirect
//
router.replace(redirect)
ElMessage.success('登录成功')
})
} catch (error) {
console.error('登录错误详情:', error)
refreshCaptcha()