docs(zulip): 完善Zulip业务模块功能文档

范围: src/business/zulip/README.md
- 补充对外提供的接口章节(14个公共方法)
- 添加使用的项目内部依赖说明(7个依赖)
- 完善核心特性描述(5个特性)
- 添加潜在风险评估(4个风险及缓解措施)
- 优化文档结构和内容完整性
This commit is contained in:
moyin
2026-01-15 10:53:04 +08:00
parent 30a4a2813d
commit ed04b8c92d
32 changed files with 622 additions and 8886 deletions

View File

@@ -0,0 +1,839 @@
/**
* WebSocket OpenAPI 文档控制器
*
* 功能描述:
* - 专门用于在OpenAPI/Swagger中展示WebSocket接口
* - 通过REST API的方式描述WebSocket的消息格式和交互流程
* - 提供WebSocket连接信息和测试工具推荐
*
* 架构定位:
* - 层级Gateway层网关层
* - 职责HTTP协议处理、OpenAPI文档暴露
* - 依赖:无业务逻辑依赖,纯文档展示
*
* 职责分离:
* - 文档展示在Swagger中展示WebSocket消息格式
* - 连接信息提供WebSocket连接配置和认证信息
* - 消息流程展示WebSocket消息交互流程
* - 测试工具:提供测试工具推荐和示例代码
*
* 最近修改:
* - 2026-01-14: 架构优化 - 从Business层迁移到Gateway层符合四层架构规范 (修改者: moyin)
* - 2026-01-14: 代码规范优化 - 完善文件头注释和职责分离描述 (修改者: moyin)
* - 2026-01-09: 功能新增 - 初始创建WebSocket OpenAPI文档控制器 (修改者: moyin)
*
* @author moyin
* @version 2.0.0
* @since 2026-01-09
* @lastModified 2026-01-14
*/
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 路径
- ✅ 支持地图房间管理
- ✅ 实现消息广播机制
**快速测试**:
- 🧪 [WebSocket 测试页面](/websocket-test?from=api-docs) - 交互式测试工具
- 📚 [完整 API 文档](/api-docs) - 返回 Swagger 文档
`
})
@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. 访问测试页面: /websocket-test?from=api-docs',
'2. 点击"🚀 一键测试"按钮自动完成所有步骤',
'3. 或手动操作: 获取JWT Token → 连接 → 登录',
'4. 发送chat消息测试聊天功能',
'5. 发送position消息测试位置更新',
'6. 观察其他客户端的消息广播'
],
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'
}
};
}
}