# 聊天网关模块 (Chat Gateway Module)

聊天网关模块是聊天系统的协议入口，负责处理 WebSocket 和 HTTP 请求，提供统一的 API 接口。作为 Gateway Layer 的核心组件，它专注于协议转换和路由管理，将客户端请求转发到 Business Layer 处理，不包含业务逻辑。

## 架构层级

**Gateway Layer（网关层）**

## 职责定位

网关层负责：

1. **协议处理**：处理 WebSocket 和 HTTP 请求
2. **数据验证**：使用 DTO 进行请求参数验证
3. **路由管理**：定义 API 端点和消息路由
4. **错误转换**：将业务错误转换为协议响应

## 模块组成

```
src/gateway/chat/
├── chat.gateway.ts              # WebSocket 网关
├── chat.controller.ts           # HTTP 控制器
├── chat.gateway.module.ts       # 网关模块配置
├── chat.dto.ts                  # 请求 DTO
├── chat_response.dto.ts         # 响应 DTO
└── README.md                    # 模块文档
```

## 依赖关系

```
Gateway Layer (chat.gateway.module)
    ↓ 依赖
Business Layer (chat.module)
    ↓ 依赖
Core Layer (zulip_core.module, redis.module)
```

## 对外提供的接口

### ChatWebSocketGateway 类

#### sendToPlayer(socketId: string, data: any): void
向指定玩家的 WebSocket 连接发送消息，用于单播通信。

#### broadcastToMap(mapId: string, data: any, excludeId?: string): void
向指定地图内的所有玩家广播消息，支持排除特定玩家。

#### getConnectionCount(): number
获取当前 WebSocket 总连接数，用于监控和统计。

#### getAuthenticatedConnectionCount(): number
获取已认证的 WebSocket 连接数，用于在线玩家统计。

#### getMapPlayerCounts(): Record<string, number>
获取各地图的在线玩家数量统计，用于负载监控。

#### getMapPlayers(mapId: string): string[]
获取指定地图内的所有玩家用户名列表，用于房间成员查询。

### ChatController 类

#### getChatHistory(query: GetChatHistoryDto): Promise<ChatHistoryResponseDto>
获取聊天历史记录，支持按地图筛选和分页查询。

#### getSystemStatus(): Promise<SystemStatusResponseDto>
获取聊天系统状态，包括 WebSocket 连接数、Zulip 状态、内存使用等。

#### getWebSocketInfo(): Promise<object>
获取 WebSocket 连接配置信息，包括连接地址、支持的事件类型等。

#### sendMessage(dto: SendChatMessageDto): Promise<ChatMessageResponseDto>
通过 REST API 发送聊天消息（不推荐），该接口会返回错误提示使用 WebSocket 连接。

## WebSocket 事件接口

### 连接地址
```
wss://whaletownend.xinghangee.icu/game
```

### 'connection'
客户端建立 WebSocket 连接，服务器自动分配连接 ID。
- 输入：无（自动触发）
- 输出：`{ type: 'connected', message: '连接成功', socketId: string }`

### 'login'
用户登录认证，验证 JWT token 并建立会话。
- 输入：`{ type: 'login', token: string }`
- 输出成功：`{ t: 'login_success', sessionId: string, userId: string, username: string, currentMap: string }`
- 输出失败：`{ t: 'login_error', message: string }`

### 'logout'
用户主动登出，清理会话和房间信息。
- 输入：`{ type: 'logout' }`
- 输出：`{ t: 'logout_success', message: '登出成功' }`

### 'chat'
发送聊天消息，支持本地和全局范围。
- 输入：`{ type: 'chat', content: string, scope?: 'local' | 'global' }`
- 输出成功：`{ t: 'chat_sent', messageId: string, message: '消息发送成功' }`
- 输出失败：`{ t: 'chat_error', message: string }`

### 'position'
更新玩家位置，自动处理地图切换和位置广播。
- 输入：`{ type: 'position', x: number, y: number, mapId: string }`
- 输出：广播给地图内其他玩家 `{ t: 'position_update', userId: string, username: string, x: number, y: number, mapId: string }`

