/** * 清洁的WebSocket网关 * 使用原生WebSocket,不依赖NestJS的WebSocket装饰器 */ import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; import * as WebSocket from 'ws'; import { ZulipService } from './zulip.service'; import { SessionManagerService } from './services/session_manager.service'; interface ExtendedWebSocket extends WebSocket { id: string; isAlive?: boolean; authenticated?: boolean; userId?: string; username?: string; sessionId?: string; currentMap?: string; } @Injectable() export class CleanWebSocketGateway implements OnModuleInit, OnModuleDestroy { private server: WebSocket.Server; private readonly logger = new Logger(CleanWebSocketGateway.name); private clients = new Map(); private mapRooms = new Map>(); // mapId -> Set constructor( private readonly zulipService: ZulipService, private readonly sessionManager: SessionManagerService, ) {} async onModuleInit() { const port = 3001; this.server = new WebSocket.Server({ port }); this.server.on('connection', (ws: ExtendedWebSocket) => { ws.id = this.generateClientId(); ws.isAlive = true; ws.authenticated = false; this.clients.set(ws.id, ws); this.logger.log(`新的WebSocket连接: ${ws.id}`); ws.on('message', (data) => { try { const message = JSON.parse(data.toString()); this.handleMessage(ws, message); } catch (error) { this.logger.error('解析消息失败', error); this.sendError(ws, '消息格式错误'); } }); ws.on('close', () => { this.logger.log(`WebSocket连接关闭: ${ws.id}`); this.cleanupClient(ws); }); ws.on('error', (error) => { this.logger.error(`WebSocket错误: ${ws.id}`, error); }); // 发送连接确认 this.sendMessage(ws, { type: 'connected', message: '连接成功', socketId: ws.id }); }); this.logger.log(`WebSocket服务器启动成功,端口: ${port}`); } async onModuleDestroy() { if (this.server) { this.server.close(); this.logger.log('WebSocket服务器已关闭'); } } private async handleMessage(ws: ExtendedWebSocket, message: any) { this.logger.log(`收到消息: ${ws.id}`, message); const messageType = message.type || message.t; this.logger.log(`消息类型: ${messageType}`, { type: message.type, t: message.t }); switch (messageType) { case 'login': await this.handleLogin(ws, message); break; case 'chat': await this.handleChat(ws, message); break; case 'position': await this.handlePositionUpdate(ws, message); break; default: this.logger.warn(`未知消息类型: ${messageType}`, message); this.sendError(ws, `未知消息类型: ${messageType}`); } } private async handleLogin(ws: ExtendedWebSocket, message: any) { try { if (!message.token) { this.sendError(ws, 'Token不能为空'); return; } // 调用ZulipService进行登录 const result = await this.zulipService.handlePlayerLogin({ socketId: ws.id, token: message.token }); if (result.success) { ws.authenticated = true; ws.userId = result.userId; ws.username = result.username; ws.sessionId = result.sessionId; ws.currentMap = 'whale_port'; // 默认地图 // 加入默认地图房间 this.joinMapRoom(ws.id, ws.currentMap); this.sendMessage(ws, { t: 'login_success', sessionId: result.sessionId, userId: result.userId, username: result.username, currentMap: ws.currentMap }); this.logger.log(`用户登录成功: ${result.username} (${ws.id}) 进入地图: ${ws.currentMap}`); } else { this.sendMessage(ws, { t: 'login_error', message: result.error || '登录失败' }); } } catch (error) { this.logger.error('登录处理失败', error); this.sendError(ws, '登录处理失败'); } } private async handleChat(ws: ExtendedWebSocket, message: any) { try { if (!ws.authenticated) { this.sendError(ws, '请先登录'); return; } if (!message.content) { this.sendError(ws, '消息内容不能为空'); return; } // 调用ZulipService发送消息 const result = await this.zulipService.sendChatMessage({ socketId: ws.id, content: message.content, scope: message.scope || 'local' }); if (result.success) { this.sendMessage(ws, { t: 'chat_sent', messageId: result.messageId, message: '消息发送成功' }); // 广播消息给其他用户(根据scope决定范围) if (message.scope === 'global') { // 全局消息:广播给所有已认证用户 this.broadcastMessage({ t: 'chat_render', from: ws.username, txt: message.content, bubble: true, scope: 'global' }, ws.id); } else { // 本地消息:只广播给同一地图的用户 this.broadcastToMap(ws.currentMap, { t: 'chat_render', from: ws.username, txt: message.content, bubble: true, scope: 'local', mapId: ws.currentMap }, ws.id); } this.logger.log(`消息发送成功: ${ws.username} -> ${message.content}`); } else { this.sendMessage(ws, { t: 'chat_error', message: result.error || '消息发送失败' }); } } catch (error) { this.logger.error('聊天处理失败', error); this.sendError(ws, '聊天处理失败'); } } private async handlePositionUpdate(ws: ExtendedWebSocket, message: any) { try { if (!ws.authenticated) { this.sendError(ws, '请先登录'); return; } // 简单的位置更新处理,这里可以添加更多逻辑 this.logger.log(`位置更新: ${ws.username} -> (${message.x}, ${message.y}) 在 ${message.mapId}`); // 如果用户切换了地图,更新房间 if (ws.currentMap !== message.mapId) { this.leaveMapRoom(ws.id, ws.currentMap); this.joinMapRoom(ws.id, message.mapId); ws.currentMap = message.mapId; this.logger.log(`用户 ${ws.username} 切换到地图: ${message.mapId}`); } // 广播位置更新给同一地图的其他用户 this.broadcastToMap(message.mapId, { t: 'position_update', userId: ws.userId, username: ws.username, x: message.x, y: message.y, mapId: message.mapId }, ws.id); } catch (error) { this.logger.error('位置更新处理失败', error); this.sendError(ws, '位置更新处理失败'); } } private sendMessage(ws: ExtendedWebSocket, data: any) { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(data)); } } private sendError(ws: ExtendedWebSocket, message: string) { this.sendMessage(ws, { type: 'error', message: message }); } private broadcastMessage(data: any, excludeId?: string) { this.clients.forEach((client, id) => { if (id !== excludeId && client.authenticated) { this.sendMessage(client, data); } }); } private broadcastToMap(mapId: string, data: any, excludeId?: string) { const room = this.mapRooms.get(mapId); if (!room) return; room.forEach(clientId => { if (clientId !== excludeId) { const client = this.clients.get(clientId); if (client && client.authenticated) { this.sendMessage(client, data); } } }); } private joinMapRoom(clientId: string, mapId: string) { if (!this.mapRooms.has(mapId)) { this.mapRooms.set(mapId, new Set()); } this.mapRooms.get(mapId).add(clientId); this.logger.log(`客户端 ${clientId} 加入地图房间: ${mapId}`); } private leaveMapRoom(clientId: string, mapId: string) { const room = this.mapRooms.get(mapId); if (room) { room.delete(clientId); if (room.size === 0) { this.mapRooms.delete(mapId); } this.logger.log(`客户端 ${clientId} 离开地图房间: ${mapId}`); } } private cleanupClient(ws: ExtendedWebSocket) { // 从地图房间中移除 if (ws.currentMap) { this.leaveMapRoom(ws.id, ws.currentMap); } // 从客户端列表中移除 this.clients.delete(ws.id); } private generateClientId(): string { return `ws_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } // 公共方法供其他服务调用 public getConnectionCount(): number { return this.clients.size; } public getAuthenticatedConnectionCount(): number { return Array.from(this.clients.values()).filter(client => client.authenticated).length; } public getMapPlayerCounts(): Record { const counts: Record = {}; this.mapRooms.forEach((clients, mapId) => { counts[mapId] = clients.size; }); return counts; } public getMapPlayers(mapId: string): string[] { const room = this.mapRooms.get(mapId); if (!room) return []; const players: string[] = []; room.forEach(clientId => { const client = this.clients.get(clientId); if (client && client.authenticated && client.username) { players.push(client.username); } }); return players; } }