431 lines
11 KiB
TypeScript
431 lines
11 KiB
TypeScript
/**
|
||
* WebSocket API 文档控制器
|
||
*
|
||
* 功能描述:
|
||
* - 提供 WebSocket API 的详细文档
|
||
* - 展示消息格式和事件类型
|
||
* - 提供连接示例和测试工具
|
||
*
|
||
* 职责分离:
|
||
* - API文档:提供完整的WebSocket API使用说明
|
||
* - 示例代码:提供各种编程语言的连接示例
|
||
* - 调试支持:提供消息格式验证和测试工具
|
||
* - 开发指导:提供最佳实践和故障排除指南
|
||
*
|
||
* 最近修改:
|
||
* - 2026-01-07: 代码规范优化 - 完善文件头注释和修改记录 (修改者: moyin)
|
||
*
|
||
* @author angjustinl
|
||
* @version 1.0.1
|
||
* @since 2025-01-07
|
||
* @lastModified 2026-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'
|
||
}
|
||
}
|
||
};
|
||
}
|
||
} |