mirror of
https://github.com/Funkoala14/KnowledgeBase_OOIN.git
synced 2025-06-08 05:09:44 +08:00
235 lines
6.9 KiB
JavaScript
235 lines
6.9 KiB
JavaScript
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 || {},
|
||
};
|
||
};
|