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连接实例 */ 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 || {}, }; };