### 'chat_render'
接收其他玩家的聊天消息（服务器推送）。
- 输入：无（服务器推送）
- 输出：`{ t: 'chat_render', from: string, txt: string, scope: string, mapId: string }`

### 'disconnect'
客户端断开连接，自动清理资源和通知其他玩家。
- 输入：无（自动触发）
- 输出：无

### 'error'
通用错误消息（服务器推送）。
- 输入：无（服务器推送）
- 输出：`{ type: 'error', message: string }`

## 对外 API 接口

### POST /chat/send
通过 REST API 发送聊天消息（不推荐使用）。
- 认证：需要 JWT Bearer Token
- 请求体：`{ content: string, scope: string, mapId?: string }`
- 响应：返回 400 错误，提示使用 WebSocket 接口
- 说明：该接口仅用于提示，实际聊天消息需通过 WebSocket 发送

### GET /chat/history
获取聊天历史记录，支持按地图筛选和分页查询。
- 认证：需要 JWT Bearer Token
- 查询参数：`mapId?: string, limit?: number, offset?: number`
- 响应：聊天消息列表和总数统计

### GET /chat/status
获取聊天系统状态，包括 WebSocket 连接数、Zulip 集成状态、内存使用等。
- 认证：无需认证
- 响应：系统状态详细信息

### GET /chat/websocket/info
获取 WebSocket 连接配置信息，包括连接地址、支持的事件类型、认证方式等。
- 认证：无需认证
- 响应：WebSocket 连接配置

## 使用的项目内部依赖

### ChatService (来自 business/chat/chat.service)
聊天业务服务，处理聊天消息发送、历史查询、玩家登录登出等业务逻辑。

### JwtAuthGuard (来自 gateway/auth/jwt_auth.guard)
JWT 认证守卫，用于保护需要认证的 HTTP API 接口。

### SendChatMessageDto (本模块)
发送聊天消息的请求 DTO，提供消息内容和范围的验证规则。

### GetChatHistoryDto (本模块)
获取聊天历史的请求 DTO，提供地图筛选和分页参数的验证规则。

### ChatMessageResponseDto (本模块)
聊天消息响应 DTO，定义消息发送结果的数据结构。

### ChatHistoryResponseDto (本模块)
聊天历史响应 DTO，定义历史消息列表的数据结构。

### SystemStatusResponseDto (本模块)
系统状态响应 DTO，定义系统状态信息的数据结构。

### LoginCoreModule (来自 core/login_core/login_core.module)
登录核心模块，提供 JWT 验证和认证功能。

### ChatModule (来自 business/chat/chat.module)
聊天业务模块，提供聊天相关的业务逻辑处理。

## 核心特性

### WebSocket 连接管理
- 原生 WebSocket 支持：基于 ws 库的原生 WebSocket 实现
- 连接生命周期管理：自动处理连接建立、认证、断开和清理
- 连接状态追踪：维护连接 ID、认证状态、用户信息等
- 心跳检测机制：通过 isAlive 标记检测连接活性

### 地图房间系统
- 动态房间管理：根据玩家所在地图自动创建和销毁房间
- 房间成员追踪：维护每个地图的玩家列表
- 自动房间切换：玩家切换地图时自动加入新房间并离开旧房间
- 房间广播优化：仅向房间内的已认证玩家广播消息

### 实时消息广播
- 单播通信：向指定玩家发送消息
- 地图广播：向地图内所有玩家广播消息，支持排除发送者
- 位置同步：实时广播玩家位置更新给房间成员
- 聊天消息推送：接收业务层的聊天消息并推送给客户端

### 协议转换与路由
- 消息类型路由：根据消息类型自动路由到对应处理方法
- 协议格式统一：统一 WebSocket 和 HTTP 的响应格式
- 错误转换：将业务层错误转换为客户端友好的错误消息
- DTO 数据验证：使用 class-validator 进行请求参数验证

