KnowledgeBase_frontend/src/services/websocket.js

235 lines
6.9 KiB
JavaScript
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 { addNotification, markNotificationAsRead } from '../store/notificationCenter/notificationCenter.slice';
import store from '../store/store'; // 修改为默认导出
// 从环境变量获取 API URL
const API_URL = import.meta.env.VITE_API_URL || '';
// 将 HTTP URL 转换为 WebSocket URL
const WS_BASE_URL = API_URL.replace(/^http/, 'ws').replace(/\/api\/?$/, '');
let socket = null;
let reconnectTimer = null;
let pingInterval = null;
const RECONNECT_DELAY = 5000; // 5秒后尝试重连
const PING_INTERVAL = 30000; // 30秒发送一次ping
/**
* 初始化WebSocket连接
* @returns {Promise<WebSocket>} WebSocket连接实例
*/
export const initWebSocket = () => {
return new Promise((resolve, reject) => {
// 如果已经有一个连接,先关闭它
if (socket && socket.readyState !== WebSocket.CLOSED) {
socket.close();
}
// 清除之前的定时器
if (reconnectTimer) clearTimeout(reconnectTimer);
if (pingInterval) clearInterval(pingInterval);
try {
// 从sessionStorage获取token
const encryptedToken = sessionStorage.getItem('token');
if (!encryptedToken) {
console.error('No token found, cannot connect to notification service');
reject(new Error('No token found'));
return;
}
const wsUrl = `${WS_BASE_URL}/ws/notifications?token=${encryptedToken}`;
socket = new WebSocket(wsUrl);
// 连接建立时的处理
socket.onopen = () => {
console.log('WebSocket connection established');
// 订阅通知频道
subscribeToNotifications();
// 设置定时发送ping消息
pingInterval = setInterval(() => {
if (socket.readyState === WebSocket.OPEN) {
sendPing();
}
}, PING_INTERVAL);
resolve(socket);
};
// 接收消息的处理
socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
handleWebSocketMessage(data);
} catch (error) {
console.error('Error parsing WebSocket message:', error);
}
};
// 错误处理
socket.onerror = (error) => {
console.error('WebSocket error:', error);
reject(error);
};
// 连接关闭时的处理
socket.onclose = (event) => {
console.log(`WebSocket connection closed: ${event.code} ${event.reason}`);
// 清除ping定时器
if (pingInterval) clearInterval(pingInterval);
// 如果不是正常关闭,尝试重连
if (event.code !== 1000) {
reconnectTimer = setTimeout(() => {
console.log('Attempting to reconnect WebSocket...');
initWebSocket().catch((err) => {
console.error('Failed to reconnect WebSocket:', err);
});
}, RECONNECT_DELAY);
}
};
} catch (error) {
console.error('Error initializing WebSocket:', error);
reject(error);
}
});
};
/**
* 订阅通知频道
*/
export const subscribeToNotifications = () => {
if (socket && socket.readyState === WebSocket.OPEN) {
const subscribeMessage = {
type: 'subscribe',
channel: 'notifications',
};
socket.send(JSON.stringify(subscribeMessage));
}
};
/**
* 发送ping消息保持连接活跃
*/
export const sendPing = () => {
if (socket && socket.readyState === WebSocket.OPEN) {
const pingMessage = {
type: 'ping',
};
socket.send(JSON.stringify(pingMessage));
}
};
/**
* 确认已读通知
* @param {string} notificationId 通知ID
*/
export const acknowledgeNotification = (notificationId) => {
if (socket && socket.readyState === WebSocket.OPEN) {
const ackMessage = {
type: 'acknowledge',
notification_id: notificationId,
};
socket.send(JSON.stringify(ackMessage));
// 使用 store.dispatch 替代 dispatch
store.dispatch(markNotificationAsRead(notificationId));
}
};
/**
* 关闭WebSocket连接
*/
export const closeWebSocket = () => {
if (socket) {
socket.close(1000, 'Normal closure');
socket = null;
}
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
if (pingInterval) {
clearInterval(pingInterval);
pingInterval = null;
}
};
/**
* 处理接收到的WebSocket消息
* @param {Object} data 解析后的消息数据
*/
const handleWebSocketMessage = (data) => {
switch (data.type) {
case 'connection_established':
console.log(`Connection established for user: ${data.user_id}`);
break;
case 'notification':
console.log('Received notification:', data);
// 将通知添加到Redux store
store.dispatch(addNotification(processNotification(data)));
break;
case 'pong':
console.log(`Received pong at ${data.timestamp}`);
break;
case 'error':
console.error(`WebSocket error: ${data.code} - ${data.message}`);
break;
default:
console.log('Received unknown message type:', data);
}
};
/**
* 处理通知数据,转换为应用内通知格式
* @param {Object} data 通知数据
* @returns {Object} 处理后的通知数据
*/
const processNotification = (data) => {
const { data: notificationData } = data;
let icon = 'bi-info-circle';
if (notificationData.category === 'system') {
icon = 'bi-info-circle';
} else if (notificationData.category === 'permission') {
icon = 'bi-shield';
}
// 计算时间显示
const createdAt = new Date(notificationData.created_at);
const now = new Date();
const diffMs = now - createdAt;
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMins / 60);
const diffDays = Math.floor(diffHours / 24);
let timeDisplay;
if (diffMins < 60) {
timeDisplay = `${diffMins}分钟前`;
} else if (diffHours < 24) {
timeDisplay = `${diffHours}小时前`;
} else {
timeDisplay = `${diffDays}天前`;
}
return {
id: notificationData.id,
type: notificationData.category,
icon,
title: notificationData.title,
content: notificationData.content,
time: timeDisplay,
hasDetail: true,
isRead: notificationData.is_read,
created_at: notificationData.created_at,
metadata: notificationData.metadata || {},
};
};