# WebSocket增强服务技术设计文档 ## 概述 本文档详细解释了 `src/services/ws.ts` 的技术设计思路、架构选择和实现细节。这个文件实现了一个增强的WebSocket服务,在保持原有接口不变的前提下,添加了心跳检测、自动重连、错误处理等企业级功能。 ## 设计目标 ### 主要目标 1. **零侵入性**:业务代码无需修改,完全透明的功能增强 2. **企业级稳定性**:心跳检测、自动重连、错误恢复 3. **可配置性**:全局配置,易于调整和优化 4. **类型安全**:完整的TypeScript类型支持 5. **内存安全**:正确的资源管理,防止内存泄漏 ### 兼容性目标 - 保持原有 `create(path): Promise` 接口不变 - 返回标准WebSocket实例,支持所有原生API - 业务代码中的 `ws.onmessage`, `ws.close()` 等调用完全兼容 ## 架构设计 ### 整体架构图 ``` ┌─────────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐ │ 业务代码 │ │ EnhancedWebSocket │ │ 原生WebSocket │ │ │ │ (包装器) │ │ │ │ ws.onmessage = ... │───▶│ 事件拦截和过滤 │───▶│ 实际网络连接 │ │ ws.send(data) │ │ 心跳检测逻辑 │ │ │ │ ws.close() │ │ 重连管理 │ │ │ └─────────────────────┘ └──────────────────────┘ └─────────────────────┘ │ ▼ ┌──────────────────────┐ │ WS_CONFIG │ │ (全局配置) │ │ - 心跳间隔 │ │ - 重连策略 │ │ - 超时设置 │ └──────────────────────┘ ``` ### 设计模式选择 #### 1. 包装器模式 (Wrapper Pattern) ```typescript class EnhancedWebSocket { private ws: WebSocket; // 包装原生WebSocket } ``` **为什么选择包装器而不是继承?** 1. **继承的问题**: ```typescript // 继承方式的问题 class EnhancedWebSocket extends WebSocket { constructor(url: string) { super(url); // 连接立即开始,无法在事件处理器设置前进行拦截 } } ``` 2. **包装器的优势**: ```typescript // 包装器方式的优势 class EnhancedWebSocket { constructor(path: string, baseUrl: string) { this.ws = new WebSocket(baseUrl + path); // 控制创建时机 this.setupHandlers(); // 立即设置我们的处理器 } } ``` #### 2. 代理模式 (Proxy Pattern) 通过getter/setter拦截用户对事件处理器的设置: ```typescript get onmessage(): ((event: MessageEvent) => void) | null { return this.userOnMessage; } set onmessage(handler: ((event: MessageEvent) => void) | null) { this.userOnMessage = handler; // 保存用户的处理器 // 我们的处理器已经在构造时设置,会调用用户的处理器 } ``` ## 核心技术实现 ### 1. Class 设计选择 #### 为什么使用 Class? ```typescript class EnhancedWebSocket { // 私有状态管理 private ws: WebSocket; private path: string; private heartbeatTimer?: NodeJS.Timeout; // ... } ``` **选择Class的原因:** 1. **状态封装**:WebSocket连接需要管理多个状态(连接、定时器、配置等) 2. **方法绑定**:事件处理器需要访问实例状态,Class提供了自然的this绑定 3. **生命周期管理**:连接的创建、维护、销毁有清晰的生命周期 4. **类型安全**:TypeScript对Class有更好的类型推导和检查 **与函数式方案的对比:** ```typescript // 函数式方案的问题 function createEnhancedWS(path: string) { let heartbeatTimer: NodeJS.Timeout; let reconnectTimer: NodeJS.Timeout; // 需要大量闭包来管理状态,复杂度高 } // Class方案的优势 class EnhancedWebSocket { private heartbeatTimer?: NodeJS.Timeout; // 清晰的状态管理 private reconnectTimer?: NodeJS.Timeout; // 方法可以直接访问状态 } ``` ### 2. Private 成员设计 #### 为什么大量使用 private? ```typescript class EnhancedWebSocket { private ws: WebSocket; // 内部WebSocket实例 private path: string; // 连接路径 private heartbeatTimer?: NodeJS.Timeout; // 心跳定时器 private reconnectTimer?: NodeJS.Timeout; // 重连定时器 private reconnectAttempts: number = 0; // 重连次数 private isManualClose: boolean = false; // 手动关闭标志 private isHeartbeatTimeout: boolean = false; // 心跳超时标志 } ``` **Private的重要性:** 1. **封装原则**:防止外部直接访问和修改内部状态 2. **API稳定性**:内部实现可以随时重构,不影响公共接口 3. **状态一致性**:防止外部代码破坏内部状态的一致性 4. **错误预防**:避免用户误用内部API导致的bug **示例对比:** ```typescript // 如果没有private,用户可能这样做 const ws = new EnhancedWebSocket('/test'); ws.heartbeatTimer = undefined; // 💥 破坏了心跳检测 ws.reconnectAttempts = -1; // 💥 破坏了重连逻辑 // 有了private,这些操作被编译器阻止 // ✅ 确保了内部状态的安全性 ``` ### 3. Constructor 设计 #### 构造函数的关键作用 ```typescript constructor(path: string, baseUrl: string) { this.path = path; this.baseUrl = baseUrl; this.ws = new WebSocket(baseUrl + path); // 创建实际连接 this.setupHandlers(); // 立即设置事件处理器 } ``` **设计要点:** 1. **立即执行**:构造时立即创建连接和设置处理器 2. **状态初始化**:确保所有私有状态都有正确的初始值 3. **参数验证**:(可以添加)对输入参数进行验证 4. **最小权限**:只接收必要的参数,其他配置使用全局配置 **为什么不延迟创建连接?** ```typescript // ❌ 错误方案:延迟创建 constructor(path: string, baseUrl: string) { this.path = path; this.baseUrl = baseUrl; // 不创建连接,等用户调用connect() } // ✅ 正确方案:立即创建 constructor(path: string, baseUrl: string) { // 立即创建,因为原有接口期望构造后就有连接 this.ws = new WebSocket(baseUrl + path); this.setupHandlers(); } ``` ### 4. Getter/Setter 设计 #### 透明的属性代理 ```typescript // 只读属性的getter get readyState(): number { return this.ws.readyState; // 直接代理到内部WebSocket } get url(): string { return this.ws.url; } // 可写属性的getter/setter get binaryType(): BinaryType { return this.ws.binaryType; } set binaryType(value: BinaryType) { this.ws.binaryType = value; } ``` **为什么需要这些getter/setter?** 1. **API兼容性**:用户期望能够访问标准WebSocket的所有属性 2. **透明代理**:用户感觉在使用标准WebSocket,实际上是我们的增强版本 3. **状态同步**:确保外部看到的状态与内部WebSocket状态一致 #### 事件处理器的特殊getter/setter ```typescript // 事件处理器的拦截 get onmessage(): ((event: MessageEvent) => void) | null { return this.userOnMessage; // 返回用户设置的处理器 } set onmessage(handler: ((event: MessageEvent) => void) | null) { this.userOnMessage = handler; // 保存用户的处理器 // 我们的内部处理器会调用用户的处理器 } ``` **关键设计思路:** 1. **双层处理**:我们的处理器 + 用户的处理器 2. **透明性**:用户感觉直接在设置WebSocket的事件处理器 3. **控制权**:我们先处理(如过滤心跳),再传递给用户 ### 5. 事件处理架构 #### 事件流设计 ``` WebSocket原生事件 → 我们的处理器 → 过滤/处理 → 用户的处理器 ``` #### 具体实现 ```typescript private setupHandlers(): void { // 1. 设置我们的处理器 this.ws.onmessage = (event) => { const messageData = event.data; // 2. 我们先处理(心跳检测) let isHeartbeatResponse = false; if (typeof messageData === 'string' && messageData === WS_CONFIG.heartbeatResponseType) { isHeartbeatResponse = true; } if (isHeartbeatResponse) { this.clearHeartbeatTimeout(); // 清除心跳超时 return; // 不传递给用户 } // 3. 传递给用户的处理器 if (this.userOnMessage) { this.userOnMessage(event); } }; } ``` **设计优势:** 1. **消息过滤**:自动过滤心跳消息,用户无感知 2. **状态管理**:自动处理连接状态变化 3. **错误恢复**:自动处理连接错误和重连 ### 6. 定时器管理 #### 定时器生命周期管理 ```typescript class EnhancedWebSocket { private heartbeatTimer?: NodeJS.Timeout; // 心跳发送定时器 private heartbeatTimeoutTimer?: NodeJS.Timeout; // 心跳响应超时定时器 private reconnectTimer?: NodeJS.Timeout; // 重连定时器 } ``` **为什么需要三个定时器?** 1. **heartbeatTimer**:定期发送心跳包 2. **heartbeatTimeoutTimer**:检测心跳响应超时 3. **reconnectTimer**:延迟重连 #### 定时器清理策略 ```typescript // 停止心跳检测 private stopHeartbeat(): void { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = undefined; // 重置为undefined } this.clearHeartbeatTimeout(); // 同时清理超时检测 } // 清除心跳响应超时检测 private clearHeartbeatTimeout(): void { if (this.heartbeatTimeoutTimer) { clearTimeout(this.heartbeatTimeoutTimer); this.heartbeatTimeoutTimer = undefined; // 重置为undefined } } ``` **内存安全保证:** 1. **及时清理**:每次停止时都清理定时器 2. **状态重置**:清理后设置为undefined 3. **多重清理**:在多个关键点都进行清理(连接关闭、手动关闭等) ### 7. 状态标志设计 #### 关键状态标志 ```typescript private isManualClose: boolean = false; // 是否手动关闭 private isHeartbeatTimeout: boolean = false; // 是否心跳超时 private reconnectAttempts: number = 0; // 重连次数 ``` **为什么需要这些标志?** 1. **区分关闭原因**:手动关闭 vs 异常断开 vs 心跳超时 2. **重连决策**:根据不同原因决定是否重连 3. **状态跟踪**:跟踪重连进度和次数 #### 状态转换逻辑 ```typescript // 心跳超时时 private startHeartbeatTimeout(): void { this.heartbeatTimeoutTimer = setTimeout(() => { this.isHeartbeatTimeout = true; // 设置心跳超时标志 this.ws.close(1000, 'Heartbeat timeout'); }, WS_CONFIG.heartbeatTimeout); } // 连接关闭时的决策 this.ws.onclose = (event) => { // 如果不是手动关闭,或者是心跳超时导致的关闭,则重连 if (!this.isManualClose || this.isHeartbeatTimeout) { this.scheduleReconnect(); } this.isHeartbeatTimeout = false; // 重置标志 }; ``` ### 8. addEventListener/removeEventListener 实现 #### 为什么需要这些方法? ```typescript addEventListener( type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | AddEventListenerOptions ): void { this.ws.addEventListener(type, listener, options); } removeEventListener( type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | EventListenerOptions ): void { this.ws.removeEventListener(type, listener, options); } ``` **重要性:** 1. **完整的API兼容性**:某些业务代码可能使用addEventListener而不是onXXX 2. **事件管理**:支持多个监听器 3. **标准兼容**:遵循WebSocket标准API **类型安全:** - 使用泛型 `` 确保事件类型正确 - listener参数的类型根据事件类型自动推导 ### 9. 心跳检测机制 #### 心跳超时检测逻辑 ```typescript // 发送心跳时,只在没有超时检测时才设置新的 this.heartbeatTimer = setInterval(() => { if (this.ws.readyState === WebSocket.OPEN) { this.ws.send(WS_CONFIG.heartbeatMessage); if (!this.heartbeatTimeoutTimer) { // 关键:避免重复设置 this.startHeartbeatTimeout(); } } }, WS_CONFIG.heartbeatInterval); ``` **设计要点:** 1. **避免重复设置**:只有在没有超时检测时才设置新的 2. **超时逻辑**:设定时间内没收到响应就断开连接 3. **状态同步**:收到响应时清除超时检测 #### 心跳响应处理 ```typescript // 检查是否为心跳响应(支持字符串和JSON格式) let isHeartbeatResponse = false; // 1. 检查简单字符串格式 if (typeof messageData === 'string' && messageData === WS_CONFIG.heartbeatResponseType) { isHeartbeatResponse = true; } // 2. 检查JSON格式 if (!isHeartbeatResponse && typeof messageData === 'string') { try { const data = JSON.parse(messageData); if (data.type === WS_CONFIG.heartbeatResponseType) { isHeartbeatResponse = true; } } catch (e) { // JSON解析失败,不是JSON格式的心跳响应 } } ``` **兼容性设计**:支持两种心跳响应格式,适应不同的服务器实现。 ### 10. 重连机制 #### 指数退避算法 ```typescript private scheduleReconnect(): void { if (this.isManualClose || this.reconnectAttempts >= WS_CONFIG.maxReconnectAttempts) { return; } this.reconnectAttempts++; // 指数退避重连策略 const delay = Math.min( WS_CONFIG.reconnectBaseDelay * Math.pow(2, this.reconnectAttempts - 1), WS_CONFIG.maxReconnectDelay ); this.reconnectTimer = setTimeout(() => { this.reconnect(); }, delay); } ``` **算法解释:** - 第1次重连:1000ms 后 - 第2次重连:2000ms 后 - 第3次重连:4000ms 后 - 第4次重连:8000ms 后 - 第5次重连:16000ms 后(受maxReconnectDelay限制,实际为30000ms) **设计考虑:** 1. **指数退避**:避免对服务器造成压力 2. **最大延迟限制**:防止延迟过长 3. **次数限制**:避免无限重连 4. **服务器友好**:给服务器恢复时间 ### 11. 类型安全设计 #### 严格的类型定义 ```typescript // 事件处理器类型 private userOnMessage: ((event: MessageEvent) => void) | null = null; private userOnClose: ((event: CloseEvent) => void) | null = null; private userOnError: ((event: Event) => void) | null = null; private userOnOpen: ((event: Event) => void) | null = null; ``` **类型安全的好处:** 1. **编译时检查**:在编译时捕获类型错误 2. **IDE支持**:更好的自动补全和错误提示 3. **重构安全**:类型系统确保重构的正确性 #### 泛型的使用 ```typescript addEventListener( type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | AddEventListenerOptions ): void ``` **泛型的价值:** - `K extends keyof WebSocketEventMap`:确保事件类型只能是WebSocket支持的类型 - `ev: WebSocketEventMap[K]`:根据事件类型自动推导事件对象类型 ### 12. 资源管理 #### 完整的清理机制 ```typescript close(code?: number, reason?: string): void { console.log(`手动关闭WebSocket: ${this.path}`); this.isManualClose = true; this.isHeartbeatTimeout = false; // 重置心跳超时标志 this.stopHeartbeat(); // 清理心跳定时器 this.clearReconnectTimer(); // 清理重连定时器 this.ws.close(code, reason); // 关闭实际连接 } ``` **资源清理的重要性:** 1. **内存泄漏预防**:确保所有定时器都被清理 2. **状态一致性**:重置所有状态标志 3. **优雅关闭**:按正确顺序清理资源 ## 配置设计 ### 全局配置对象 ```typescript const WS_CONFIG = { heartbeatInterval: 3000, // 心跳间隔 heartbeatTimeout: 5000, // 心跳响应超时时间 maxReconnectAttempts: 5, // 最大重连次数 reconnectBaseDelay: 1000, // 重连基础延迟 maxReconnectDelay: 30000, // 最大重连延迟 heartbeatMessage: 'ping', // 心跳消息 heartbeatResponseType: 'pong', // 心跳响应类型 }; ``` **配置设计原则:** 1. **集中管理**:所有配置在一个地方,易于维护 2. **合理默认值**:开箱即用的配置 3. **易于调整**:生产环境可以快速调整参数 4. **文档化**:每个配置都有清晰的注释 ## 接口兼容性 ### 原有接口保持不变 ```typescript // 原有接口 function create(path: string): Promise { const baseUrl = import.meta.env.ENV_WEBSOCKET_BASE ?? ''; const ws = new EnhancedWebSocket(path, baseUrl) as any; return new Promise((resolve, reject) => { const timeout = setTimeout(() => { ws.close(); reject(new Error('WebSocket connection timeout')); }, 10000); ws.addEventListener('open', () => { clearTimeout(timeout); resolve(ws); // 返回增强的WebSocket,但类型为WebSocket }); ws.addEventListener('error', (e: any) => { clearTimeout(timeout); reject(e); }); }); } ``` **兼容性保证:** 1. **相同的函数签名**:`create(path: string): Promise` 2. **相同的返回类型**:返回Promise 3. **相同的使用方式**:业务代码无需任何修改 ## 总结 ### 技术选择总结 | 技术选择 | 原因 | 替代方案 | 为什么不选择替代方案 | | ------------- | -------------------------------- | ------------ | ----------------------- | | Class | 状态封装、方法绑定、生命周期管理 | 函数+闭包 | 复杂度高,类型支持差 | | 包装器模式 | 控制创建时机、事件拦截 | 继承 | 无法在事件设置前拦截 | | Private成员 | 封装、API稳定性、状态保护 | Public成员 | 容易被误用,状态不安全 | | Getter/Setter | 透明代理、API兼容性 | 直接方法 | 不符合WebSocket API习惯 | | 多定时器 | 职责分离、精确控制 | 单定时器 | 逻辑混乱,难以维护 | | 状态标志 | 精确控制重连逻辑 | 仅依赖状态码 | WebSocket状态码限制多 | ### 架构优势 1. **零侵入性**:业务代码完全无需修改 2. **高可靠性**:多重保障确保连接稳定 3. **高可维护性**:清晰的架构和完整的类型支持 4. **高性能**:最小的性能开销 5. **高扩展性**:易于添加新功能 ### 最佳实践体现 1. **单一职责原则**:每个方法只负责一个功能 2. **开闭原则**:对扩展开放,对修改封闭 3. **依赖倒置原则**:依赖抽象(接口)而非具体实现 4. **接口隔离原则**:用户只看到需要的接口 5. **里氏替换原则**:增强版本完全可以替换原版本 这个实现展示了如何在保持向后兼容的同时,提供企业级的功能增强,是一个很好的渐进式增强的例子。