KnowledgeBase_frontend/src/services/websocket.js

235 lines
6.9 KiB
JavaScript
Raw Normal View History

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 || {},
};
};