From a9dc1191bfaa6a3024f3507702742cc1250bb3d4 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Mon, 27 Oct 2025 13:13:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:.=20mockSocketService=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20connecting=20=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 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) 调用 - 确保监听器注册完毕后再触发事件\ --- src/contexts/NotificationContext.js | 25 +++++++---- src/hooks/useEventNotifications.js | 55 ++++++++++++----------- src/services/mockSocketService.js | 24 +++++++++- src/services/socket/index.js | 69 +++++++++++++++++++++++++++++ src/utils/logger.js | 49 +++++++++++++++++--- 5 files changed, 182 insertions(+), 40 deletions(-) diff --git a/src/contexts/NotificationContext.js b/src/contexts/NotificationContext.js index 68c0f25e..b6d4853b 100644 --- a/src/contexts/NotificationContext.js +++ b/src/contexts/NotificationContext.js @@ -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 严格模式重复执行 // ==================== 智能自动重试 ==================== diff --git a/src/hooks/useEventNotifications.js b/src/hooks/useEventNotifications.js index c9611c7d..3156cbf4 100644 --- a/src/hooks/useEventNotifications.js +++ b/src/hooks/useEventNotifications.js @@ -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,21 +131,28 @@ export const useEventNotifications = (options = {}) => { console.log('[useEventNotifications DEBUG] importance:', importance); console.log('[useEventNotifications DEBUG] enabled:', enabled); - socketService.subscribeToEvents({ - eventType, - importance, - onNewEvent: handleNewEvent, - onSubscribed: (data) => { - console.log('\n[useEventNotifications DEBUG] ========== 订阅成功回调 =========='); - console.log('[useEventNotifications DEBUG] 订阅数据:', data); - console.log('[useEventNotifications DEBUG] ========== 订阅成功处理完成 ==========\n'); - }, - }); - console.log('[useEventNotifications DEBUG] ========== 订阅请求已发送 ==========\n'); + // 检查 socket 是否有 subscribeToEvents 方法(mockSocketService 和 socketService 都有) + if (socket.subscribeToEvents) { + socket.subscribeToEvents({ + eventType, + importance, + onNewEvent: handleNewEvent, + onSubscribed: (data) => { + console.log('\n[useEventNotifications DEBUG] ========== 订阅成功回调 =========='); + console.log('[useEventNotifications DEBUG] 订阅数据:', data); + console.log('[useEventNotifications DEBUG] ========== 订阅成功处理完成 ==========\n'); + }, + }); + 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 依赖 diff --git a/src/services/mockSocketService.js b/src/services/mockSocketService.js index a41c3d87..d1b03ae8 100644 --- a/src/services/mockSocketService.js +++ b/src/services/mockSocketService.js @@ -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;'); - // 触发连接成功事件 - this.emit('connect', { timestamp: Date.now() }); + // ✅ 使用 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(() => { diff --git a/src/services/socket/index.js b/src/services/socket/index.js index dfecdad3..99924555 100644 --- a/src/services/socket/index.js +++ b/src/services/socket/index.js @@ -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; diff --git a/src/utils/logger.js b/src/utils/logger.js index 10c24644..e0a5dcd2 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -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) {