# 聊天网关模块 (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 获取各地图的在线玩家数量统计,用于负载监控。 #### getMapPlayers(mapId: string): string[] 获取指定地图内的所有玩家用户名列表,用于房间成员查询。 ### ChatController 类 #### getChatHistory(query: GetChatHistoryDto): Promise 获取聊天历史记录,支持按地图筛选和分页查询。 #### getSystemStatus(): Promise 获取聊天系统状态,包括 WebSocket 连接数、Zulip 状态、内存使用等。 #### getWebSocketInfo(): Promise 获取 WebSocket 连接配置信息,包括连接地址、支持的事件类型等。 #### sendMessage(dto: SendChatMessageDto): Promise 通过 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 连接需要先登录才能发送聊天消息