feat:. mockSocketService 添加 connecting 状态
- 新增 connecting 标志防止重复连接 - 在 connect() 方法中检查 connected 和 connecting 状态 - 连接成功或失败后清除 connecting 标志\ 2. NotificationContext 调整监听器注册顺序 - 在 useEffect 中重新排序初始化步骤 - 第一步:注册所有事件监听器(connect, disconnect, new_event 等) - 第二步:获取最大重连次数 - 第三步:调用 socket.connect() - 使用空依赖数组 [] 防止 React 严格模式重复执行\ 3. logger 添加日志限流 - 实现 shouldLog() 函数,1秒内相同日志只输出一次 - 使用 Map 缓存最近日志,带最大缓存限制(100条) - 应用到所有 logger 方法:info, warn, debug, api.request, api.response - 错误日志(error, api.error)不做限流,始终输出\ 修复 emit 时机确保事件被接收 - 在 mockSocketService 的 connect() 方法中 - 使用 setTimeout(0) 延迟 emit(connect) 调用 - 确保监听器注册完毕后再触发事件\
This commit is contained in:
@@ -572,14 +572,10 @@ export const NotificationProvider = ({ children }) => {
|
||||
// 连接到 Socket 服务
|
||||
useEffect(() => {
|
||||
logger.info('NotificationContext', 'Initializing socket connection...');
|
||||
console.log(`%c[NotificationContext] Initializing socket (type: ${SOCKET_TYPE})`, 'color: #673AB7; font-weight: bold;');
|
||||
|
||||
// 连接 socket
|
||||
socket.connect();
|
||||
|
||||
// 获取并保存最大重连次数
|
||||
const maxAttempts = socket.getMaxReconnectAttempts?.() || Infinity;
|
||||
setMaxReconnectAttempts(maxAttempts);
|
||||
logger.info('NotificationContext', 'Max reconnect attempts', { maxAttempts });
|
||||
// ✅ 第一步: 注册所有事件监听器
|
||||
console.log('%c[NotificationContext] Step 1: Registering event listeners...', 'color: #673AB7;');
|
||||
|
||||
// 监听连接状态
|
||||
socket.on('connect', () => {
|
||||
@@ -587,6 +583,7 @@ export const NotificationProvider = ({ children }) => {
|
||||
setIsConnected(true);
|
||||
setReconnectAttempt(0);
|
||||
logger.info('NotificationContext', 'Socket connected', { wasDisconnected });
|
||||
console.log('%c[NotificationContext] ✅ Received connect event, updating state to connected', 'color: #4CAF50; font-weight: bold;');
|
||||
|
||||
// 如果之前断开过,显示 RECONNECTED 状态2秒后自动消失
|
||||
if (wasDisconnected) {
|
||||
@@ -683,6 +680,18 @@ export const NotificationProvider = ({ children }) => {
|
||||
addNotification(data);
|
||||
});
|
||||
|
||||
console.log('%c[NotificationContext] ✅ All event listeners registered', 'color: #4CAF50; font-weight: bold;');
|
||||
|
||||
// ✅ 第二步: 获取最大重连次数
|
||||
const maxAttempts = socket.getMaxReconnectAttempts?.() || Infinity;
|
||||
setMaxReconnectAttempts(maxAttempts);
|
||||
logger.info('NotificationContext', 'Max reconnect attempts', { maxAttempts });
|
||||
|
||||
// ✅ 第三步: 调用 socket.connect()
|
||||
console.log('%c[NotificationContext] Step 2: Calling socket.connect()...', 'color: #673AB7; font-weight: bold;');
|
||||
socket.connect();
|
||||
console.log('%c[NotificationContext] socket.connect() completed', 'color: #673AB7;');
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
logger.info('NotificationContext', 'Cleaning up socket connection');
|
||||
@@ -700,7 +709,7 @@ export const NotificationProvider = ({ children }) => {
|
||||
socket.off('system_notification');
|
||||
socket.disconnect();
|
||||
};
|
||||
}, [adaptEventToNotification, connectionStatus, toast]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}, []); // ✅ 空依赖数组,确保只执行一次,避免 React 严格模式重复执行
|
||||
|
||||
// ==================== 智能自动重试 ====================
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
*/
|
||||
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { socketService } from '../services/socketService';
|
||||
import socket from '../services/socket';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
export const useEventNotifications = (options = {}) => {
|
||||
@@ -80,16 +80,16 @@ export const useEventNotifications = (options = {}) => {
|
||||
};
|
||||
|
||||
// 监听连接事件(必须在connect之前设置,否则可能错过事件)
|
||||
socketService.on('connect', handleConnect);
|
||||
socketService.on('disconnect', handleDisconnect);
|
||||
socketService.on('connect_error', handleConnectError);
|
||||
socket.on('connect', handleConnect);
|
||||
socket.on('disconnect', handleDisconnect);
|
||||
socket.on('connect_error', handleConnectError);
|
||||
|
||||
// 连接 WebSocket
|
||||
console.log('[useEventNotifications DEBUG] 准备连接 WebSocket...');
|
||||
logger.info('useEventNotifications', 'Initializing WebSocket connection');
|
||||
|
||||
// 先检查是否已经连接
|
||||
const alreadyConnected = socketService.isConnected();
|
||||
const alreadyConnected = socket.connected || false;
|
||||
console.log('[useEventNotifications DEBUG] 当前连接状态:', alreadyConnected);
|
||||
logger.info('useEventNotifications', 'Pre-connection check', { isConnected: alreadyConnected });
|
||||
|
||||
@@ -99,7 +99,7 @@ export const useEventNotifications = (options = {}) => {
|
||||
setIsConnected(true);
|
||||
} else {
|
||||
// 否则建立新连接
|
||||
socketService.connect();
|
||||
socket.connect();
|
||||
}
|
||||
|
||||
// 新事件处理函数 - 使用 ref 中的回调
|
||||
@@ -131,7 +131,9 @@ export const useEventNotifications = (options = {}) => {
|
||||
console.log('[useEventNotifications DEBUG] importance:', importance);
|
||||
console.log('[useEventNotifications DEBUG] enabled:', enabled);
|
||||
|
||||
socketService.subscribeToEvents({
|
||||
// 检查 socket 是否有 subscribeToEvents 方法(mockSocketService 和 socketService 都有)
|
||||
if (socket.subscribeToEvents) {
|
||||
socket.subscribeToEvents({
|
||||
eventType,
|
||||
importance,
|
||||
onNewEvent: handleNewEvent,
|
||||
@@ -142,10 +144,15 @@ export const useEventNotifications = (options = {}) => {
|
||||
},
|
||||
});
|
||||
console.log('[useEventNotifications DEBUG] ========== 订阅请求已发送 ==========\n');
|
||||
} else {
|
||||
console.warn('[useEventNotifications] socket.subscribeToEvents 方法不存在');
|
||||
}
|
||||
|
||||
// 保存取消订阅函数
|
||||
unsubscribeRef.current = () => {
|
||||
socketService.unsubscribeFromEvents({ eventType });
|
||||
if (socket.unsubscribeFromEvents) {
|
||||
socket.unsubscribeFromEvents({ eventType });
|
||||
}
|
||||
};
|
||||
|
||||
// 组件卸载时清理
|
||||
@@ -160,14 +167,12 @@ export const useEventNotifications = (options = {}) => {
|
||||
|
||||
// 移除监听器
|
||||
console.log('[useEventNotifications DEBUG] 移除事件监听器...');
|
||||
socketService.off('connect', handleConnect);
|
||||
socketService.off('disconnect', handleDisconnect);
|
||||
socketService.off('connect_error', handleConnectError);
|
||||
|
||||
// 断开连接
|
||||
console.log('[useEventNotifications DEBUG] 断开 WebSocket 连接...');
|
||||
socketService.disconnect();
|
||||
socket.off('connect', handleConnect);
|
||||
socket.off('disconnect', handleDisconnect);
|
||||
socket.off('connect_error', handleConnectError);
|
||||
|
||||
// 注意:不断开连接,因为 socket 是全局共享的
|
||||
// 由 NotificationContext 统一管理连接生命周期
|
||||
console.log('[useEventNotifications DEBUG] ========== 清理完成 ==========\n');
|
||||
};
|
||||
}, [eventType, importance, enabled]); // 移除 onNewEvent 依赖
|
||||
|
||||
@@ -303,6 +303,7 @@ const mockFinancialNews = [
|
||||
class MockSocketService {
|
||||
constructor() {
|
||||
this.connected = false;
|
||||
this.connecting = false; // 新增:正在连接标志,防止重复连接
|
||||
this.listeners = new Map();
|
||||
this.intervals = [];
|
||||
this.messageQueue = [];
|
||||
@@ -325,18 +326,30 @@ class MockSocketService {
|
||||
* 连接到 mock socket
|
||||
*/
|
||||
connect() {
|
||||
// ✅ 防止重复连接
|
||||
if (this.connected) {
|
||||
logger.warn('mockSocketService', 'Already connected');
|
||||
console.log('%c[Mock Socket] Already connected, skipping', 'color: #FF9800; font-weight: bold;');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.connecting) {
|
||||
logger.warn('mockSocketService', 'Connection in progress');
|
||||
console.log('%c[Mock Socket] Connection already in progress, skipping', 'color: #FF9800; font-weight: bold;');
|
||||
return;
|
||||
}
|
||||
|
||||
this.connecting = true; // 标记为连接中
|
||||
logger.info('mockSocketService', 'Connecting to mock socket service...');
|
||||
console.log('%c[Mock Socket] 🔌 Connecting...', 'color: #2196F3; font-weight: bold;');
|
||||
|
||||
// 模拟连接延迟
|
||||
setTimeout(() => {
|
||||
// 检查是否应该模拟连接失败
|
||||
if (this.failConnection) {
|
||||
this.connecting = false; // 清除连接中标志
|
||||
logger.warn('mockSocketService', 'Simulated connection failure');
|
||||
console.log('%c[Mock Socket] ❌ Connection failed (simulated)', 'color: #F44336; font-weight: bold;');
|
||||
|
||||
// 触发连接错误事件
|
||||
this.emit('connect_error', {
|
||||
@@ -351,6 +364,7 @@ class MockSocketService {
|
||||
|
||||
// 正常连接成功
|
||||
this.connected = true;
|
||||
this.connecting = false; // 清除连接中标志
|
||||
this.reconnectAttempts = 0;
|
||||
|
||||
// 清除自定义重连定时器
|
||||
@@ -360,9 +374,15 @@ class MockSocketService {
|
||||
}
|
||||
|
||||
logger.info('mockSocketService', 'Mock socket connected successfully');
|
||||
console.log('%c[Mock Socket] ✅ Connected successfully!', 'color: #4CAF50; font-weight: bold; font-size: 14px;');
|
||||
console.log(`%c[Mock Socket] Status: connected=${this.connected}, connecting=${this.connecting}`, 'color: #4CAF50;');
|
||||
|
||||
// 触发连接成功事件
|
||||
// ✅ 使用 setTimeout(0) 确保监听器已注册后再触发事件
|
||||
setTimeout(() => {
|
||||
console.log('%c[Mock Socket] Emitting connect event...', 'color: #9C27B0;');
|
||||
this.emit('connect', { timestamp: Date.now() });
|
||||
console.log('%c[Mock Socket] Connect event emitted', 'color: #9C27B0;');
|
||||
}, 0);
|
||||
|
||||
// 在连接后3秒发送欢迎消息
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -25,4 +25,73 @@ console.log(
|
||||
`color: ${useMock ? '#FF9800' : '#4CAF50'}; font-weight: bold; font-size: 12px;`
|
||||
);
|
||||
|
||||
// ========== 暴露调试 API 到全局 ==========
|
||||
if (typeof window !== 'undefined') {
|
||||
// 暴露 Socket 类型到全局
|
||||
window.SOCKET_TYPE = SOCKET_TYPE;
|
||||
|
||||
// 暴露调试 API
|
||||
window.__SOCKET_DEBUG__ = {
|
||||
// 获取当前连接状态
|
||||
getStatus: () => {
|
||||
const isConnected = socket.connected || false;
|
||||
return {
|
||||
type: SOCKET_TYPE,
|
||||
connected: isConnected,
|
||||
reconnectAttempts: socket.getReconnectAttempts?.() || 0,
|
||||
maxReconnectAttempts: socket.getMaxReconnectAttempts?.() || Infinity,
|
||||
service: useMock ? 'mockSocketService' : 'socketService',
|
||||
};
|
||||
},
|
||||
|
||||
// 手动重连
|
||||
reconnect: () => {
|
||||
console.log('[Socket Debug] Manual reconnect triggered');
|
||||
if (socket.reconnect) {
|
||||
socket.reconnect();
|
||||
} else {
|
||||
socket.disconnect();
|
||||
socket.connect();
|
||||
}
|
||||
},
|
||||
|
||||
// 断开连接
|
||||
disconnect: () => {
|
||||
console.log('[Socket Debug] Manual disconnect triggered');
|
||||
socket.disconnect();
|
||||
},
|
||||
|
||||
// 连接
|
||||
connect: () => {
|
||||
console.log('[Socket Debug] Manual connect triggered');
|
||||
socket.connect();
|
||||
},
|
||||
|
||||
// 获取服务实例 (仅用于调试)
|
||||
getService: () => socket,
|
||||
|
||||
// 导出诊断信息
|
||||
exportDiagnostics: () => {
|
||||
const status = window.__SOCKET_DEBUG__.getStatus();
|
||||
const diagnostics = {
|
||||
...status,
|
||||
timestamp: new Date().toISOString(),
|
||||
userAgent: navigator.userAgent,
|
||||
url: window.location.href,
|
||||
};
|
||||
console.log('[Socket Diagnostics]', diagnostics);
|
||||
return diagnostics;
|
||||
}
|
||||
};
|
||||
|
||||
console.log(
|
||||
'%c[Socket Debug] Debug API available at window.__SOCKET_DEBUG__',
|
||||
'color: #2196F3; font-weight: bold;'
|
||||
);
|
||||
console.log(
|
||||
'%cTry: window.__SOCKET_DEBUG__.getStatus()',
|
||||
'color: #2196F3;'
|
||||
);
|
||||
}
|
||||
|
||||
export default socket;
|
||||
|
||||
@@ -3,6 +3,43 @@
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
|
||||
// ========== 日志限流配置 ==========
|
||||
const LOG_THROTTLE_TIME = 1000; // 1秒内相同日志只输出一次
|
||||
const recentLogs = new Map(); // 日志缓存,用于去重
|
||||
const MAX_CACHE_SIZE = 100; // 最大缓存数量
|
||||
|
||||
/**
|
||||
* 生成日志的唯一键
|
||||
*/
|
||||
function getLogKey(component, message) {
|
||||
return `${component}:${message}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否应该输出日志(限流检查)
|
||||
*/
|
||||
function shouldLog(component, message) {
|
||||
const key = getLogKey(component, message);
|
||||
const now = Date.now();
|
||||
const lastLog = recentLogs.get(key);
|
||||
|
||||
// 如果1秒内已经输出过相同日志,跳过
|
||||
if (lastLog && now - lastLog < LOG_THROTTLE_TIME) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 记录日志时间
|
||||
recentLogs.set(key, now);
|
||||
|
||||
// 限制缓存大小,避免内存泄漏
|
||||
if (recentLogs.size > MAX_CACHE_SIZE) {
|
||||
const oldestKey = recentLogs.keys().next().value;
|
||||
recentLogs.delete(oldestKey);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一日志工具
|
||||
* 开发环境:输出详细日志
|
||||
@@ -20,7 +57,7 @@ export const logger = {
|
||||
* @param {object} data - 请求参数/body
|
||||
*/
|
||||
request: (method, url, data = null) => {
|
||||
if (isDevelopment) {
|
||||
if (isDevelopment && shouldLog('API', `${method} ${url}`)) {
|
||||
console.group(`🌐 API Request: ${method} ${url}`);
|
||||
console.log('Timestamp:', new Date().toISOString());
|
||||
if (data) console.log('Data:', data);
|
||||
@@ -36,7 +73,7 @@ export const logger = {
|
||||
* @param {any} data - 响应数据
|
||||
*/
|
||||
response: (method, url, status, data) => {
|
||||
if (isDevelopment) {
|
||||
if (isDevelopment && shouldLog('API', `${method} ${url} ${status}`)) {
|
||||
console.group(`✅ API Response: ${method} ${url}`);
|
||||
console.log('Status:', status);
|
||||
console.log('Data:', data);
|
||||
@@ -53,6 +90,7 @@ export const logger = {
|
||||
* @param {object} requestData - 请求参数(可选)
|
||||
*/
|
||||
error: (method, url, error, requestData = null) => {
|
||||
// API 错误始终输出,不做限流
|
||||
console.group(`❌ API Error: ${method} ${url}`);
|
||||
console.error('Error:', error);
|
||||
console.error('Message:', error?.message || error);
|
||||
@@ -75,6 +113,7 @@ export const logger = {
|
||||
* @param {object} context - 上下文信息(可选)
|
||||
*/
|
||||
error: (component, method, error, context = {}) => {
|
||||
// 错误日志始终输出,不做限流
|
||||
console.group(`🔴 Error in ${component}.${method}`);
|
||||
console.error('Error:', error);
|
||||
console.error('Message:', error?.message || error);
|
||||
@@ -93,7 +132,7 @@ export const logger = {
|
||||
* @param {object} data - 相关数据(可选)
|
||||
*/
|
||||
warn: (component, message, data = {}) => {
|
||||
if (isDevelopment) {
|
||||
if (isDevelopment && shouldLog(component, message)) {
|
||||
console.group(`⚠️ Warning: ${component}`);
|
||||
console.warn('Message:', message);
|
||||
if (Object.keys(data).length > 0) {
|
||||
@@ -111,7 +150,7 @@ export const logger = {
|
||||
* @param {object} data - 相关数据(可选)
|
||||
*/
|
||||
debug: (component, message, data = {}) => {
|
||||
if (isDevelopment) {
|
||||
if (isDevelopment && shouldLog(component, message)) {
|
||||
console.group(`🐛 Debug: ${component}`);
|
||||
console.log('Message:', message);
|
||||
if (Object.keys(data).length > 0) {
|
||||
@@ -129,7 +168,7 @@ export const logger = {
|
||||
* @param {object} data - 相关数据(可选)
|
||||
*/
|
||||
info: (component, message, data = {}) => {
|
||||
if (isDevelopment) {
|
||||
if (isDevelopment && shouldLog(component, message)) {
|
||||
console.group(`ℹ️ Info: ${component}`);
|
||||
console.log('Message:', message);
|
||||
if (Object.keys(data).length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user