mirror of
https://github.com/Funkoala14/KnowledgeBase_OOIN.git
synced 2025-06-08 09:21:29 +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 || {},
|
|||
|
};
|
|||
|
};
|