feat: 修改文件 │
│ │
│ 1. src/services/socketService.js - 指数退避 + 无限重试 │
│ 2. src/components/ConnectionStatusBar/index.js - UI 优化 + 自动消失 │
│ 3. src/App.js - handleClose 实现 + dismissed 状态管理 │
│ 4. src/contexts/NotificationContext.js - 添加成功状态检测 │
│ 5. NOTIFICATION_SYSTEM.md - v2.11.0 文档更新
This commit is contained in:
@@ -306,6 +306,19 @@ class MockSocketService {
|
||||
this.listeners = new Map();
|
||||
this.intervals = [];
|
||||
this.messageQueue = [];
|
||||
this.reconnectAttempts = 0;
|
||||
this.customReconnectTimer = null;
|
||||
this.failConnection = false; // 是否模拟连接失败
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算指数退避延迟(Mock 模式使用更短的时间便于测试)
|
||||
* 第1次: 10秒, 第2次: 20秒, 第3次: 40秒, 第4次及以后: 40秒
|
||||
*/
|
||||
getReconnectionDelay(attempt) {
|
||||
const delays = [10000, 20000, 40000]; // 10s, 20s, 40s (缩短10倍便于测试)
|
||||
const index = Math.min(attempt - 1, delays.length - 1);
|
||||
return delays[index];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,7 +334,31 @@ class MockSocketService {
|
||||
|
||||
// 模拟连接延迟
|
||||
setTimeout(() => {
|
||||
// 检查是否应该模拟连接失败
|
||||
if (this.failConnection) {
|
||||
logger.warn('mockSocketService', 'Simulated connection failure');
|
||||
|
||||
// 触发连接错误事件
|
||||
this.emit('connect_error', {
|
||||
message: 'Mock connection error for testing',
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// 安排下次重连(会继续失败,直到 failConnection 被清除)
|
||||
this.scheduleReconnection();
|
||||
return;
|
||||
}
|
||||
|
||||
// 正常连接成功
|
||||
this.connected = true;
|
||||
this.reconnectAttempts = 0;
|
||||
|
||||
// 清除自定义重连定时器
|
||||
if (this.customReconnectTimer) {
|
||||
clearTimeout(this.customReconnectTimer);
|
||||
this.customReconnectTimer = null;
|
||||
}
|
||||
|
||||
logger.info('mockSocketService', 'Mock socket connected successfully');
|
||||
|
||||
// 触发连接成功事件
|
||||
@@ -329,22 +366,25 @@ class MockSocketService {
|
||||
|
||||
// 在连接后3秒发送欢迎消息
|
||||
setTimeout(() => {
|
||||
this.emit('new_event', {
|
||||
type: 'system_notification',
|
||||
severity: 'info',
|
||||
title: '连接成功',
|
||||
message: '实时消息推送服务已启动 (Mock 模式)',
|
||||
timestamp: Date.now(),
|
||||
autoClose: 5000,
|
||||
});
|
||||
if (this.connected) {
|
||||
this.emit('new_event', {
|
||||
type: 'system_notification',
|
||||
severity: 'info',
|
||||
title: '连接成功',
|
||||
message: '实时消息推送服务已启动 (Mock 模式)',
|
||||
timestamp: Date.now(),
|
||||
autoClose: 5000,
|
||||
});
|
||||
}
|
||||
}, 3000);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
* @param {boolean} triggerReconnect - 是否触发自动重连(模拟意外断开)
|
||||
*/
|
||||
disconnect() {
|
||||
disconnect(triggerReconnect = false) {
|
||||
if (!this.connected) {
|
||||
return;
|
||||
}
|
||||
@@ -355,8 +395,130 @@ class MockSocketService {
|
||||
this.intervals.forEach(interval => clearInterval(interval));
|
||||
this.intervals = [];
|
||||
|
||||
const wasConnected = this.connected;
|
||||
this.connected = false;
|
||||
this.emit('disconnect', { timestamp: Date.now() });
|
||||
this.emit('disconnect', {
|
||||
timestamp: Date.now(),
|
||||
reason: triggerReconnect ? 'transport close' : 'io client disconnect'
|
||||
});
|
||||
|
||||
// 如果需要触发重连(模拟意外断开)
|
||||
if (triggerReconnect && wasConnected) {
|
||||
this.scheduleReconnection();
|
||||
} else {
|
||||
// 清除重连定时器
|
||||
if (this.customReconnectTimer) {
|
||||
clearTimeout(this.customReconnectTimer);
|
||||
this.customReconnectTimer = null;
|
||||
}
|
||||
this.reconnectAttempts = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指数退避策略安排重连
|
||||
*/
|
||||
scheduleReconnection() {
|
||||
// 清除之前的定时器
|
||||
if (this.customReconnectTimer) {
|
||||
clearTimeout(this.customReconnectTimer);
|
||||
}
|
||||
|
||||
this.reconnectAttempts++;
|
||||
const delay = this.getReconnectionDelay(this.reconnectAttempts);
|
||||
logger.info('mockSocketService', `Scheduling reconnection in ${delay / 1000}s (attempt ${this.reconnectAttempts})`);
|
||||
|
||||
// 触发 connect_error 事件通知UI
|
||||
this.emit('connect_error', {
|
||||
message: 'Mock connection error for testing',
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
this.customReconnectTimer = setTimeout(() => {
|
||||
if (!this.connected) {
|
||||
logger.info('mockSocketService', 'Attempting reconnection...', {
|
||||
attempt: this.reconnectAttempts,
|
||||
});
|
||||
this.connect();
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动重连
|
||||
* @returns {boolean} 是否触发重连
|
||||
*/
|
||||
reconnect() {
|
||||
if (this.connected) {
|
||||
logger.info('mockSocketService', 'Already connected, no need to reconnect');
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.info('mockSocketService', 'Manually triggering reconnection...');
|
||||
|
||||
// 清除自动重连定时器
|
||||
if (this.customReconnectTimer) {
|
||||
clearTimeout(this.customReconnectTimer);
|
||||
this.customReconnectTimer = null;
|
||||
}
|
||||
|
||||
// 重置重连计数
|
||||
this.reconnectAttempts = 0;
|
||||
|
||||
// 立即触发重连
|
||||
this.connect();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟意外断线(测试用)
|
||||
* @param {number} duration - 断线持续时间(毫秒),0表示需要手动重连
|
||||
*/
|
||||
simulateDisconnection(duration = 0) {
|
||||
logger.info('mockSocketService', `Simulating disconnection${duration > 0 ? ` for ${duration}ms` : ' (manual reconnect required)'}...`);
|
||||
|
||||
if (duration > 0) {
|
||||
// 短暂断线,自动重连
|
||||
this.disconnect(true);
|
||||
} else {
|
||||
// 需要手动重连
|
||||
this.disconnect(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟持续连接失败(测试用)
|
||||
* 连接会一直失败,直到调用 allowReconnection()
|
||||
*/
|
||||
simulateConnectionFailure() {
|
||||
logger.info('mockSocketService', '🚫 Simulating persistent connection failure...');
|
||||
logger.info('mockSocketService', 'Connection will keep failing until allowReconnection() is called');
|
||||
|
||||
// 设置失败标志
|
||||
this.failConnection = true;
|
||||
|
||||
// 如果当前已连接,先断开并触发重连(会失败)
|
||||
if (this.connected) {
|
||||
this.disconnect(true);
|
||||
} else {
|
||||
// 如果未连接,直接触发一次连接尝试(会失败)
|
||||
this.connect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 允许重连成功(测试用)
|
||||
* 清除连接失败标志,下次重连将会成功
|
||||
*/
|
||||
allowReconnection() {
|
||||
logger.info('mockSocketService', '✅ Allowing reconnection to succeed...');
|
||||
logger.info('mockSocketService', 'Next reconnection attempt will succeed');
|
||||
|
||||
// 清除失败标志
|
||||
this.failConnection = false;
|
||||
|
||||
// 不立即重连,等待自动重连或手动重连
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -488,9 +650,74 @@ class MockSocketService {
|
||||
isConnected() {
|
||||
return this.connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前重连尝试次数
|
||||
*/
|
||||
getReconnectAttempts() {
|
||||
return this.reconnectAttempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最大重连次数(Mock 模式无限重试)
|
||||
*/
|
||||
getMaxReconnectAttempts() {
|
||||
return Infinity;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
export const mockSocketService = new MockSocketService();
|
||||
|
||||
// 开发模式下添加全局测试函数
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
window.__mockSocket = {
|
||||
// 模拟意外断线(自动重连成功)
|
||||
simulateDisconnection: () => {
|
||||
logger.info('mockSocketService', '🔌 Simulating disconnection (will auto-reconnect)...');
|
||||
mockSocketService.simulateDisconnection(1); // 触发自动重连
|
||||
},
|
||||
|
||||
// 模拟持续连接失败
|
||||
simulateConnectionFailure: () => {
|
||||
logger.info('mockSocketService', '🚫 Simulating connection failure (will keep retrying)...');
|
||||
mockSocketService.simulateConnectionFailure();
|
||||
},
|
||||
|
||||
// 允许重连成功
|
||||
allowReconnection: () => {
|
||||
logger.info('mockSocketService', '✅ Allowing next reconnection to succeed...');
|
||||
mockSocketService.allowReconnection();
|
||||
},
|
||||
|
||||
// 获取连接状态
|
||||
isConnected: () => {
|
||||
const connected = mockSocketService.isConnected();
|
||||
logger.info('mockSocketService', `Connection status: ${connected ? '✅ Connected' : '❌ Disconnected'}`);
|
||||
return connected;
|
||||
},
|
||||
|
||||
// 手动重连
|
||||
reconnect: () => {
|
||||
logger.info('mockSocketService', '🔄 Manually triggering reconnection...');
|
||||
return mockSocketService.reconnect();
|
||||
},
|
||||
|
||||
// 获取重连尝试次数
|
||||
getAttempts: () => {
|
||||
const attempts = mockSocketService.getReconnectAttempts();
|
||||
logger.info('mockSocketService', `Current reconnection attempts: ${attempts}`);
|
||||
return attempts;
|
||||
},
|
||||
};
|
||||
|
||||
logger.info('mockSocketService', '💡 Mock Socket test functions available:');
|
||||
logger.info('mockSocketService', ' __mockSocket.simulateDisconnection() - 模拟断线(自动重连成功)');
|
||||
logger.info('mockSocketService', ' __mockSocket.simulateConnectionFailure() - 模拟连接失败(持续失败)');
|
||||
logger.info('mockSocketService', ' __mockSocket.allowReconnection() - 允许重连成功');
|
||||
logger.info('mockSocketService', ' __mockSocket.isConnected() - 查看连接状态');
|
||||
logger.info('mockSocketService', ' __mockSocket.reconnect() - 手动重连');
|
||||
logger.info('mockSocketService', ' __mockSocket.getAttempts() - 查看重连次数');
|
||||
}
|
||||
|
||||
export default mockSocketService;
|
||||
|
||||
@@ -14,7 +14,18 @@ class SocketService {
|
||||
this.socket = null;
|
||||
this.connected = false;
|
||||
this.reconnectAttempts = 0;
|
||||
this.maxReconnectAttempts = 5;
|
||||
this.maxReconnectAttempts = Infinity; // 无限重试
|
||||
this.customReconnectTimer = null; // 自定义重连定时器
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算指数退避延迟
|
||||
* 第1次: 60秒, 第2次: 120秒, 第3次: 240秒, 第4次及以后: 240秒
|
||||
*/
|
||||
getReconnectionDelay(attempt) {
|
||||
const delays = [60000, 120000, 240000]; // 1min, 2min, 4min
|
||||
const index = Math.min(attempt - 1, delays.length - 1);
|
||||
return delays[index];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,13 +40,10 @@ class SocketService {
|
||||
|
||||
logger.info('socketService', 'Connecting to Socket.IO server...', { url: API_BASE_URL });
|
||||
|
||||
// 创建 socket 连接
|
||||
// 创建 socket 连接 - 禁用 Socket.IO 自带的重连机制,使用自定义指数退避
|
||||
this.socket = io(API_BASE_URL, {
|
||||
transports: ['websocket', 'polling'],
|
||||
reconnection: true,
|
||||
reconnectionDelay: 1000,
|
||||
reconnectionDelayMax: 5000,
|
||||
reconnectionAttempts: this.maxReconnectAttempts,
|
||||
reconnection: false, // 禁用自动重连,改用自定义策略
|
||||
timeout: 20000,
|
||||
autoConnect: true,
|
||||
withCredentials: true, // 允许携带认证信息
|
||||
@@ -46,6 +54,13 @@ class SocketService {
|
||||
this.socket.on('connect', () => {
|
||||
this.connected = true;
|
||||
this.reconnectAttempts = 0;
|
||||
|
||||
// 清除自定义重连定时器
|
||||
if (this.customReconnectTimer) {
|
||||
clearTimeout(this.customReconnectTimer);
|
||||
this.customReconnectTimer = null;
|
||||
}
|
||||
|
||||
logger.info('socketService', 'Socket.IO connected successfully', {
|
||||
socketId: this.socket.id,
|
||||
});
|
||||
@@ -53,8 +68,14 @@ class SocketService {
|
||||
|
||||
// 监听断开连接
|
||||
this.socket.on('disconnect', (reason) => {
|
||||
const wasConnected = this.connected;
|
||||
this.connected = false;
|
||||
logger.warn('socketService', 'Socket.IO disconnected', { reason });
|
||||
|
||||
// 如果是意外断开(非主动断开),触发自定义重连
|
||||
if (wasConnected && reason !== 'io client disconnect') {
|
||||
this.scheduleReconnection();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听连接错误
|
||||
@@ -62,30 +83,33 @@ class SocketService {
|
||||
this.reconnectAttempts++;
|
||||
logger.error('socketService', 'connect_error', error, {
|
||||
attempts: this.reconnectAttempts,
|
||||
maxAttempts: this.maxReconnectAttempts,
|
||||
});
|
||||
|
||||
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||
logger.error('socketService', 'Max reconnection attempts reached');
|
||||
this.socket.close();
|
||||
// 使用指数退避策略安排下次重连
|
||||
this.scheduleReconnection();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指数退避策略安排重连
|
||||
*/
|
||||
scheduleReconnection() {
|
||||
// 清除之前的定时器
|
||||
if (this.customReconnectTimer) {
|
||||
clearTimeout(this.customReconnectTimer);
|
||||
}
|
||||
|
||||
const delay = this.getReconnectionDelay(this.reconnectAttempts);
|
||||
logger.info('socketService', `Scheduling reconnection in ${delay / 1000}s (attempt ${this.reconnectAttempts})`);
|
||||
|
||||
this.customReconnectTimer = setTimeout(() => {
|
||||
if (!this.connected && this.socket) {
|
||||
logger.info('socketService', 'Attempting reconnection...', {
|
||||
attempt: this.reconnectAttempts,
|
||||
});
|
||||
this.socket.connect();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听重连尝试
|
||||
this.socket.io.on('reconnect_attempt', (attemptNumber) => {
|
||||
logger.info('socketService', 'Reconnection attempt', { attemptNumber });
|
||||
});
|
||||
|
||||
// 监听重连成功
|
||||
this.socket.io.on('reconnect', (attemptNumber) => {
|
||||
this.reconnectAttempts = 0;
|
||||
logger.info('socketService', 'Reconnected successfully', { attemptNumber });
|
||||
});
|
||||
|
||||
// 监听重连失败
|
||||
this.socket.io.on('reconnect_failed', () => {
|
||||
logger.error('socketService', 'Reconnection failed after max attempts');
|
||||
});
|
||||
}, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,9 +121,17 @@ class SocketService {
|
||||
}
|
||||
|
||||
logger.info('socketService', 'Disconnecting from Socket.IO server...');
|
||||
|
||||
// 清除自定义重连定时器
|
||||
if (this.customReconnectTimer) {
|
||||
clearTimeout(this.customReconnectTimer);
|
||||
this.customReconnectTimer = null;
|
||||
}
|
||||
|
||||
this.socket.disconnect();
|
||||
this.socket = null;
|
||||
this.connected = false;
|
||||
this.reconnectAttempts = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,10 +236,16 @@ class SocketService {
|
||||
|
||||
logger.info('socketService', 'Manually triggering reconnection...');
|
||||
|
||||
// 清除自动重连定时器
|
||||
if (this.customReconnectTimer) {
|
||||
clearTimeout(this.customReconnectTimer);
|
||||
this.customReconnectTimer = null;
|
||||
}
|
||||
|
||||
// 重置重连计数
|
||||
this.reconnectAttempts = 0;
|
||||
|
||||
// 触发重连
|
||||
// 立即触发重连
|
||||
this.socket.connect();
|
||||
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user