/** * WebSocket API 文档控制器 * * 功能描述: * - 提供 WebSocket API 的详细文档 * - 展示消息格式和事件类型 * - 提供连接示例和测试工具 * * @author angjustinl * @version 1.0.0 * @since 2025-01-07 */ import { Controller, Get } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; @ApiTags('chat') @Controller('websocket') export class WebSocketDocsController { /** * 获取 WebSocket API 文档 */ @Get('docs') @ApiOperation({ summary: 'WebSocket API 文档', description: '获取 WebSocket 连接和消息格式的详细文档' }) @ApiResponse({ status: 200, description: 'WebSocket API 文档', schema: { type: 'object', properties: { connection: { type: 'object', properties: { url: { type: 'string', example: 'ws://localhost:3000/game', description: 'WebSocket 连接地址' }, namespace: { type: 'string', example: '/game', description: 'Socket.IO 命名空间' }, transports: { type: 'array', items: { type: 'string' }, example: ['websocket', 'polling'], description: '支持的传输协议' } } }, authentication: { type: 'object', properties: { required: { type: 'boolean', example: true, description: '是否需要认证' }, method: { type: 'string', example: 'JWT Token', description: '认证方式' }, tokenFormat: { type: 'object', description: 'JWT Token 格式要求' } } }, events: { type: 'object', description: '支持的事件和消息格式' } } } }) getWebSocketDocs() { return { connection: { url: 'ws://localhost:3000/game', namespace: '/game', transports: ['websocket', 'polling'], options: { timeout: 20000, forceNew: true, reconnection: true, reconnectionAttempts: 3, reconnectionDelay: 1000 } }, authentication: { required: true, method: 'JWT Token', tokenFormat: { issuer: 'whale-town', audience: 'whale-town-users', type: 'access', requiredFields: ['sub', 'username', 'email', 'role'], example: { sub: 'user_123', username: 'player_name', email: 'user@example.com', role: 'user', type: 'access', aud: 'whale-town-users', iss: 'whale-town', iat: 1767768599, exp: 1768373399 } } }, events: { clientToServer: { login: { description: '用户登录', format: { type: 'login', token: 'JWT_TOKEN_HERE' }, example: { type: 'login', token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' }, responses: ['login_success', 'login_error'] }, chat: { description: '发送聊天消息', format: { t: 'chat', content: 'string', scope: 'local | global' }, example: { t: 'chat', content: '大家好!我刚进入游戏', scope: 'local' }, responses: ['chat_sent', 'chat_error'] }, position_update: { description: '更新玩家位置', format: { t: 'position', x: 'number', y: 'number', mapId: 'string' }, example: { t: 'position', x: 150, y: 400, mapId: 'whale_port' }, responses: [] } }, serverToClient: { login_success: { description: '登录成功响应', format: { t: 'login_success', sessionId: 'string', userId: 'string', username: 'string', currentMap: 'string' }, example: { t: 'login_success', sessionId: '89aff162-52d9-484e-9a35-036ba63a2280', userId: 'user_123', username: 'Player_123', currentMap: 'whale_port' } }, login_error: { description: '登录失败响应', format: { t: 'login_error', message: 'string' }, example: { t: 'login_error', message: 'Token验证失败' } }, chat_sent: { description: '消息发送成功确认', format: { t: 'chat_sent', messageId: 'number', message: 'string' }, example: { t: 'chat_sent', messageId: 137, message: '消息发送成功' } }, chat_error: { description: '消息发送失败', format: { t: 'chat_error', message: 'string' }, example: { t: 'chat_error', message: '消息内容不能为空' } }, chat_render: { description: '接收到聊天消息', format: { t: 'chat_render', from: 'string', txt: 'string', bubble: 'boolean' }, example: { t: 'chat_render', from: 'Player_456', txt: '欢迎新玩家!', bubble: true } } } }, maps: { whale_port: { name: 'Whale Port', displayName: '鲸鱼港', zulipStream: 'Whale Port', description: '游戏的主要港口区域' }, pumpkin_valley: { name: 'Pumpkin Valley', displayName: '南瓜谷', zulipStream: 'Pumpkin Valley', description: '充满南瓜的神秘山谷' }, novice_village: { name: 'Novice Village', displayName: '新手村', zulipStream: 'Novice Village', description: '新玩家的起始区域' } }, examples: { javascript: { connection: ` // 使用 Socket.IO 客户端连接 const io = require('socket.io-client'); const socket = io('ws://localhost:3000/game', { transports: ['websocket', 'polling'], timeout: 20000, forceNew: true, reconnection: true, reconnectionAttempts: 3, reconnectionDelay: 1000 }); // 连接成功 socket.on('connect', () => { console.log('连接成功:', socket.id); // 发送登录消息 socket.emit('login', { type: 'login', token: 'YOUR_JWT_TOKEN_HERE' }); }); // 登录成功 socket.on('login_success', (data) => { console.log('登录成功:', data); // 发送聊天消息 socket.emit('chat', { t: 'chat', content: '大家好!', scope: 'local' }); }); // 接收聊天消息 socket.on('chat_render', (data) => { console.log('收到消息:', data.from, '说:', data.txt); }); `, godot: ` # Godot WebSocket 客户端示例 extends Node var socket = WebSocketClient.new() var url = "ws://localhost:3000/game" func _ready(): socket.connect("connection_closed", self, "_closed") socket.connect("connection_error", self, "_error") socket.connect("connection_established", self, "_connected") socket.connect("data_received", self, "_on_data") var err = socket.connect_to_url(url) if err != OK: print("连接失败") func _connected(protocol): print("WebSocket 连接成功") # 发送登录消息 var login_msg = { "type": "login", "token": "YOUR_JWT_TOKEN_HERE" } socket.get_peer(1).put_packet(JSON.print(login_msg).to_utf8()) func _on_data(): var packet = socket.get_peer(1).get_packet() var message = JSON.parse(packet.get_string_from_utf8()) print("收到消息: ", message.result) ` } }, troubleshooting: { commonIssues: [ { issue: 'Token验证失败', solution: '确保JWT Token包含正确的issuer、audience和type字段' }, { issue: '连接超时', solution: '检查服务器是否运行,防火墙设置是否正确' }, { issue: '消息发送失败', solution: '确保已经成功登录,消息内容不为空' } ], testTools: [ { name: 'WebSocket King', url: 'https://websocketking.com/', description: '在线WebSocket测试工具' }, { name: 'Postman', description: 'Postman也支持WebSocket连接测试' } ] } }; } /** * 获取消息格式示例 */ @Get('message-examples') @ApiOperation({ summary: '消息格式示例', description: '获取各种 WebSocket 消息的格式示例' }) @ApiResponse({ status: 200, description: '消息格式示例', }) getMessageExamples() { return { login: { request: { type: 'login', token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0X3VzZXJfMTIzIiwidXNlcm5hbWUiOiJ0ZXN0X3VzZXIiLCJlbWFpbCI6InRlc3RfdXNlckBleGFtcGxlLmNvbSIsInJvbGUiOiJ1c2VyIiwidHlwZSI6ImFjY2VzcyIsImF1ZCI6IndoYWxlLXRvd24tdXNlcnMiLCJpc3MiOiJ3aGFsZS10b3duIiwiaWF0IjoxNzY3NzY4NTk5LCJleHAiOjE3NjgzNzMzOTl9.Mq3YccSV_pMKxIAbeNRAUws1j7doqFqvlSv4Z9DhGjI' }, successResponse: { t: 'login_success', sessionId: '89aff162-52d9-484e-9a35-036ba63a2280', userId: 'test_user_123', username: 'test_user', currentMap: 'whale_port' }, errorResponse: { t: 'login_error', message: 'Token验证失败' } }, chat: { request: { t: 'chat', content: '大家好!我刚进入游戏', scope: 'local' }, successResponse: { t: 'chat_sent', messageId: 137, message: '消息发送成功' }, errorResponse: { t: 'chat_error', message: '消息内容不能为空' }, incomingMessage: { t: 'chat_render', from: 'Player_456', txt: '欢迎新玩家!', bubble: true } }, position: { request: { t: 'position', x: 150, y: 400, mapId: 'pumpkin_valley' } } }; } }