/** * 聊天 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 { 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 { 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 { 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', }; } }