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 服务
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 严格模式重复执行
// ==================== 智能自动重试 ====================

View File

@@ -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 依赖

View File

@@ -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(() => {

View File

@@ -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;

View File

@@ -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) {