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:
zdl
2025-10-27 13:13:56 +08:00
parent 227e1c9d15
commit a9dc1191bf
5 changed files with 182 additions and 40 deletions

View File

@@ -572,14 +572,10 @@ export const NotificationProvider = ({ children }) => {
// 连接到 Socket 服务 // 连接到 Socket 服务
useEffect(() => { useEffect(() => {
logger.info('NotificationContext', 'Initializing socket connection...'); logger.info('NotificationContext', 'Initializing socket connection...');
console.log(`%c[NotificationContext] Initializing socket (type: ${SOCKET_TYPE})`, 'color: #673AB7; font-weight: bold;');
// 连接 socket // ✅ 第一步: 注册所有事件监听器
socket.connect(); console.log('%c[NotificationContext] Step 1: Registering event listeners...', 'color: #673AB7;');
// 获取并保存最大重连次数
const maxAttempts = socket.getMaxReconnectAttempts?.() || Infinity;
setMaxReconnectAttempts(maxAttempts);
logger.info('NotificationContext', 'Max reconnect attempts', { maxAttempts });
// 监听连接状态 // 监听连接状态
socket.on('connect', () => { socket.on('connect', () => {
@@ -587,6 +583,7 @@ export const NotificationProvider = ({ children }) => {
setIsConnected(true); setIsConnected(true);
setReconnectAttempt(0); setReconnectAttempt(0);
logger.info('NotificationContext', 'Socket connected', { wasDisconnected }); logger.info('NotificationContext', 'Socket connected', { wasDisconnected });
console.log('%c[NotificationContext] ✅ Received connect event, updating state to connected', 'color: #4CAF50; font-weight: bold;');
// 如果之前断开过,显示 RECONNECTED 状态2秒后自动消失 // 如果之前断开过,显示 RECONNECTED 状态2秒后自动消失
if (wasDisconnected) { if (wasDisconnected) {
@@ -683,6 +680,18 @@ export const NotificationProvider = ({ children }) => {
addNotification(data); 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 () => { return () => {
logger.info('NotificationContext', 'Cleaning up socket connection'); logger.info('NotificationContext', 'Cleaning up socket connection');
@@ -700,7 +709,7 @@ export const NotificationProvider = ({ children }) => {
socket.off('system_notification'); socket.off('system_notification');
socket.disconnect(); socket.disconnect();
}; };
}, [adaptEventToNotification, connectionStatus, toast]); // eslint-disable-line react-hooks/exhaustive-deps }, []); // ✅ 空依赖数组,确保只执行一次,避免 React 严格模式重复执行
// ==================== 智能自动重试 ==================== // ==================== 智能自动重试 ====================

View File

@@ -22,7 +22,7 @@
*/ */
import { useEffect, useState, useRef } from 'react'; import { useEffect, useState, useRef } from 'react';
import { socketService } from '../services/socketService'; import socket from '../services/socket';
import { logger } from '../utils/logger'; import { logger } from '../utils/logger';
export const useEventNotifications = (options = {}) => { export const useEventNotifications = (options = {}) => {
@@ -80,16 +80,16 @@ export const useEventNotifications = (options = {}) => {
}; };
// 监听连接事件必须在connect之前设置否则可能错过事件 // 监听连接事件必须在connect之前设置否则可能错过事件
socketService.on('connect', handleConnect); socket.on('connect', handleConnect);
socketService.on('disconnect', handleDisconnect); socket.on('disconnect', handleDisconnect);
socketService.on('connect_error', handleConnectError); socket.on('connect_error', handleConnectError);
// 连接 WebSocket // 连接 WebSocket
console.log('[useEventNotifications DEBUG] 准备连接 WebSocket...'); console.log('[useEventNotifications DEBUG] 准备连接 WebSocket...');
logger.info('useEventNotifications', 'Initializing WebSocket connection'); logger.info('useEventNotifications', 'Initializing WebSocket connection');
// 先检查是否已经连接 // 先检查是否已经连接
const alreadyConnected = socketService.isConnected(); const alreadyConnected = socket.connected || false;
console.log('[useEventNotifications DEBUG] 当前连接状态:', alreadyConnected); console.log('[useEventNotifications DEBUG] 当前连接状态:', alreadyConnected);
logger.info('useEventNotifications', 'Pre-connection check', { isConnected: alreadyConnected }); logger.info('useEventNotifications', 'Pre-connection check', { isConnected: alreadyConnected });
@@ -99,7 +99,7 @@ export const useEventNotifications = (options = {}) => {
setIsConnected(true); setIsConnected(true);
} else { } else {
// 否则建立新连接 // 否则建立新连接
socketService.connect(); socket.connect();
} }
// 新事件处理函数 - 使用 ref 中的回调 // 新事件处理函数 - 使用 ref 中的回调
@@ -131,21 +131,28 @@ export const useEventNotifications = (options = {}) => {
console.log('[useEventNotifications DEBUG] importance:', importance); console.log('[useEventNotifications DEBUG] importance:', importance);
console.log('[useEventNotifications DEBUG] enabled:', enabled); console.log('[useEventNotifications DEBUG] enabled:', enabled);
socketService.subscribeToEvents({ // 检查 socket 是否有 subscribeToEvents 方法mockSocketService 和 socketService 都有)
eventType, if (socket.subscribeToEvents) {
importance, socket.subscribeToEvents({
onNewEvent: handleNewEvent, eventType,
onSubscribed: (data) => { importance,
console.log('\n[useEventNotifications DEBUG] ========== 订阅成功回调 =========='); onNewEvent: handleNewEvent,
console.log('[useEventNotifications DEBUG] 订阅数据:', data); onSubscribed: (data) => {
console.log('[useEventNotifications DEBUG] ========== 订阅成功处理完成 ==========\n'); console.log('\n[useEventNotifications DEBUG] ========== 订阅成功回调 ==========');
}, console.log('[useEventNotifications DEBUG] 订阅数据:', data);
}); console.log('[useEventNotifications DEBUG] ========== 订阅成功处理完成 ==========\n');
console.log('[useEventNotifications DEBUG] ========== 订阅请求已发送 ==========\n'); },
});
console.log('[useEventNotifications DEBUG] ========== 订阅请求已发送 ==========\n');
} else {
console.warn('[useEventNotifications] socket.subscribeToEvents 方法不存在');
}
// 保存取消订阅函数 // 保存取消订阅函数
unsubscribeRef.current = () => { unsubscribeRef.current = () => {
socketService.unsubscribeFromEvents({ eventType }); if (socket.unsubscribeFromEvents) {
socket.unsubscribeFromEvents({ eventType });
}
}; };
// 组件卸载时清理 // 组件卸载时清理
@@ -160,14 +167,12 @@ export const useEventNotifications = (options = {}) => {
// 移除监听器 // 移除监听器
console.log('[useEventNotifications DEBUG] 移除事件监听器...'); console.log('[useEventNotifications DEBUG] 移除事件监听器...');
socketService.off('connect', handleConnect); socket.off('connect', handleConnect);
socketService.off('disconnect', handleDisconnect); socket.off('disconnect', handleDisconnect);
socketService.off('connect_error', handleConnectError); socket.off('connect_error', handleConnectError);
// 断开连接
console.log('[useEventNotifications DEBUG] 断开 WebSocket 连接...');
socketService.disconnect();
// 注意:不断开连接,因为 socket 是全局共享的
// 由 NotificationContext 统一管理连接生命周期
console.log('[useEventNotifications DEBUG] ========== 清理完成 ==========\n'); console.log('[useEventNotifications DEBUG] ========== 清理完成 ==========\n');
}; };
}, [eventType, importance, enabled]); // 移除 onNewEvent 依赖 }, [eventType, importance, enabled]); // 移除 onNewEvent 依赖

View File

@@ -303,6 +303,7 @@ const mockFinancialNews = [
class MockSocketService { class MockSocketService {
constructor() { constructor() {
this.connected = false; this.connected = false;
this.connecting = false; // 新增:正在连接标志,防止重复连接
this.listeners = new Map(); this.listeners = new Map();
this.intervals = []; this.intervals = [];
this.messageQueue = []; this.messageQueue = [];
@@ -325,18 +326,30 @@ class MockSocketService {
* 连接到 mock socket * 连接到 mock socket
*/ */
connect() { connect() {
// ✅ 防止重复连接
if (this.connected) { if (this.connected) {
logger.warn('mockSocketService', 'Already connected'); logger.warn('mockSocketService', 'Already connected');
console.log('%c[Mock Socket] Already connected, skipping', 'color: #FF9800; font-weight: bold;');
return; 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...'); logger.info('mockSocketService', 'Connecting to mock socket service...');
console.log('%c[Mock Socket] 🔌 Connecting...', 'color: #2196F3; font-weight: bold;');
// 模拟连接延迟 // 模拟连接延迟
setTimeout(() => { setTimeout(() => {
// 检查是否应该模拟连接失败 // 检查是否应该模拟连接失败
if (this.failConnection) { if (this.failConnection) {
this.connecting = false; // 清除连接中标志
logger.warn('mockSocketService', 'Simulated connection failure'); logger.warn('mockSocketService', 'Simulated connection failure');
console.log('%c[Mock Socket] ❌ Connection failed (simulated)', 'color: #F44336; font-weight: bold;');
// 触发连接错误事件 // 触发连接错误事件
this.emit('connect_error', { this.emit('connect_error', {
@@ -351,6 +364,7 @@ class MockSocketService {
// 正常连接成功 // 正常连接成功
this.connected = true; this.connected = true;
this.connecting = false; // 清除连接中标志
this.reconnectAttempts = 0; this.reconnectAttempts = 0;
// 清除自定义重连定时器 // 清除自定义重连定时器
@@ -360,9 +374,15 @@ class MockSocketService {
} }
logger.info('mockSocketService', 'Mock socket connected successfully'); 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) 确保监听器已注册后再触发事件
this.emit('connect', { timestamp: Date.now() }); 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秒发送欢迎消息 // 在连接后3秒发送欢迎消息
setTimeout(() => { setTimeout(() => {

View File

@@ -25,4 +25,73 @@ console.log(
`color: ${useMock ? '#FF9800' : '#4CAF50'}; font-weight: bold; font-size: 12px;` `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; export default socket;

View File

@@ -3,6 +3,43 @@
const isDevelopment = process.env.NODE_ENV === 'development'; 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 * @param {object} data - 请求参数/body
*/ */
request: (method, url, data = null) => { request: (method, url, data = null) => {
if (isDevelopment) { if (isDevelopment && shouldLog('API', `${method} ${url}`)) {
console.group(`🌐 API Request: ${method} ${url}`); console.group(`🌐 API Request: ${method} ${url}`);
console.log('Timestamp:', new Date().toISOString()); console.log('Timestamp:', new Date().toISOString());
if (data) console.log('Data:', data); if (data) console.log('Data:', data);
@@ -36,7 +73,7 @@ export const logger = {
* @param {any} data - 响应数据 * @param {any} data - 响应数据
*/ */
response: (method, url, status, data) => { response: (method, url, status, data) => {
if (isDevelopment) { if (isDevelopment && shouldLog('API', `${method} ${url} ${status}`)) {
console.group(`✅ API Response: ${method} ${url}`); console.group(`✅ API Response: ${method} ${url}`);
console.log('Status:', status); console.log('Status:', status);
console.log('Data:', data); console.log('Data:', data);
@@ -53,6 +90,7 @@ export const logger = {
* @param {object} requestData - 请求参数(可选) * @param {object} requestData - 请求参数(可选)
*/ */
error: (method, url, error, requestData = null) => { error: (method, url, error, requestData = null) => {
// API 错误始终输出,不做限流
console.group(`❌ API Error: ${method} ${url}`); console.group(`❌ API Error: ${method} ${url}`);
console.error('Error:', error); console.error('Error:', error);
console.error('Message:', error?.message || error); console.error('Message:', error?.message || error);
@@ -75,6 +113,7 @@ export const logger = {
* @param {object} context - 上下文信息(可选) * @param {object} context - 上下文信息(可选)
*/ */
error: (component, method, error, context = {}) => { error: (component, method, error, context = {}) => {
// 错误日志始终输出,不做限流
console.group(`🔴 Error in ${component}.${method}`); console.group(`🔴 Error in ${component}.${method}`);
console.error('Error:', error); console.error('Error:', error);
console.error('Message:', error?.message || error); console.error('Message:', error?.message || error);
@@ -93,7 +132,7 @@ export const logger = {
* @param {object} data - 相关数据(可选) * @param {object} data - 相关数据(可选)
*/ */
warn: (component, message, data = {}) => { warn: (component, message, data = {}) => {
if (isDevelopment) { if (isDevelopment && shouldLog(component, message)) {
console.group(`⚠️ Warning: ${component}`); console.group(`⚠️ Warning: ${component}`);
console.warn('Message:', message); console.warn('Message:', message);
if (Object.keys(data).length > 0) { if (Object.keys(data).length > 0) {
@@ -111,7 +150,7 @@ export const logger = {
* @param {object} data - 相关数据(可选) * @param {object} data - 相关数据(可选)
*/ */
debug: (component, message, data = {}) => { debug: (component, message, data = {}) => {
if (isDevelopment) { if (isDevelopment && shouldLog(component, message)) {
console.group(`🐛 Debug: ${component}`); console.group(`🐛 Debug: ${component}`);
console.log('Message:', message); console.log('Message:', message);
if (Object.keys(data).length > 0) { if (Object.keys(data).length > 0) {
@@ -129,7 +168,7 @@ export const logger = {
* @param {object} data - 相关数据(可选) * @param {object} data - 相关数据(可选)
*/ */
info: (component, message, data = {}) => { info: (component, message, data = {}) => {
if (isDevelopment) { if (isDevelopment && shouldLog(component, message)) {
console.group(` Info: ${component}`); console.group(` Info: ${component}`);
console.log('Message:', message); console.log('Message:', message);
if (Object.keys(data).length > 0) { if (Object.keys(data).length > 0) {