### 监控与统计
- 连接数统计：实时统计总连接数和已认证连接数
- 地图人数统计：统计各地图的在线玩家数量
- 系统状态监控：提供内存使用、运行时间等系统指标
- 日志记录：记录连接、消息、错误等关键事件

## 潜在风险

### WebSocket 连接管理风险
- 大量并发连接可能导致内存占用过高
- 连接泄漏风险：异常断开时可能未正确清理资源
- 僵尸连接问题：网络异常时连接可能长时间挂起
- 缓解措施：实现连接数限制、定期清理超时连接、完善错误处理

### 实时通信性能风险
- 高频位置更新可能导致服务器 CPU 压力
- 大房间广播延迟：房间人数过多时广播性能下降
- 消息队列堆积：处理速度慢于接收速度时消息堆积
- 缓解措施：位置更新限流、分片广播、消息优先级队列

### 认证与安全风险
- JWT token 泄露风险：WebSocket 连接中 token 可能被截获
- 未认证消息攻击：恶意客户端可能发送大量未认证消息
- 消息内容安全：缺少消息内容的安全过滤
- 缓解措施：使用 WSS 加密传输、限制未认证连接的消息频率、在业务层进行内容过滤

### 资源清理风险
- 断开连接时资源清理不完整可能导致内存泄漏
- 地图房间未及时清理导致空房间占用内存
- 客户端映射未清理导致无效引用
- 缓解措施：完善 cleanupClient 方法、定期清理空房间、使用 WeakMap 避免内存泄漏

### 错误处理风险
- 业务层异常未正确捕获可能导致连接中断
- 消息解析失败可能导致连接关闭
- 错误信息泄露敏感信息
- 缓解措施：完善 try-catch 覆盖、统一错误处理、脱敏错误消息

### 扩展性风险
- 单实例 WebSocket 服务器无法水平扩展
- 内存存储的房间信息无法跨实例共享
- 负载均衡时 WebSocket 连接可能断开
- 缓解措施：引入 Redis 共享房间信息、使用 Sticky Session、实现 WebSocket 集群

## 核心原则

### 1. 只做协议转换，不做业务逻辑

```typescript
// ✅ 正确：只做协议处理
private async handleChat(ws: ExtendedWebSocket, message: any) {
  if (!ws.authenticated) {
    this.sendError(ws, '请先登录');
    return;
  }

  const result = await this.chatService.sendChatMessage({
    socketId: ws.id,
    content: message.content,
    scope: message.scope || 'local'
  });

  if (result.success) {
    this.sendMessage(ws, { t: 'chat_sent', messageId: result.messageId });
  } else {
    this.sendMessage(ws, { t: 'chat_error', message: result.error });
  }
}

// ❌ 错误：在网关中包含业务逻辑
private async handleChat(ws: ExtendedWebSocket, message: any) {
  // 不应该在这里做敏感词过滤、频率限制等业务逻辑
  if (message.content.includes('敏感词')) {
    this.sendError(ws, '包含敏感词');
    return;
  }
}
```

### 2. 统一的错误处理

```typescript
private sendError(ws: ExtendedWebSocket, message: string) {
  this.sendMessage(ws, { type: 'error', message });
}
```

## 使用示例

### WebSocket 连接示例

```javascript
const ws = new WebSocket('wss://whaletownend.xinghangee.icu/game');

ws.onopen = () => {
  // 登录
  ws.send(JSON.stringify({
    type: 'login',
    token: 'your-jwt-token'
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  
  switch (data.t || data.type) {
    case 'login_success':
      console.log('登录成功', data);
      break;
    case 'chat_render':
      console.log('收到消息', data.from, data.txt);
      break;
  }
};

// 发送聊天消息
ws.send(JSON.stringify({
  type: 'chat',
  content: '大家好！',
  scope: 'local'
}));
```

## 注意事项

- 网关层不应该直接访问数据库
- 网关层不应该包含复杂的业务逻辑
- 所有业务逻辑都应该在 Business 层实现
- WebSocket 连接需要先登录才能发送聊天消息
