feat:添加WebSocket OpenAPI文档控制器
- 新增专门的WebSocket API文档控制器 - 提供详细的连接信息和配置说明 - 包含完整的消息格式文档和示例 - 添加架构信息和测试工具指南 - 支持多种编程语言的客户端示例
This commit is contained in:
817
src/business/zulip/websocket_openapi.controller.ts
Normal file
817
src/business/zulip/websocket_openapi.controller.ts
Normal file
@@ -0,0 +1,817 @@
|
|||||||
|
/**
|
||||||
|
* WebSocket OpenAPI 文档控制器
|
||||||
|
*
|
||||||
|
* 专门用于在OpenAPI/Swagger中展示WebSocket接口
|
||||||
|
* 通过REST API的方式描述WebSocket的消息格式和交互流程
|
||||||
|
*
|
||||||
|
* @author moyin
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 2026-01-09
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Controller, Get, Post, Body } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
ApiTags,
|
||||||
|
ApiOperation,
|
||||||
|
ApiResponse,
|
||||||
|
ApiProperty,
|
||||||
|
ApiExtraModels,
|
||||||
|
} from '@nestjs/swagger';
|
||||||
|
|
||||||
|
// WebSocket 消息格式 DTO
|
||||||
|
class WebSocketLoginRequest {
|
||||||
|
@ApiProperty({
|
||||||
|
description: '消息类型',
|
||||||
|
example: 'login',
|
||||||
|
enum: ['login']
|
||||||
|
})
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'JWT认证令牌',
|
||||||
|
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
|
||||||
|
})
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketLoginSuccessResponse {
|
||||||
|
@ApiProperty({
|
||||||
|
description: '响应类型',
|
||||||
|
example: 'login_success'
|
||||||
|
})
|
||||||
|
t: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '会话ID',
|
||||||
|
example: '89aff162-52d9-484e-9a35-036ba63a2280'
|
||||||
|
})
|
||||||
|
sessionId: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '用户ID',
|
||||||
|
example: 'user_123'
|
||||||
|
})
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '用户名',
|
||||||
|
example: 'Player_123'
|
||||||
|
})
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '当前地图',
|
||||||
|
example: 'whale_port'
|
||||||
|
})
|
||||||
|
currentMap: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketChatRequest {
|
||||||
|
@ApiProperty({
|
||||||
|
description: '消息类型',
|
||||||
|
example: 'chat',
|
||||||
|
enum: ['chat']
|
||||||
|
})
|
||||||
|
t: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '消息内容',
|
||||||
|
example: '大家好!我刚进入游戏',
|
||||||
|
maxLength: 1000
|
||||||
|
})
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '消息范围',
|
||||||
|
example: 'local',
|
||||||
|
enum: ['local', 'global']
|
||||||
|
})
|
||||||
|
scope: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketChatResponse {
|
||||||
|
@ApiProperty({
|
||||||
|
description: '响应类型',
|
||||||
|
example: 'chat_render'
|
||||||
|
})
|
||||||
|
t: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '发送者用户名',
|
||||||
|
example: 'Player_456'
|
||||||
|
})
|
||||||
|
from: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '消息内容',
|
||||||
|
example: '欢迎新玩家!'
|
||||||
|
})
|
||||||
|
txt: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '是否显示气泡',
|
||||||
|
example: true
|
||||||
|
})
|
||||||
|
bubble: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '消息范围',
|
||||||
|
example: 'local'
|
||||||
|
})
|
||||||
|
scope: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '地图ID(本地消息时)',
|
||||||
|
example: 'whale_port',
|
||||||
|
required: false
|
||||||
|
})
|
||||||
|
mapId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketPositionRequest {
|
||||||
|
@ApiProperty({
|
||||||
|
description: '消息类型',
|
||||||
|
example: 'position'
|
||||||
|
})
|
||||||
|
t: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'X坐标',
|
||||||
|
example: 150
|
||||||
|
})
|
||||||
|
x: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Y坐标',
|
||||||
|
example: 400
|
||||||
|
})
|
||||||
|
y: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '地图ID',
|
||||||
|
example: 'whale_port'
|
||||||
|
})
|
||||||
|
mapId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketErrorResponse {
|
||||||
|
@ApiProperty({
|
||||||
|
description: '错误类型',
|
||||||
|
example: 'error'
|
||||||
|
})
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '错误消息',
|
||||||
|
example: '请先登录'
|
||||||
|
})
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiTags('websocket')
|
||||||
|
@ApiExtraModels(
|
||||||
|
WebSocketLoginRequest,
|
||||||
|
WebSocketLoginSuccessResponse,
|
||||||
|
WebSocketChatRequest,
|
||||||
|
WebSocketChatResponse,
|
||||||
|
WebSocketPositionRequest,
|
||||||
|
WebSocketErrorResponse
|
||||||
|
)
|
||||||
|
@Controller('websocket-api')
|
||||||
|
export class WebSocketOpenApiController {
|
||||||
|
|
||||||
|
@Get('connection-info')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'WebSocket 连接信息',
|
||||||
|
description: `
|
||||||
|
获取WebSocket连接的基本信息和配置
|
||||||
|
|
||||||
|
**连接地址**: \`wss://whaletownend.xinghangee.icu/game\`
|
||||||
|
|
||||||
|
**协议**: 原生WebSocket (非Socket.IO)
|
||||||
|
|
||||||
|
**认证**: 需要JWT Token
|
||||||
|
|
||||||
|
**架构更新**:
|
||||||
|
- ✅ 已从Socket.IO迁移到原生WebSocket
|
||||||
|
- ✅ 统一使用 /game 路径
|
||||||
|
- ✅ 支持地图房间管理
|
||||||
|
- ✅ 实现消息广播机制
|
||||||
|
`
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'WebSocket连接配置信息',
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
url: {
|
||||||
|
type: 'string',
|
||||||
|
example: 'wss://whaletownend.xinghangee.icu/game',
|
||||||
|
description: 'WebSocket服务器地址'
|
||||||
|
},
|
||||||
|
protocol: {
|
||||||
|
type: 'string',
|
||||||
|
example: 'native-websocket',
|
||||||
|
description: '使用原生WebSocket协议'
|
||||||
|
},
|
||||||
|
authentication: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
required: { type: 'boolean', example: true },
|
||||||
|
method: { type: 'string', example: 'JWT Token' },
|
||||||
|
tokenFormat: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
issuer: { type: 'string', example: 'whale-town' },
|
||||||
|
audience: { type: 'string', example: 'whale-town-users' },
|
||||||
|
type: { type: 'string', example: 'access' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
supportedMaps: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string' },
|
||||||
|
example: ['whale_port', 'pumpkin_valley', 'novice_village']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
getConnectionInfo() {
|
||||||
|
return {
|
||||||
|
url: 'wss://whaletownend.xinghangee.icu/game',
|
||||||
|
protocol: 'native-websocket',
|
||||||
|
path: '/game',
|
||||||
|
port: {
|
||||||
|
development: 3001,
|
||||||
|
production: 'via_nginx_proxy'
|
||||||
|
},
|
||||||
|
authentication: {
|
||||||
|
required: true,
|
||||||
|
method: 'JWT Token',
|
||||||
|
tokenFormat: {
|
||||||
|
issuer: 'whale-town',
|
||||||
|
audience: 'whale-town-users',
|
||||||
|
type: 'access',
|
||||||
|
requiredFields: ['sub', 'username', 'email', 'role']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
supportedMaps: [
|
||||||
|
'whale_port',
|
||||||
|
'pumpkin_valley',
|
||||||
|
'novice_village'
|
||||||
|
],
|
||||||
|
features: [
|
||||||
|
'实时聊天',
|
||||||
|
'位置同步',
|
||||||
|
'地图房间管理',
|
||||||
|
'消息广播',
|
||||||
|
'连接状态监控',
|
||||||
|
'自动重连支持'
|
||||||
|
],
|
||||||
|
messageTypes: {
|
||||||
|
clientToServer: [
|
||||||
|
{
|
||||||
|
type: 'login',
|
||||||
|
description: '用户登录认证',
|
||||||
|
required: ['type', 'token']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'chat',
|
||||||
|
description: '发送聊天消息',
|
||||||
|
required: ['t', 'content', 'scope']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'position',
|
||||||
|
description: '更新位置信息',
|
||||||
|
required: ['t', 'x', 'y', 'mapId']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
serverToClient: [
|
||||||
|
{
|
||||||
|
type: 'connected',
|
||||||
|
description: '连接确认'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'login_success',
|
||||||
|
description: '登录成功'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'login_error',
|
||||||
|
description: '登录失败'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'chat_render',
|
||||||
|
description: '接收聊天消息'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'position_update',
|
||||||
|
description: '位置更新广播'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'error',
|
||||||
|
description: '通用错误消息'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
connectionLimits: {
|
||||||
|
maxConnections: 1000,
|
||||||
|
sessionTimeout: 1800, // 30分钟
|
||||||
|
heartbeatInterval: 30000 // 30秒
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('login')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '用户登录 (WebSocket消息格式)',
|
||||||
|
description: `
|
||||||
|
**注意**: 这不是真实的REST API端点,而是WebSocket消息格式的文档展示
|
||||||
|
|
||||||
|
通过WebSocket发送此格式的消息来进行用户登录认证
|
||||||
|
|
||||||
|
**WebSocket连接后发送**:
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"type": "login",
|
||||||
|
"token": "your_jwt_token_here"
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**成功响应**:
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"t": "login_success",
|
||||||
|
"sessionId": "uuid",
|
||||||
|
"userId": "user_id",
|
||||||
|
"username": "username",
|
||||||
|
"currentMap": "whale_port"
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '登录成功响应格式',
|
||||||
|
type: WebSocketLoginSuccessResponse
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 400,
|
||||||
|
description: '登录失败响应格式',
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
t: { type: 'string', example: 'login_error' },
|
||||||
|
message: { type: 'string', example: 'Token验证失败' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
websocketLogin(@Body() loginRequest: WebSocketLoginRequest) {
|
||||||
|
// 这个方法不会被实际调用,仅用于文档展示
|
||||||
|
return {
|
||||||
|
note: '这是WebSocket消息格式文档,请通过WebSocket连接发送消息',
|
||||||
|
websocketUrl: 'wss://whaletownend.xinghangee.icu/game',
|
||||||
|
messageFormat: loginRequest
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('chat')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '发送聊天消息 (WebSocket消息格式)',
|
||||||
|
description: `
|
||||||
|
**注意**: 这不是真实的REST API端点,而是WebSocket消息格式的文档展示
|
||||||
|
|
||||||
|
通过WebSocket发送聊天消息的格式
|
||||||
|
|
||||||
|
**发送消息**:
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"t": "chat",
|
||||||
|
"content": "消息内容",
|
||||||
|
"scope": "local"
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**接收消息**:
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"t": "chat_render",
|
||||||
|
"from": "发送者",
|
||||||
|
"txt": "消息内容",
|
||||||
|
"bubble": true,
|
||||||
|
"scope": "local",
|
||||||
|
"mapId": "whale_port"
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**消息范围说明**:
|
||||||
|
- \`local\`: 仅当前地图的玩家可见
|
||||||
|
- \`global\`: 所有在线玩家可见
|
||||||
|
`
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '聊天消息广播格式',
|
||||||
|
type: WebSocketChatResponse
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 400,
|
||||||
|
description: '发送失败响应',
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
t: { type: 'string', example: 'chat_error' },
|
||||||
|
message: { type: 'string', example: '消息内容不能为空' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
websocketChat(@Body() chatRequest: WebSocketChatRequest) {
|
||||||
|
return {
|
||||||
|
note: '这是WebSocket消息格式文档,请通过WebSocket连接发送消息',
|
||||||
|
websocketUrl: 'wss://whaletownend.xinghangee.icu/game',
|
||||||
|
messageFormat: chatRequest
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('position')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '位置更新 (WebSocket消息格式)',
|
||||||
|
description: `
|
||||||
|
**注意**: 这不是真实的REST API端点,而是WebSocket消息格式的文档展示
|
||||||
|
|
||||||
|
更新玩家位置信息,支持地图切换
|
||||||
|
|
||||||
|
**发送格式**:
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"t": "position",
|
||||||
|
"x": 150,
|
||||||
|
"y": 400,
|
||||||
|
"mapId": "whale_port"
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**功能说明**:
|
||||||
|
- 自动处理地图房间切换
|
||||||
|
- 向同地图其他玩家广播位置更新
|
||||||
|
- 支持实时位置同步
|
||||||
|
`
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '位置更新成功,无特定响应消息'
|
||||||
|
})
|
||||||
|
websocketPosition(@Body() positionRequest: WebSocketPositionRequest) {
|
||||||
|
return {
|
||||||
|
note: '这是WebSocket消息格式文档,请通过WebSocket连接发送消息',
|
||||||
|
websocketUrl: 'wss://whaletownend.xinghangee.icu/game',
|
||||||
|
messageFormat: positionRequest
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('message-flow')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'WebSocket 消息流程图',
|
||||||
|
description: '展示WebSocket连接和消息交互的完整流程'
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'WebSocket交互流程',
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
connectionFlow: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string' },
|
||||||
|
example: [
|
||||||
|
'1. 建立WebSocket连接到 wss://whaletownend.xinghangee.icu/game',
|
||||||
|
'2. 发送login消息进行认证',
|
||||||
|
'3. 接收login_success确认',
|
||||||
|
'4. 发送chat/position消息进行交互',
|
||||||
|
'5. 接收其他玩家的消息广播'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
getMessageFlow() {
|
||||||
|
return {
|
||||||
|
connectionFlow: [
|
||||||
|
'1. 建立WebSocket连接到 wss://whaletownend.xinghangee.icu/game',
|
||||||
|
'2. 发送login消息进行认证',
|
||||||
|
'3. 接收login_success确认',
|
||||||
|
'4. 发送chat/position消息进行交互',
|
||||||
|
'5. 接收其他玩家的消息广播'
|
||||||
|
],
|
||||||
|
messageTypes: {
|
||||||
|
clientToServer: [
|
||||||
|
'login - 用户登录认证',
|
||||||
|
'chat - 发送聊天消息',
|
||||||
|
'position - 更新位置信息'
|
||||||
|
],
|
||||||
|
serverToClient: [
|
||||||
|
'connected - 连接确认',
|
||||||
|
'login_success/login_error - 登录结果',
|
||||||
|
'chat_sent/chat_error - 消息发送结果',
|
||||||
|
'chat_render - 接收聊天消息',
|
||||||
|
'position_update - 位置更新广播',
|
||||||
|
'error - 通用错误消息'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
exampleSession: {
|
||||||
|
step1: {
|
||||||
|
action: '建立连接',
|
||||||
|
client: 'new WebSocket("wss://whaletownend.xinghangee.icu/game")',
|
||||||
|
server: '{"type":"connected","message":"连接成功","socketId":"ws_123"}'
|
||||||
|
},
|
||||||
|
step2: {
|
||||||
|
action: '用户登录',
|
||||||
|
client: '{"type":"login","token":"jwt_token"}',
|
||||||
|
server: '{"t":"login_success","sessionId":"uuid","userId":"user_123","username":"Player","currentMap":"whale_port"}'
|
||||||
|
},
|
||||||
|
step3: {
|
||||||
|
action: '发送消息',
|
||||||
|
client: '{"t":"chat","content":"Hello!","scope":"local"}',
|
||||||
|
server: '{"t":"chat_sent","messageId":137,"message":"消息发送成功"}'
|
||||||
|
},
|
||||||
|
step4: {
|
||||||
|
action: '接收广播',
|
||||||
|
server: '{"t":"chat_render","from":"Player","txt":"Hello!","bubble":true,"scope":"local","mapId":"whale_port"}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('testing-tools')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'WebSocket 测试工具推荐',
|
||||||
|
description: '推荐的WebSocket测试工具和示例代码'
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '测试工具和示例代码',
|
||||||
|
})
|
||||||
|
getTestingTools() {
|
||||||
|
return {
|
||||||
|
onlineTools: [
|
||||||
|
{
|
||||||
|
name: 'WebSocket King',
|
||||||
|
url: 'https://websocketking.com/',
|
||||||
|
description: '在线WebSocket测试工具,支持消息发送和接收'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'WebSocket Test Client',
|
||||||
|
url: 'https://www.websocket.org/echo.html',
|
||||||
|
description: '简单的WebSocket回显测试'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '内置测试页面',
|
||||||
|
url: '/websocket-test',
|
||||||
|
description: '项目内置的WebSocket测试界面,支持完整功能测试'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
codeExamples: {
|
||||||
|
javascript: `
|
||||||
|
// JavaScript WebSocket 客户端示例
|
||||||
|
const ws = new WebSocket('wss://whaletownend.xinghangee.icu/game');
|
||||||
|
|
||||||
|
ws.onopen = function() {
|
||||||
|
console.log('连接成功');
|
||||||
|
// 发送登录消息
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: 'login',
|
||||||
|
token: 'your_jwt_token_here'
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = function(event) {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
console.log('收到消息:', data);
|
||||||
|
|
||||||
|
if (data.t === 'login_success') {
|
||||||
|
// 登录成功,发送聊天消息
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
t: 'chat',
|
||||||
|
content: 'Hello from JavaScript!',
|
||||||
|
scope: 'local'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function(event) {
|
||||||
|
console.log('连接关闭:', event.code, event.reason);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = function(error) {
|
||||||
|
console.error('WebSocket错误:', error);
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
python: `
|
||||||
|
# Python WebSocket 客户端示例
|
||||||
|
import websocket
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
|
||||||
|
def on_message(ws, message):
|
||||||
|
data = json.loads(message)
|
||||||
|
print(f"收到消息: {data}")
|
||||||
|
|
||||||
|
if data.get('t') == 'login_success':
|
||||||
|
# 登录成功,发送聊天消息
|
||||||
|
ws.send(json.dumps({
|
||||||
|
't': 'chat',
|
||||||
|
'content': 'Hello from Python!',
|
||||||
|
'scope': 'local'
|
||||||
|
}))
|
||||||
|
|
||||||
|
def on_error(ws, error):
|
||||||
|
print(f"WebSocket错误: {error}")
|
||||||
|
|
||||||
|
def on_close(ws, close_status_code, close_msg):
|
||||||
|
print(f"连接关闭: {close_status_code} - {close_msg}")
|
||||||
|
|
||||||
|
def on_open(ws):
|
||||||
|
print("连接成功")
|
||||||
|
# 发送登录消息
|
||||||
|
ws.send(json.dumps({
|
||||||
|
'type': 'login',
|
||||||
|
'token': 'your_jwt_token_here'
|
||||||
|
}))
|
||||||
|
|
||||||
|
# 创建WebSocket连接
|
||||||
|
ws = websocket.WebSocketApp("wss://whaletownend.xinghangee.icu/game",
|
||||||
|
on_message=on_message,
|
||||||
|
on_error=on_error,
|
||||||
|
on_close=on_close,
|
||||||
|
on_open=on_open)
|
||||||
|
|
||||||
|
# 启动连接
|
||||||
|
ws.run_forever()
|
||||||
|
`,
|
||||||
|
nodejs: `
|
||||||
|
// Node.js WebSocket 客户端示例
|
||||||
|
const WebSocket = require('ws');
|
||||||
|
|
||||||
|
const ws = new WebSocket('wss://whaletownend.xinghangee.icu/game');
|
||||||
|
|
||||||
|
ws.on('open', function() {
|
||||||
|
console.log('连接成功');
|
||||||
|
|
||||||
|
// 发送登录消息
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: 'login',
|
||||||
|
token: 'your_jwt_token_here'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('message', function(data) {
|
||||||
|
const message = JSON.parse(data.toString());
|
||||||
|
console.log('收到消息:', message);
|
||||||
|
|
||||||
|
if (message.t === 'login_success') {
|
||||||
|
// 登录成功,发送聊天消息
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
t: 'chat',
|
||||||
|
content: 'Hello from Node.js!',
|
||||||
|
scope: 'local'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', function(code, reason) {
|
||||||
|
console.log(\`连接关闭: \${code} - \${reason}\`);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('error', function(error) {
|
||||||
|
console.error('WebSocket错误:', error);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
},
|
||||||
|
testingSteps: [
|
||||||
|
'1. 获取有效的JWT Token(通过 /auth/login 接口)',
|
||||||
|
'2. 使用WebSocket客户端连接到 wss://whaletownend.xinghangee.icu/game',
|
||||||
|
'3. 发送login消息进行认证',
|
||||||
|
'4. 验证收到login_success响应',
|
||||||
|
'5. 发送chat消息测试聊天功能',
|
||||||
|
'6. 发送position消息测试位置更新',
|
||||||
|
'7. 观察其他客户端的消息广播'
|
||||||
|
],
|
||||||
|
troubleshooting: {
|
||||||
|
connectionFailed: [
|
||||||
|
'检查网络连接是否正常',
|
||||||
|
'验证WebSocket服务器是否启动',
|
||||||
|
'确认防火墙设置允许WebSocket连接',
|
||||||
|
'检查SSL证书是否有效(WSS连接)'
|
||||||
|
],
|
||||||
|
authenticationFailed: [
|
||||||
|
'验证JWT Token是否有效且未过期',
|
||||||
|
'检查Token格式是否正确',
|
||||||
|
'确认Token包含必需的字段(sub, username, email, role)',
|
||||||
|
'验证Token的issuer和audience是否匹配'
|
||||||
|
],
|
||||||
|
messageFailed: [
|
||||||
|
'确认已完成登录认证',
|
||||||
|
'检查消息格式是否符合API规范',
|
||||||
|
'验证必需字段是否都已提供',
|
||||||
|
'检查消息内容是否符合长度限制'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('architecture')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'WebSocket 架构信息',
|
||||||
|
description: '展示WebSocket服务的技术架构和实现细节'
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'WebSocket架构信息',
|
||||||
|
})
|
||||||
|
getArchitecture() {
|
||||||
|
return {
|
||||||
|
overview: {
|
||||||
|
title: 'WebSocket 架构概览',
|
||||||
|
description: '基于原生WebSocket的实时通信架构',
|
||||||
|
version: '2.1.0',
|
||||||
|
migrationFrom: 'Socket.IO',
|
||||||
|
migrationDate: '2026-01-09'
|
||||||
|
},
|
||||||
|
technicalStack: {
|
||||||
|
server: {
|
||||||
|
framework: 'NestJS',
|
||||||
|
websocketLibrary: 'ws (原生WebSocket)',
|
||||||
|
adapter: '@nestjs/platform-ws',
|
||||||
|
port: 3001,
|
||||||
|
path: '/game'
|
||||||
|
},
|
||||||
|
proxy: {
|
||||||
|
server: 'Nginx',
|
||||||
|
sslTermination: true,
|
||||||
|
loadBalancing: 'Single Instance',
|
||||||
|
pathRouting: '/game -> localhost:3001'
|
||||||
|
},
|
||||||
|
authentication: {
|
||||||
|
method: 'JWT Bearer Token',
|
||||||
|
validation: 'Real-time on connection',
|
||||||
|
sessionManagement: 'In-memory with Redis backup'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
connectionManagement: {
|
||||||
|
maxConnections: 1000,
|
||||||
|
connectionPooling: true,
|
||||||
|
automaticReconnection: 'Client-side',
|
||||||
|
heartbeat: '30s interval'
|
||||||
|
},
|
||||||
|
messaging: {
|
||||||
|
messageTypes: ['login', 'chat', 'position'],
|
||||||
|
messageRouting: 'Room-based (by map)',
|
||||||
|
messageFiltering: 'Content and rate limiting',
|
||||||
|
messageHistory: 'Not stored (real-time only)'
|
||||||
|
},
|
||||||
|
roomManagement: {
|
||||||
|
strategy: 'Map-based rooms',
|
||||||
|
autoJoin: 'On position update',
|
||||||
|
autoLeave: 'On disconnect or map change',
|
||||||
|
broadcasting: 'Room-scoped and global'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
performance: {
|
||||||
|
latency: '< 50ms (local network)',
|
||||||
|
throughput: '1000+ messages/second',
|
||||||
|
memoryUsage: '~1MB per 100 connections',
|
||||||
|
cpuUsage: 'Low (event-driven)'
|
||||||
|
},
|
||||||
|
monitoring: {
|
||||||
|
metrics: [
|
||||||
|
'Active connections count',
|
||||||
|
'Messages per second',
|
||||||
|
'Authentication success rate',
|
||||||
|
'Error rate by type'
|
||||||
|
],
|
||||||
|
logging: [
|
||||||
|
'Connection events',
|
||||||
|
'Authentication attempts',
|
||||||
|
'Message routing',
|
||||||
|
'Error conditions'
|
||||||
|
],
|
||||||
|
healthCheck: '/chat/status endpoint'
|
||||||
|
},
|
||||||
|
security: {
|
||||||
|
authentication: 'JWT Token validation',
|
||||||
|
authorization: 'Role-based access control',
|
||||||
|
rateLimit: 'Per-user message rate limiting',
|
||||||
|
contentFilter: 'Sensitive word filtering',
|
||||||
|
inputValidation: 'Message format validation'
|
||||||
|
},
|
||||||
|
deployment: {
|
||||||
|
environment: 'Production ready',
|
||||||
|
scaling: 'Horizontal scaling supported',
|
||||||
|
backup: 'Session state in Redis',
|
||||||
|
monitoring: 'Integrated with application monitoring'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user