fix(socket): 保留暂存监听器,修复重连后事件监听器丢失问题
## 问题
生产环境 Socket 已连接且订阅成功,但收到事件时不触发通知:
- Socket 连接正常:`connected: true`
- 订阅成功:`已订阅 all 类型的事件推送`
- **但是 `new_event` 监听器未注册**:`_callbacks.$new_event: undefined`
- Network 面板显示后端推送的消息已到达
## 根本原因
`socketService.js` 的监听器注册机制有缺陷:
### 原始逻辑(有问题):
```javascript
// connect() 方法中
if (this.pendingListeners.length > 0) {
this.pendingListeners.forEach(({ event, callback }) => {
this.on(event, callback); // 注册监听器
});
this.pendingListeners = []; // ❌ 清空暂存队列
}
```
### 问题:
1. **首次连接**:监听器从 `pendingListeners` 注册到 Socket,然后清空队列
2. **Socket 重连**:`pendingListeners` 已被清空,无法重新注册监听器
3. **结果**:重连后 `new_event` 监听器丢失,事件无法触发
### 为什么会重连?
- 用户网络波动
- 服务器重启
- 浏览器从休眠恢复
- Socket.IO 底层重连机制
## 解决方案
### 修改 1:保留 `pendingListeners`(不清空)
**文件**:`src/services/socketService.js:54-69`
```javascript
// 注册所有暂存的事件监听器(保留 pendingListeners,不清空)
if (this.pendingListeners.length > 0) {
console.log(`[socketService] 📦 注册 ${this.pendingListeners.length} 个暂存的事件监听器`);
this.pendingListeners.forEach(({ event, callback }) => {
// 直接在 Socket.IO 实例上注册(避免递归调用 this.on())
const wrappedCallback = (...args) => {
console.log(`%c[socketService] 🔔 收到原始事件: ${event}`, ...);
callback(...args);
};
this.socket.on(event, wrappedCallback);
console.log(`[socketService] ✓ 已注册事件监听器: ${event}`);
});
// ⚠️ 重要:不清空 pendingListeners,保留用于重连
}
```
**变更**:
- ❌ 删除:`this.pendingListeners = [];`
- ✅ 新增:直接在 `this.socket.on()` 上注册(避免递归)
- ✅ 保留:`pendingListeners` 数组,用于重连时重新注册
### 修改 2:避免重复注册
**文件**:`src/services/socketService.js:166-181`
```javascript
on(event, callback) {
if (!this.socket) {
// Socket 未初始化,暂存监听器(检查是否已存在,避免重复)
const exists = this.pendingListeners.some(
(listener) => listener.event === event && listener.callback === callback
);
if (!exists) {
this.pendingListeners.push({ event, callback });
} else {
console.log(`[socketService] ⚠️ 监听器已存在,跳过: ${event}`);
}
return;
}
// ...
}
```
**变更**:
- ✅ 新增:检查监听器是否已存在(`event` 和 `callback` 都匹配)
- ✅ 避免:重复添加相同监听器到 `pendingListeners`
## 效果
### 修复前:
```
首次连接: ✅ new_event 监听器注册
重连后: ❌ new_event 监听器丢失
事件推送: ❌ 不触发通知
```
### 修复后:
```
首次连接: ✅ new_event 监听器注册
重连后: ✅ new_event 监听器自动重新注册
事件推送: ✅ 正常触发通知
```
## 验证步骤
部署后在浏览器 Console 执行:
```javascript
// 1. 检查监听器
window.socket.socket._callbacks.$new_event // 应该有 1-2 个监听器
// 2. 手动断开重连
window.socket.disconnect();
setTimeout(() => window.socket.connect(), 1000);
// 3. 重连后再次检查
window.socket.socket._callbacks.$new_event // 应该仍然有监听器
// 4. 等待后端推送事件,验证通知显示
```
## 影响范围
- 修改文件: `src/services/socketService.js`(1 个文件,2 处修改)
- 影响功能: Socket 事件监听器注册机制
- 风险等级: 低(只修改监听器管理逻辑,不改变业务代码)
## 相关 Issue
- 修复生产环境 Socket 事件不触发通知问题
- 解决 Socket 重连后监听器丢失问题
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -51,13 +51,21 @@ class SocketService {
|
|||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 注册所有暂存的事件监听器
|
// 注册所有暂存的事件监听器(保留 pendingListeners,不清空)
|
||||||
if (this.pendingListeners.length > 0) {
|
if (this.pendingListeners.length > 0) {
|
||||||
console.log(`[socketService] 📦 注册 ${this.pendingListeners.length} 个暂存的事件监听器`);
|
console.log(`[socketService] 📦 注册 ${this.pendingListeners.length} 个暂存的事件监听器`);
|
||||||
this.pendingListeners.forEach(({ event, callback }) => {
|
this.pendingListeners.forEach(({ event, callback }) => {
|
||||||
this.on(event, callback);
|
// 直接在 Socket.IO 实例上注册(避免递归调用 this.on())
|
||||||
|
const wrappedCallback = (...args) => {
|
||||||
|
console.log(`%c[socketService] 🔔 收到原始事件: ${event}`, 'color: #2196F3; font-weight: bold;');
|
||||||
|
console.log(`[socketService] 事件数据 (${event}):`, ...args);
|
||||||
|
callback(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.socket.on(event, wrappedCallback);
|
||||||
|
console.log(`[socketService] ✓ 已注册事件监听器: ${event}`);
|
||||||
});
|
});
|
||||||
this.pendingListeners = []; // 清空暂存队列
|
// ⚠️ 重要:不清空 pendingListeners,保留用于重连
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听连接成功
|
// 监听连接成功
|
||||||
@@ -157,10 +165,18 @@ class SocketService {
|
|||||||
*/
|
*/
|
||||||
on(event, callback) {
|
on(event, callback) {
|
||||||
if (!this.socket) {
|
if (!this.socket) {
|
||||||
// Socket 未初始化,暂存监听器
|
// Socket 未初始化,暂存监听器(检查是否已存在,避免重复)
|
||||||
logger.info('socketService', 'Socket not ready, queuing listener', { event });
|
const exists = this.pendingListeners.some(
|
||||||
console.log(`[socketService] 📦 Socket 未初始化,暂存事件监听器: ${event}`);
|
(listener) => listener.event === event && listener.callback === callback
|
||||||
this.pendingListeners.push({ event, callback });
|
);
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
logger.info('socketService', 'Socket not ready, queuing listener', { event });
|
||||||
|
console.log(`[socketService] 📦 Socket 未初始化,暂存事件监听器: ${event}`);
|
||||||
|
this.pendingListeners.push({ event, callback });
|
||||||
|
} else {
|
||||||
|
console.log(`[socketService] ⚠️ 监听器已存在,跳过: ${event}`);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user