# WebSocket增强服务技术设计文档

## 概述

本文档详细解释了 `src/services/ws.ts` 的技术设计思路、架构选择和实现细节。这个文件实现了一个增强的WebSocket服务，在保持原有接口不变的前提下，添加了心跳检测、自动重连、错误处理等企业级功能。

## 设计目标

### 主要目标

1. **零侵入性**：业务代码无需修改，完全透明的功能增强
2. **企业级稳定性**：心跳检测、自动重连、错误恢复
3. **可配置性**：全局配置，易于调整和优化
4. **类型安全**：完整的TypeScript类型支持
5. **内存安全**：正确的资源管理，防止内存泄漏

### 兼容性目标

- 保持原有 `create(path): Promise<WebSocket>` 接口不变
- 返回标准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<K extends keyof WebSocketEventMap>(
  type: K,
  listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,
  options?: boolean | AddEventListenerOptions
): void {
  this.ws.addEventListener(type, listener, options);
}

removeEventListener<K extends keyof WebSocketEventMap>(
  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

**类型安全：**

- 使用泛型 `<K extends keyof WebSocketEventMap>` 确保事件类型正确
- 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<K extends keyof WebSocketEventMap>(
  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<WebSocket> {
  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<WebSocket>`
2. **相同的返回类型**：返回Promise<WebSocket>
3. **相同的使用方式**：业务代码无需任何修改

## 总结

### 技术选择总结

| 技术选择      | 原因                             | 替代方案     | 为什么不选择替代方案    |
| ------------- | -------------------------------- | ------------ | ----------------------- |
| Class         | 状态封装、方法绑定、生命周期管理 | 函数+闭包    | 复杂度高，类型支持差    |
| 包装器模式    | 控制创建时机、事件拦截           | 继承         | 无法在事件设置前拦截    |
| Private成员   | 封装、API稳定性、状态保护        | Public成员   | 容易被误用，状态不安全  |
| Getter/Setter | 透明代理、API兼容性              | 直接方法     | 不符合WebSocket API习惯 |
| 多定时器      | 职责分离、精确控制               | 单定时器     | 逻辑混乱，难以维护      |
| 状态标志      | 精确控制重连逻辑                 | 仅依赖状态码 | WebSocket状态码限制多   |

### 架构优势

1. **零侵入性**：业务代码完全无需修改
2. **高可靠性**：多重保障确保连接稳定
3. **高可维护性**：清晰的架构和完整的类型支持
4. **高性能**：最小的性能开销
5. **高扩展性**：易于添加新功能

### 最佳实践体现

1. **单一职责原则**：每个方法只负责一个功能
2. **开闭原则**：对扩展开放，对修改封闭
3. **依赖倒置原则**：依赖抽象（接口）而非具体实现
4. **接口隔离原则**：用户只看到需要的接口
5. **里氏替换原则**：增强版本完全可以替换原版本

这个实现展示了如何在保持向后兼容的同时，提供企业级的功能增强，是一个很好的渐进式增强的例子。
