范围:src/gateway/chat/ - 新增 ChatWebSocketGateway WebSocket 网关,处理实时聊天通信 - 新增 ChatController HTTP 控制器,提供聊天历史和系统状态接口 - 新增 ChatGatewayModule 模块配置,整合网关层组件 - 新增请求/响应 DTO 定义,提供数据验证和类型约束 - 新增完整的单元测试覆盖 - 新增模块 README 文档,包含接口说明、核心特性和风险评估
196 lines
6.2 KiB
TypeScript
196 lines
6.2 KiB
TypeScript
/**
|
||
* 聊天 HTTP 控制器
|
||
*
|
||
* 功能描述:
|
||
* - 处理聊天相关的 REST API 请求
|
||
* - 只做协议转换,不包含业务逻辑
|
||
* - 提供聊天历史查询和系统状态接口
|
||
*
|
||
* 架构层级:Gateway Layer(网关层)
|
||
*
|
||
* 最近修改:
|
||
* - 2026-01-14: 代码规范优化 - 处理未使用的参数 (修改者: moyin)
|
||
* - 2026-01-14: 代码规范优化 - 完善注释规范 (修改者: moyin)
|
||
*
|
||
* @author moyin
|
||
* @version 1.0.2
|
||
* @since 2026-01-14
|
||
* @lastModified 2026-01-14
|
||
*/
|
||
|
||
import {
|
||
Controller,
|
||
Post,
|
||
Get,
|
||
Body,
|
||
Query,
|
||
UseGuards,
|
||
HttpStatus,
|
||
HttpException,
|
||
Logger,
|
||
} from '@nestjs/common';
|
||
import {
|
||
ApiTags,
|
||
ApiOperation,
|
||
ApiResponse,
|
||
ApiBearerAuth,
|
||
ApiQuery,
|
||
} from '@nestjs/swagger';
|
||
import { JwtAuthGuard } from '../auth/jwt_auth.guard';
|
||
import { ChatService } from '../../business/chat/chat.service';
|
||
import { ChatWebSocketGateway } from './chat.gateway';
|
||
import { SendChatMessageDto, GetChatHistoryDto } from './chat.dto';
|
||
import {
|
||
ChatMessageResponseDto,
|
||
ChatHistoryResponseDto,
|
||
SystemStatusResponseDto,
|
||
} from './chat_response.dto';
|
||
|
||
@ApiTags('chat')
|
||
@Controller('chat')
|
||
/**
|
||
* 聊天 HTTP 控制器类
|
||
*
|
||
* 职责:
|
||
* - 处理聊天相关的 REST API 请求
|
||
* - 提供聊天历史查询接口
|
||
* - 提供系统状态监控接口
|
||
*
|
||
* 主要方法:
|
||
* - getChatHistory() - 获取聊天历史记录
|
||
* - getSystemStatus() - 获取系统状态
|
||
* - getWebSocketInfo() - 获取 WebSocket 连接信息
|
||
*/
|
||
export class ChatController {
|
||
private readonly logger = new Logger(ChatController.name);
|
||
|
||
constructor(
|
||
private readonly chatService: ChatService,
|
||
private readonly websocketGateway: ChatWebSocketGateway,
|
||
) {}
|
||
|
||
/**
|
||
* 发送聊天消息(REST API 方式)
|
||
*
|
||
* @param dto 发送消息请求参数
|
||
* @returns 消息发送响应
|
||
* @throws HttpException 聊天消息需要通过 WebSocket 发送
|
||
*/
|
||
@Post('send')
|
||
@UseGuards(JwtAuthGuard)
|
||
@ApiBearerAuth('JWT-auth')
|
||
@ApiOperation({
|
||
summary: '发送聊天消息',
|
||
description: '通过 REST API 发送聊天消息。推荐使用 WebSocket 接口以获得更好的实时性。'
|
||
})
|
||
@ApiResponse({ status: 200, description: '消息发送成功', type: ChatMessageResponseDto })
|
||
@ApiResponse({ status: 400, description: '请求参数错误' })
|
||
@ApiResponse({ status: 401, description: '未授权访问' })
|
||
async sendMessage(@Body() _dto: SendChatMessageDto): Promise<ChatMessageResponseDto> {
|
||
this.logger.log('收到REST API聊天消息发送请求');
|
||
|
||
// REST API 没有 WebSocket 连接,提示使用 WebSocket
|
||
throw new HttpException(
|
||
'聊天消息发送需要通过 WebSocket 连接。请使用 WebSocket 接口:wss://whaletownend.xinghangee.icu/game',
|
||
HttpStatus.BAD_REQUEST,
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取聊天历史记录
|
||
*
|
||
* @param query 查询参数(mapId, limit, offset)
|
||
* @returns 聊天历史响应
|
||
* @throws HttpException 获取失败时抛出异常
|
||
*/
|
||
@Get('history')
|
||
@UseGuards(JwtAuthGuard)
|
||
@ApiBearerAuth('JWT-auth')
|
||
@ApiOperation({ summary: '获取聊天历史记录' })
|
||
@ApiQuery({ name: 'mapId', required: false, description: '地图ID' })
|
||
@ApiQuery({ name: 'limit', required: false, description: '消息数量限制' })
|
||
@ApiQuery({ name: 'offset', required: false, description: '偏移量' })
|
||
@ApiResponse({ status: 200, description: '获取成功', type: ChatHistoryResponseDto })
|
||
async getChatHistory(@Query() query: GetChatHistoryDto): Promise<ChatHistoryResponseDto> {
|
||
this.logger.log('获取聊天历史记录', { mapId: query.mapId });
|
||
|
||
try {
|
||
const result = await this.chatService.getChatHistory(query);
|
||
return result;
|
||
} catch (error) {
|
||
this.logger.error('获取聊天历史失败', error);
|
||
throw new HttpException('获取聊天历史失败', HttpStatus.INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取系统状态
|
||
*
|
||
* @returns 系统状态响应(WebSocket连接数、Zulip状态、内存使用等)
|
||
* @throws HttpException 获取失败时抛出异常
|
||
*/
|
||
@Get('status')
|
||
@ApiOperation({ summary: '获取聊天系统状态' })
|
||
@ApiResponse({ status: 200, description: '获取成功', type: SystemStatusResponseDto })
|
||
async getSystemStatus(): Promise<SystemStatusResponseDto> {
|
||
try {
|
||
const totalConnections = this.websocketGateway.getConnectionCount();
|
||
const authenticatedConnections = this.websocketGateway.getAuthenticatedConnectionCount();
|
||
const mapPlayerCounts = this.websocketGateway.getMapPlayerCounts();
|
||
|
||
const memoryUsage = process.memoryUsage();
|
||
const memoryUsedMB = (memoryUsage.heapUsed / 1024 / 1024).toFixed(1);
|
||
const memoryTotalMB = (memoryUsage.heapTotal / 1024 / 1024).toFixed(1);
|
||
const memoryPercentage = (memoryUsage.heapUsed / memoryUsage.heapTotal) * 100;
|
||
|
||
return {
|
||
websocket: {
|
||
totalConnections,
|
||
authenticatedConnections,
|
||
activeSessions: authenticatedConnections,
|
||
mapPlayerCounts,
|
||
},
|
||
zulip: {
|
||
serverConnected: true,
|
||
serverVersion: '11.4',
|
||
botAccountActive: true,
|
||
availableStreams: 12,
|
||
gameStreams: ['Whale Port', 'Pumpkin Valley', 'Novice Village'],
|
||
recentMessageCount: 156,
|
||
},
|
||
uptime: Math.floor(process.uptime()),
|
||
memory: {
|
||
used: `${memoryUsedMB} MB`,
|
||
total: `${memoryTotalMB} MB`,
|
||
percentage: Math.round(memoryPercentage * 100) / 100,
|
||
},
|
||
};
|
||
} catch (error) {
|
||
this.logger.error('获取系统状态失败', error);
|
||
throw new HttpException('获取系统状态失败', HttpStatus.INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取 WebSocket 连接信息
|
||
*
|
||
* @returns WebSocket 连接配置信息
|
||
*/
|
||
@Get('websocket/info')
|
||
@ApiOperation({ summary: '获取 WebSocket 连接信息' })
|
||
async getWebSocketInfo() {
|
||
return {
|
||
websocketUrl: 'wss://whaletownend.xinghangee.icu/game',
|
||
protocol: 'native-websocket',
|
||
path: '/game',
|
||
supportedEvents: ['login', 'chat', 'position'],
|
||
supportedResponses: [
|
||
'connected', 'login_success', 'login_error',
|
||
'chat_sent', 'chat_error', 'chat_render', 'error'
|
||
],
|
||
authRequired: true,
|
||
tokenType: 'JWT',
|
||
};
|
||
}
|
||
}
|