From 926ffa1b8f5f324d3d96523cf3d6de2450bba6bb Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 11 Nov 2025 14:16:00 +0800 Subject: [PATCH] =?UTF-8?q?fix(socket):=20=E4=BF=9D=E7=95=99=E6=9A=82?= =?UTF-8?q?=E5=AD=98=E7=9B=91=E5=90=AC=E5=99=A8=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=87=8D=E8=BF=9E=E5=90=8E=E4=BA=8B=E4=BB=B6=E7=9B=91=E5=90=AC?= =?UTF-8?q?=E5=99=A8=E4=B8=A2=E5=A4=B1=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 生产环境 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 --- src/services/socketService.js | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/services/socketService.js b/src/services/socketService.js index 3203f923..2e8d22cb 100644 --- a/src/services/socketService.js +++ b/src/services/socketService.js @@ -51,13 +51,21 @@ class SocketService { ...options, }); - // 注册所有暂存的事件监听器 + // 注册所有暂存的事件监听器(保留 pendingListeners,不清空) if (this.pendingListeners.length > 0) { console.log(`[socketService] 📦 注册 ${this.pendingListeners.length} 个暂存的事件监听器`); 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) { if (!this.socket) { - // Socket 未初始化,暂存监听器 - logger.info('socketService', 'Socket not ready, queuing listener', { event }); - console.log(`[socketService] 📦 Socket 未初始化,暂存事件监听器: ${event}`); - this.pendingListeners.push({ event, callback }); + // Socket 未初始化,暂存监听器(检查是否已存在,避免重复) + const exists = this.pendingListeners.some( + (listener) => listener.event === event && listener.callback === 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; }