refactor:项目架构重构和命名规范化

- 统一文件命名为snake_case格式(kebab-case  snake_case)
- 重构zulip模块为zulip_core,明确Core层职责
- 重构user-mgmt模块为user_mgmt,统一命名规范
- 调整模块依赖关系,优化架构分层
- 删除过时的文件和目录结构
- 更新相关文档和配置文件

本次重构涉及大量文件重命名和模块重组,
旨在建立更清晰的项目架构和统一的命名规范。
This commit is contained in:
moyin
2026-01-08 00:14:14 +08:00
parent 4fa4bd1a70
commit bb796a2469
178 changed files with 24767 additions and 3484 deletions

View File

@@ -1,172 +1,276 @@
# Zulip集成业务模块
# Zulip 游戏集成业务模块
## 架构重构说明
Zulip 是游戏与Zulip社群平台的集成业务模块提供完整的实时聊天、会话管理、消息过滤和WebSocket通信功能实现游戏内聊天与Zulip社群的双向同步支持基于位置的聊天上下文管理和业务规则驱动的消息过滤控制。
本模块已按照项目的分层架构要求进行重构,将技术实现细节移动到核心服务层,业务逻辑保留在业务层。
## 玩家登录和会话管理
### 重构前后对比
### handlePlayerLogin()
验证游戏Token创建Zulip客户端建立会话映射关系支持JWT认证和API Key获取。
#### 重构前(❌ 违反架构原则)
```
src/business/zulip/services/
├── zulip_client.service.ts # 技术实现API调用
├── zulip_client_pool.service.ts # 技术实现:连接池管理
├── config_manager.service.ts # 技术实现:配置管理
├── zulip_event_processor.service.ts # 技术实现:事件处理
├── session_manager.service.ts # ✅ 业务逻辑:会话管理
└── message_filter.service.ts # ✅ 业务逻辑:消息过滤
```
### handlePlayerLogout()
清理玩家会话注销Zulip事件队列释放相关资源确保连接正常断开。
#### 重构后(✅ 符合架构原则)
```
# 业务逻辑层
src/business/zulip/
├── zulip.service.ts # 业务协调服务
├── zulip_websocket.gateway.ts # WebSocket业务网关
└── services/
├── session_manager.service.ts # 会话业务逻辑
└── message_filter.service.ts # 消息过滤业务规则
### getSession()
根据socketId获取会话信息并更新最后活动时间支持会话状态查询。
# 核心服务层
src/core/zulip/
├── interfaces/
│ └── zulip-core.interfaces.ts # 核心服务接口定义
├── services/
│ ├── zulip_client.service.ts # Zulip API封装
│ ├── zulip_client_pool.service.ts # 客户端池管理
│ ├── config_manager.service.ts # 配置管理
│ ├── zulip_event_processor.service.ts # 事件处理
│ └── ... # 其他技术服务
└── zulip-core.module.ts # 核心服务模块
```
### getSocketsInMap()
获取指定地图中所有在线玩家的Socket ID列表用于消息分发和空间过滤。
### 架构优势
## 消息发送和处理
#### 1. 单一职责原则
- **业务层**:只关注游戏相关的业务逻辑和规则
- **核心层**只处理技术实现和第三方API调用
### sendChatMessage()
处理游戏客户端发送的聊天消息转发到对应的Zulip Stream/Topic包含内容过滤和权限验证。
#### 2. 依赖注入和接口抽象
### processZulipMessage()
处理Zulip事件队列推送的消息转换格式后发送给相关的游戏客户端实现双向通信。
### updatePlayerPosition()
更新玩家在游戏世界中的位置信息,用于消息路由和上下文注入,支持地图切换。
## WebSocket网关功能
### handleConnection()
处理游戏客户端WebSocket连接建立记录连接信息并初始化连接状态。
### handleDisconnect()
处理游戏客户端连接断开,清理相关资源并执行登出逻辑。
### handleLogin()
处理登录消息验证Token并建立会话返回登录结果和用户信息。
### handleChat()
处理聊天消息,验证用户认证状态和消息格式,调用业务服务发送消息。
### sendChatRender()
向指定客户端发送聊天渲染消息,用于显示气泡或聊天框。
### broadcastToMap()
向指定地图的所有客户端广播消息,支持区域性消息分发。
## 会话管理功能
### createSession()
创建会话并绑定Socket_ID与Zulip_Queue_ID建立WebSocket连接与Zulip队列的映射关系。
### injectContext()
上下文注入根据玩家位置确定消息应该发送到的Zulip Stream和Topic。
### destroySession()
清理玩家会话数据,从地图玩家列表中移除,释放相关资源。
### cleanupExpiredSessions()
定时清理超时的会话数据和相关资源返回需要注销的Zulip队列ID列表。
## 消息过滤和安全
### validateMessage()
对消息进行综合验证,包括内容过滤、频率限制和权限验证。
### filterContent()
检查消息内容是否包含敏感词,进行内容过滤和替换。
### checkRateLimit()
检查用户是否超过消息发送频率限制,防止刷屏。
### validatePermission()
验证用户是否有权限向目标Stream发送消息防止位置欺诈。
### logViolation()
记录用户的违规行为,用于监控和分析。
## REST API接口
### sendMessage()
通过REST API发送聊天消息到Zulip推荐使用WebSocket接口
### getChatHistory()
获取指定地图或全局的聊天历史记录,支持分页查询。
### getSystemStatus()
获取WebSocket连接状态、Zulip集成状态等系统信息。
### getWebSocketInfo()
获取WebSocket连接的详细信息包括连接地址、协议等。
## 使用的项目内部依赖
### ZulipCoreModule (来自 core/zulip_core)
提供Zulip核心技术服务包括客户端池管理、配置管理和事件处理等底层技术实现。
### LoginCoreModule (来自 core/login_core)
提供用户认证和Token验证服务支持JWT令牌验证和用户信息获取。
### RedisModule (来自 core/redis)
提供会话状态缓存和数据存储服务,支持会话持久化和快速查询。
### LoggerModule (来自 core/utils/logger)
提供统一的日志记录服务,支持结构化日志和性能监控。
### ZulipAccountsModule (来自 core/db/zulip_accounts)
提供Zulip账号关联管理功能支持用户与Zulip账号的绑定关系。
### AuthModule (来自 business/auth)
提供JWT验证和用户认证服务支持用户身份验证和权限控制。
### IZulipClientPoolService (来自 core/zulip_core/interfaces)
Zulip客户端池服务接口用于管理用户专用的Zulip客户端实例。
### IZulipConfigService (来自 core/zulip_core/interfaces)
Zulip配置服务接口用于获取地图到Stream的映射关系和配置信息。
### ApiKeySecurityService (来自 core/zulip_core/services)
API密钥安全服务用于获取和管理用户的Zulip API Key。
### IRedisService (来自 core/redis)
Redis服务接口用于会话数据存储、频率限制和违规记录管理。
### SendChatMessageDto (本模块)
发送聊天消息的数据传输对象定义消息内容、范围和地图ID等字段。
### ChatMessageResponseDto (本模块)
聊天消息响应的数据传输对象包含成功状态、消息ID和错误信息。
### SystemStatusResponseDto (本模块)
系统状态响应的数据传输对象包含WebSocket状态、Zulip集成状态和系统信息。
## 核心特性
### 双向通信支持
- WebSocket实时通信支持游戏客户端与服务器的实时双向通信
- Zulip集成同步实现游戏内聊天与Zulip社群的双向消息同步
- 事件驱动架构基于事件队列处理Zulip消息推送和游戏事件
### 会话状态管理
- Redis持久化存储会话数据存储在Redis中支持服务重启后状态恢复
- 自动过期清理:定时清理超时会话,释放系统资源
- 多地图支持:支持玩家在不同地图间切换,自动更新地图玩家列表
### 消息过滤和安全
- 敏感词过滤支持block和replace两种级别的敏感词处理
- 频率限制控制:防止用户发送消息过于频繁导致刷屏
- 位置权限验证防止用户向不匹配位置的Stream发送消息
- 违规行为记录:记录和统计用户违规行为,支持监控和分析
### 业务规则引擎
- 上下文注入机制根据玩家位置自动确定消息的目标Stream和Topic
- 动态配置管理支持地图到Stream映射关系的动态配置和热重载
- 权限分级控制:支持不同用户角色的权限控制和消息发送限制
## 潜在风险
### 会话数据丢失
- Redis服务故障可能导致会话数据丢失影响用户体验
- 建议配置Redis主从复制和持久化策略
- 实现会话数据的定期备份和恢复机制
### 消息同步延迟
- Zulip服务器网络延迟可能影响消息同步实时性
- 大量并发消息可能导致事件队列处理延迟
- 建议监控消息处理延迟并设置合理的超时机制
### 频率限制绕过
- 恶意用户可能通过多个账号绕过频率限制
- IP级别的频率限制可能影响正常用户
- 建议结合用户行为分析和动态调整限制策略
### 敏感词过滤失效
- 新型敏感词和变体可能绕过现有过滤规则
- 过度严格的过滤可能影响正常交流
- 建议定期更新敏感词库并优化过滤算法
### WebSocket连接稳定性
- 网络不稳定可能导致WebSocket连接频繁断开重连
- 大量连接可能消耗过多服务器资源
- 建议实现连接池管理和自动重连机制
### 位置验证绕过
- 客户端修改可能绕过位置验证机制
- 服务端位置验证逻辑需要持续完善
- 建议结合多种验证手段和异常行为检测
## 使用示例
### WebSocket 客户端连接
```typescript
// 业务层通过接口依赖核心服务
constructor(
@Inject('ZULIP_CLIENT_POOL_SERVICE')
private readonly zulipClientPool: IZulipClientPoolService,
@Inject('ZULIP_CONFIG_SERVICE')
private readonly configManager: IZulipConfigService,
) {}
// 建立WebSocket连接
const socket = io('ws://localhost:3000/zulip');
// 监听连接事件
socket.on('connect', () => {
console.log('Connected to Zulip WebSocket');
});
// 发送登录消息
socket.emit('login', {
token: 'your-jwt-token'
});
// 发送聊天消息
socket.emit('chat', {
content: '大家好!',
scope: 'local',
mapId: 'whale_port'
});
// 监听聊天消息
socket.on('chat_render', (data) => {
console.log('收到消息:', data);
});
```
#### 3. 易于测试和维护
- 业务逻辑可以独立测试,不依赖具体的技术实现
- 核心服务可以独立替换,不影响业务逻辑
- 接口定义清晰,便于理解和维护
### REST API 调用
```typescript
// 发送聊天消息
const response = await fetch('/api/zulip/send-message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-jwt-token'
},
body: JSON.stringify({
content: '测试消息',
scope: 'global',
mapId: 'whale_port'
})
});
### 服务职责划分
// 获取聊天历史
const history = await fetch('/api/zulip/chat-history?mapId=whale_port&limit=50');
const messages = await history.json();
#### 业务逻辑层服务
// 获取系统状态
const status = await fetch('/api/zulip/system-status');
const systemInfo = await status.json();
```
| 服务 | 职责 | 业务价值 |
|------|------|----------|
| `ZulipService` | 游戏登录/登出业务流程协调 | 处理玩家生命周期管理 |
| `SessionManagerService` | 游戏会话状态和上下文管理 | 维护玩家位置和聊天上下文 |
| `MessageFilterService` | 消息过滤和业务规则控制 | 实现内容审核和权限验证 |
| `ZulipWebSocketGateway` | WebSocket业务协议处理 | 游戏协议转换和路由 |
#### 核心服务层服务
| 服务 | 职责 | 技术价值 |
|------|------|----------|
| `ZulipClientService` | Zulip REST API封装 | 第三方API调用抽象 |
| `ZulipClientPoolService` | 客户端连接池管理 | 资源管理和性能优化 |
| `ConfigManagerService` | 配置文件管理和热重载 | 系统配置和运维支持 |
| `ZulipEventProcessorService` | 事件队列处理和消息转换 | 异步消息处理机制 |
### 使用示例
#### 业务层调用核心服务
### 服务集成示例
```typescript
@Injectable()
export class ZulipService {
export class GameChatService {
constructor(
@Inject('ZULIP_CLIENT_POOL_SERVICE')
private readonly zulipClientPool: IZulipClientPoolService,
private readonly zulipService: ZulipService,
private readonly sessionManager: SessionManagerService
) {}
async sendChatMessage(request: ChatMessageRequest): Promise<ChatMessageResponse> {
// 业务逻辑:验证和处理
const session = await this.sessionManager.getSession(request.socketId);
const context = await this.sessionManager.injectContext(request.socketId);
async handlePlayerMessage(playerId: string, message: string) {
// 获取玩家会话
const session = await this.sessionManager.getSession(playerId);
// 调用核心服务:技术实现
const result = await this.zulipClientPool.sendMessage(
session.userId,
context.stream,
context.topic,
request.content,
);
// 发送消息到Zulip
const result = await this.zulipService.sendChatMessage({
gameUserId: playerId,
content: message,
scope: 'local',
mapId: session.mapId
});
return { success: result.success, messageId: result.messageId };
return result;
}
}
```
### 迁移指南
如果你的代码中直接导入了已移动的服务,请按以下方式更新:
#### 更新导入路径
```typescript
// ❌ 旧的导入方式
import { ZulipClientPoolService } from './services/zulip_client_pool.service';
// ✅ 新的导入方式(通过依赖注入)
import { IZulipClientPoolService } from '../../core/zulip/interfaces/zulip-core.interfaces';
constructor(
@Inject('ZULIP_CLIENT_POOL_SERVICE')
private readonly zulipClientPool: IZulipClientPoolService,
) {}
```
#### 更新模块导入
```typescript
// ✅ 业务模块自动导入核心模块
@Module({
imports: [
ZulipCoreModule, // 自动提供所有核心服务
// ...
],
})
export class ZulipModule {}
```
### 测试策略
#### 业务逻辑测试
```typescript
// 使用Mock核心服务测试业务逻辑
const mockZulipClientPool: IZulipClientPoolService = {
sendMessage: jest.fn().mockResolvedValue({ success: true }),
// ...
};
const module = await Test.createTestingModule({
providers: [
ZulipService,
{ provide: 'ZULIP_CLIENT_POOL_SERVICE', useValue: mockZulipClientPool },
],
}).compile();
```
#### 核心服务测试
```typescript
// 独立测试技术实现
describe('ZulipClientService', () => {
it('should call Zulip API correctly', async () => {
// 测试API调用逻辑
});
});
```
这种架构设计确保了业务逻辑与技术实现的清晰分离,提高了代码的可维护性和可测试性。
## 版本信息
- **版本**: 1.2.1
- **作者**: angjustinl
- **创建时间**: 2025-12-20
- **最后修改**: 2026-01-07

View File

@@ -7,9 +7,19 @@
* - 查看系统状态和统计信息
* - 管理 WebSocket 连接状态
*
* 职责分离:
* - REST接口提供HTTP方式的聊天功能访问
* - 状态查询:提供系统运行状态和统计信息
* - 文档支持提供WebSocket API的使用文档
* - 监控支持:提供连接数和性能监控接口
*
* 最近修改:
* - 2026-01-07: 代码规范优化 - 完善文件头注释和修改记录 (修改者: moyin)
*
* @author angjustinl
* @version 1.0.0
* @version 1.0.1
* @since 2025-01-07
* @lastModified 2026-01-07
*/
import {
@@ -30,7 +40,7 @@ import {
ApiBearerAuth,
ApiQuery,
} from '@nestjs/swagger';
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
import { JwtAuthGuard } from '../../auth/jwt_auth.guard';
import { ZulipService } from '../zulip.service';
import { ZulipWebSocketGateway } from '../zulip_websocket.gateway';
import {
@@ -39,7 +49,7 @@ import {
GetChatHistoryDto,
ChatHistoryResponseDto,
SystemStatusResponseDto,
} from '../dto/chat.dto';
} from '../chat.dto';
@ApiTags('chat')
@Controller('chat')

View File

@@ -6,9 +6,19 @@
* -
* -
*
*
* - API文档WebSocket API使用说明
* -
* -
* -
*
*
* - 2026-01-07: 代码规范优化 - (修改者: moyin)
*
* @author angjustinl
* @version 1.0.0
* @version 1.0.1
* @since 2025-01-07
* @lastModified 2026-01-07
*/
import { Controller, Get } from '@nestjs/common';

View File

@@ -0,0 +1,581 @@
/**
* Zulip账号关联管理控制器
*
* 功能描述:
* - 提供Zulip账号关联管理的REST API接口
* - 支持CRUD操作和批量管理
* - 提供账号验证和统计功能
*
* @author angjustinl
* @version 1.0.0
* @since 2025-01-07
*/
import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
Query,
UseGuards,
HttpStatus,
HttpCode,
Inject,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBearerAuth,
ApiParam,
ApiQuery,
} from '@nestjs/swagger';
import { JwtAuthGuard } from '../../auth/jwt_auth.guard';
import { ZulipAccountsService } from '../../../core/db/zulip_accounts/zulip_accounts.service';
import { ZulipAccountsMemoryService } from '../../../core/db/zulip_accounts/zulip_accounts_memory.service';
import {
CreateZulipAccountDto,
UpdateZulipAccountDto,
QueryZulipAccountDto,
ZulipAccountResponseDto,
ZulipAccountListResponseDto,
ZulipAccountStatsResponseDto,
BatchUpdateStatusDto,
BatchUpdateResponseDto,
VerifyAccountDto,
VerifyAccountResponseDto,
} from '../../../core/db/zulip_accounts/zulip_accounts.dto';
@ApiTags('zulip-accounts')
@Controller('zulip-accounts')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT-auth')
export class ZulipAccountsController {
constructor(
@Inject('ZulipAccountsService')
private readonly zulipAccountsService: ZulipAccountsService | ZulipAccountsMemoryService,
) {}
/**
* 创建Zulip账号关联
*/
@Post()
@ApiOperation({
summary: '创建Zulip账号关联',
description: '为游戏用户创建与Zulip账号的关联关系'
})
@ApiResponse({
status: 201,
description: '创建成功',
type: ZulipAccountResponseDto,
})
@ApiResponse({
status: 400,
description: '请求参数错误',
})
@ApiResponse({
status: 409,
description: '关联已存在',
})
@HttpCode(HttpStatus.CREATED)
async create(@Body() createDto: CreateZulipAccountDto): Promise<ZulipAccountResponseDto> {
return this.zulipAccountsService.create(createDto);
}
/**
* 获取所有Zulip账号关联
*/
@Get()
@ApiOperation({
summary: '查询Zulip账号关联列表',
description: '根据条件查询Zulip账号关联列表'
})
@ApiQuery({
name: 'gameUserId',
required: false,
description: '游戏用户ID',
example: '12345'
})
@ApiQuery({
name: 'zulipUserId',
required: false,
description: 'Zulip用户ID',
example: 67890
})
@ApiQuery({
name: 'zulipEmail',
required: false,
description: 'Zulip邮箱地址',
example: 'user@example.com'
})
@ApiQuery({
name: 'status',
required: false,
description: '账号状态',
enum: ['active', 'inactive', 'suspended', 'error']
})
@ApiQuery({
name: 'includeGameUser',
required: false,
description: '是否包含游戏用户信息',
type: Boolean,
example: false
})
@ApiResponse({
status: 200,
description: '查询成功',
type: ZulipAccountListResponseDto,
})
async findMany(@Query() queryDto: QueryZulipAccountDto): Promise<ZulipAccountListResponseDto> {
return this.zulipAccountsService.findMany(queryDto);
}
/**
* 根据ID获取Zulip账号关联
*/
@Get(':id')
@ApiOperation({
summary: '根据ID获取Zulip账号关联',
description: '根据关联记录ID获取详细信息'
})
@ApiParam({
name: 'id',
description: '关联记录ID',
example: '1'
})
@ApiQuery({
name: 'includeGameUser',
required: false,
description: '是否包含游戏用户信息',
type: Boolean,
example: false
})
@ApiResponse({
status: 200,
description: '获取成功',
type: ZulipAccountResponseDto,
})
@ApiResponse({
status: 404,
description: '记录不存在',
})
async findById(
@Param('id') id: string,
@Query('includeGameUser') includeGameUser?: boolean,
): Promise<ZulipAccountResponseDto> {
return this.zulipAccountsService.findById(id, includeGameUser);
}
/**
* 根据游戏用户ID获取Zulip账号关联
*/
@Get('game-user/:gameUserId')
@ApiOperation({
summary: '根据游戏用户ID获取Zulip账号关联',
description: '根据游戏用户ID获取关联的Zulip账号信息'
})
@ApiParam({
name: 'gameUserId',
description: '游戏用户ID',
example: '12345'
})
@ApiQuery({
name: 'includeGameUser',
required: false,
description: '是否包含游戏用户信息',
type: Boolean,
example: false
})
@ApiResponse({
status: 200,
description: '获取成功',
type: ZulipAccountResponseDto,
})
@ApiResponse({
status: 404,
description: '关联不存在',
})
async findByGameUserId(
@Param('gameUserId') gameUserId: string,
@Query('includeGameUser') includeGameUser?: boolean,
): Promise<ZulipAccountResponseDto | null> {
return this.zulipAccountsService.findByGameUserId(gameUserId, includeGameUser);
}
/**
* 根据Zulip用户ID获取账号关联
*/
@Get('zulip-user/:zulipUserId')
@ApiOperation({
summary: '根据Zulip用户ID获取账号关联',
description: '根据Zulip用户ID获取关联的游戏账号信息'
})
@ApiParam({
name: 'zulipUserId',
description: 'Zulip用户ID',
example: '67890'
})
@ApiQuery({
name: 'includeGameUser',
required: false,
description: '是否包含游戏用户信息',
type: Boolean,
example: false
})
@ApiResponse({
status: 200,
description: '获取成功',
type: ZulipAccountResponseDto,
})
@ApiResponse({
status: 404,
description: '关联不存在',
})
async findByZulipUserId(
@Param('zulipUserId') zulipUserId: string,
@Query('includeGameUser') includeGameUser?: boolean,
): Promise<ZulipAccountResponseDto | null> {
return this.zulipAccountsService.findByZulipUserId(parseInt(zulipUserId), includeGameUser);
}
/**
* 根据Zulip邮箱获取账号关联
*/
@Get('zulip-email/:zulipEmail')
@ApiOperation({
summary: '根据Zulip邮箱获取账号关联',
description: '根据Zulip邮箱地址获取关联的游戏账号信息'
})
@ApiParam({
name: 'zulipEmail',
description: 'Zulip邮箱地址',
example: 'user@example.com'
})
@ApiQuery({
name: 'includeGameUser',
required: false,
description: '是否包含游戏用户信息',
type: Boolean,
example: false
})
@ApiResponse({
status: 200,
description: '获取成功',
type: ZulipAccountResponseDto,
})
@ApiResponse({
status: 404,
description: '关联不存在',
})
async findByZulipEmail(
@Param('zulipEmail') zulipEmail: string,
@Query('includeGameUser') includeGameUser?: boolean,
): Promise<ZulipAccountResponseDto | null> {
return this.zulipAccountsService.findByZulipEmail(zulipEmail, includeGameUser);
}
/**
* 更新Zulip账号关联
*/
@Put(':id')
@ApiOperation({
summary: '更新Zulip账号关联',
description: '根据ID更新Zulip账号关联信息'
})
@ApiParam({
name: 'id',
description: '关联记录ID',
example: '1'
})
@ApiResponse({
status: 200,
description: '更新成功',
type: ZulipAccountResponseDto,
})
@ApiResponse({
status: 404,
description: '记录不存在',
})
async update(
@Param('id') id: string,
@Body() updateDto: UpdateZulipAccountDto,
): Promise<ZulipAccountResponseDto> {
return this.zulipAccountsService.update(id, updateDto);
}
/**
* 根据游戏用户ID更新关联
*/
@Put('game-user/:gameUserId')
@ApiOperation({
summary: '根据游戏用户ID更新关联',
description: '根据游戏用户ID更新Zulip账号关联信息'
})
@ApiParam({
name: 'gameUserId',
description: '游戏用户ID',
example: '12345'
})
@ApiResponse({
status: 200,
description: '更新成功',
type: ZulipAccountResponseDto,
})
@ApiResponse({
status: 404,
description: '关联不存在',
})
async updateByGameUserId(
@Param('gameUserId') gameUserId: string,
@Body() updateDto: UpdateZulipAccountDto,
): Promise<ZulipAccountResponseDto> {
return this.zulipAccountsService.updateByGameUserId(gameUserId, updateDto);
}
/**
* 删除Zulip账号关联
*/
@Delete(':id')
@ApiOperation({
summary: '删除Zulip账号关联',
description: '根据ID删除Zulip账号关联记录'
})
@ApiParam({
name: 'id',
description: '关联记录ID',
example: '1'
})
@ApiResponse({
status: 200,
description: '删除成功',
schema: {
type: 'object',
properties: {
success: { type: 'boolean', example: true },
message: { type: 'string', example: '删除成功' }
}
}
})
@ApiResponse({
status: 404,
description: '记录不存在',
})
async delete(@Param('id') id: string): Promise<{ success: boolean; message: string }> {
await this.zulipAccountsService.delete(id);
return { success: true, message: '删除成功' };
}
/**
* 根据游戏用户ID删除关联
*/
@Delete('game-user/:gameUserId')
@ApiOperation({
summary: '根据游戏用户ID删除关联',
description: '根据游戏用户ID删除Zulip账号关联记录'
})
@ApiParam({
name: 'gameUserId',
description: '游戏用户ID',
example: '12345'
})
@ApiResponse({
status: 200,
description: '删除成功',
schema: {
type: 'object',
properties: {
success: { type: 'boolean', example: true },
message: { type: 'string', example: '删除成功' }
}
}
})
@ApiResponse({
status: 404,
description: '关联不存在',
})
async deleteByGameUserId(@Param('gameUserId') gameUserId: string): Promise<{ success: boolean; message: string }> {
await this.zulipAccountsService.deleteByGameUserId(gameUserId);
return { success: true, message: '删除成功' };
}
/**
* 获取需要验证的账号列表
*/
@Get('management/verification-needed')
@ApiOperation({
summary: '获取需要验证的账号列表',
description: '获取超过指定时间未验证的账号列表'
})
@ApiQuery({
name: 'maxAge',
required: false,
description: '最大验证间隔毫秒默认24小时',
example: 86400000
})
@ApiResponse({
status: 200,
description: '获取成功',
type: ZulipAccountListResponseDto,
})
async findAccountsNeedingVerification(
@Query('maxAge') maxAge?: number,
): Promise<ZulipAccountListResponseDto> {
return this.zulipAccountsService.findAccountsNeedingVerification(maxAge);
}
/**
* 获取错误状态的账号列表
*/
@Get('management/error-accounts')
@ApiOperation({
summary: '获取错误状态的账号列表',
description: '获取处于错误状态的账号列表'
})
@ApiQuery({
name: 'maxRetryCount',
required: false,
description: '最大重试次数默认3次',
example: 3
})
@ApiResponse({
status: 200,
description: '获取成功',
type: ZulipAccountListResponseDto,
})
async findErrorAccounts(
@Query('maxRetryCount') maxRetryCount?: number,
): Promise<ZulipAccountListResponseDto> {
return this.zulipAccountsService.findErrorAccounts(maxRetryCount);
}
/**
* 批量更新账号状态
*/
@Put('management/batch-status')
@ApiOperation({
summary: '批量更新账号状态',
description: '批量更新多个账号的状态'
})
@ApiResponse({
status: 200,
description: '更新成功',
type: BatchUpdateResponseDto,
})
async batchUpdateStatus(@Body() batchDto: BatchUpdateStatusDto): Promise<BatchUpdateResponseDto> {
return this.zulipAccountsService.batchUpdateStatus(batchDto.ids, batchDto.status);
}
/**
* 获取账号状态统计
*/
@Get('management/statistics')
@ApiOperation({
summary: '获取账号状态统计',
description: '获取各种状态的账号数量统计'
})
@ApiResponse({
status: 200,
description: '获取成功',
type: ZulipAccountStatsResponseDto,
})
async getStatusStatistics(): Promise<ZulipAccountStatsResponseDto> {
return this.zulipAccountsService.getStatusStatistics();
}
/**
* 验证账号有效性
*/
@Post('management/verify')
@ApiOperation({
summary: '验证账号有效性',
description: '验证指定游戏用户的Zulip账号关联是否有效'
})
@ApiResponse({
status: 200,
description: '验证完成',
type: VerifyAccountResponseDto,
})
async verifyAccount(@Body() verifyDto: VerifyAccountDto): Promise<VerifyAccountResponseDto> {
return this.zulipAccountsService.verifyAccount(verifyDto.gameUserId);
}
/**
* 检查邮箱是否已存在
*/
@Get('validation/email-exists/:email')
@ApiOperation({
summary: '检查邮箱是否已存在',
description: '检查指定的Zulip邮箱是否已被其他账号使用'
})
@ApiParam({
name: 'email',
description: 'Zulip邮箱地址',
example: 'user@example.com'
})
@ApiQuery({
name: 'excludeId',
required: false,
description: '排除的记录ID用于更新时检查',
example: '1'
})
@ApiResponse({
status: 200,
description: '检查完成',
schema: {
type: 'object',
properties: {
exists: { type: 'boolean', example: false },
email: { type: 'string', example: 'user@example.com' }
}
}
})
async checkEmailExists(
@Param('email') email: string,
@Query('excludeId') excludeId?: string,
): Promise<{ exists: boolean; email: string }> {
const exists = await this.zulipAccountsService.existsByEmail(email, excludeId);
return { exists, email };
}
/**
* 检查Zulip用户ID是否已存在
*/
@Get('validation/zulip-user-exists/:zulipUserId')
@ApiOperation({
summary: '检查Zulip用户ID是否已存在',
description: '检查指定的Zulip用户ID是否已被其他账号使用'
})
@ApiParam({
name: 'zulipUserId',
description: 'Zulip用户ID',
example: '67890'
})
@ApiQuery({
name: 'excludeId',
required: false,
description: '排除的记录ID用于更新时检查',
example: '1'
})
@ApiResponse({
status: 200,
description: '检查完成',
schema: {
type: 'object',
properties: {
exists: { type: 'boolean', example: false },
zulipUserId: { type: 'number', example: 67890 }
}
}
})
async checkZulipUserIdExists(
@Param('zulipUserId') zulipUserId: string,
@Query('excludeId') excludeId?: string,
): Promise<{ exists: boolean; zulipUserId: number }> {
const zulipUserIdNum = parseInt(zulipUserId);
const exists = await this.zulipAccountsService.existsByZulipUserId(zulipUserIdNum, excludeId);
return { exists, zulipUserId: zulipUserIdNum };
}
}

View File

@@ -13,7 +13,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import * as fc from 'fast-check';
import { MessageFilterService, ViolationType } from './message_filter.service';
import { IZulipConfigService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
import { IZulipConfigService } from '../../../core/zulip_core/interfaces/zulip_core.interfaces';
import { AppLoggerService } from '../../../core/utils/logger/logger.service';
import { IRedisService } from '../../../core/redis/redis.interface';

View File

@@ -7,6 +7,13 @@
* - 防止恶意操作和滥用
* - 与ConfigManager集成实现位置权限验证
*
* 职责分离:
* - 内容审核:检查消息内容是否包含敏感词和恶意链接
* - 频率控制:防止用户发送消息过于频繁导致刷屏
* - 权限验证验证用户是否有权限向目标Stream发送消息
* - 违规记录:记录和统计用户的违规行为
* - 规则管理:动态管理敏感词列表和过滤规则
*
* 主要方法:
* - filterContent(): 内容过滤,敏感词检查
* - checkRateLimit(): 频率限制检查
@@ -23,14 +30,19 @@
* - IRedisService: Redis缓存服务
* - ConfigManagerService: 配置管理服务
*
* 最近修改:
* - 2026-01-07: 代码规范优化 - 清理未使用的导入(forwardRef) (修改者: moyin)
* - 2026-01-07: 代码规范优化 - 完善文件头注释和修改记录 (修改者: moyin)
*
* @author angjustinl
* @version 1.1.0
* @version 1.1.2
* @since 2025-12-25
* @lastModified 2026-01-07
*/
import { Injectable, Logger, Inject, forwardRef } from '@nestjs/common';
import { Injectable, Logger, Inject } from '@nestjs/common';
import { IRedisService } from '../../../core/redis/redis.interface';
import { IZulipConfigService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
import { IZulipConfigService } from '../../../core/zulip_core/interfaces/zulip_core.interfaces';
/**
* 内容过滤结果接口

View File

@@ -25,7 +25,7 @@ import {
CleanupResult
} from './session_cleanup.service';
import { SessionManagerService } from './session_manager.service';
import { IZulipClientPoolService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
import { IZulipClientPoolService } from '../../../core/zulip_core/interfaces/zulip_core.interfaces';
describe('SessionCleanupService', () => {
let service: SessionCleanupService;

View File

@@ -23,7 +23,7 @@
import { Injectable, Logger, OnModuleInit, OnModuleDestroy, Inject } from '@nestjs/common';
import { SessionManagerService } from './session_manager.service';
import { IZulipClientPoolService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
import { IZulipClientPoolService } from '../../../core/zulip_core/interfaces/zulip_core.interfaces';
/**
* 清理任务配置接口

View File

@@ -13,7 +13,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import * as fc from 'fast-check';
import { SessionManagerService, GameSession, Position } from './session_manager.service';
import { IZulipConfigService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
import { IZulipConfigService } from '../../../core/zulip_core/interfaces/zulip_core.interfaces';
import { AppLoggerService } from '../../../core/utils/logger/logger.service';
import { IRedisService } from '../../../core/redis/redis.interface';

View File

@@ -8,6 +8,13 @@
* - 支持会话状态的序列化和反序列化
* - 支持服务重启后的状态恢复
*
* 职责分离:
* - 会话存储管理会话数据在Redis中的存储和检索
* - 位置跟踪:维护玩家在游戏世界中的位置信息
* - 上下文注入根据玩家位置确定消息的目标Stream和Topic
* - 空间过滤根据地图ID筛选相关的玩家会话
* - 资源清理:定期清理过期会话和释放相关资源
*
* 主要方法:
* - createSession(): 创建会话并绑定Socket_ID与Zulip_Queue_ID
* - getSession(): 获取会话信息
@@ -28,15 +35,19 @@
* - 消息分发时进行空间过滤
* - 玩家登出时清理会话数据
*
* 最近修改:
* - 2026-01-07: 代码规范优化 - 完善文件头注释和修改记录 (修改者: moyin)
*
* @author angjustinl
* @version 1.0.0
* @version 1.0.1
* @since 2025-12-25
* @lastModified 2026-01-07
*/
import { Injectable, Logger, Inject } from '@nestjs/common';
import { IRedisService } from '../../../core/redis/redis.interface';
import { IZulipConfigService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
import { Internal, Constants } from '../../../core/zulip/interfaces/zulip.interfaces';
import { IZulipConfigService } from '../../../core/zulip_core/interfaces/zulip_core.interfaces';
import { Internal, Constants } from '../../../core/zulip_core/interfaces/zulip.interfaces';
/**
* 游戏会话接口 - 重新导出以保持向后兼容

View File

@@ -26,7 +26,7 @@ import {
MessageDistributor,
} from './zulip_event_processor.service';
import { SessionManagerService, GameSession } from './session_manager.service';
import { IZulipConfigService, IZulipClientPoolService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
import { IZulipConfigService, IZulipClientPoolService } from '../../../core/zulip_core/interfaces/zulip_core.interfaces';
import { AppLoggerService } from '../../../core/utils/logger/logger.service';
describe('ZulipEventProcessorService', () => {

View File

@@ -32,7 +32,7 @@
import { Injectable, OnModuleDestroy, Inject, forwardRef, Logger } from '@nestjs/common';
import { SessionManagerService } from './session_manager.service';
import { IZulipConfigService, IZulipClientPoolService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
import { IZulipConfigService, IZulipClientPoolService } from '../../../core/zulip_core/interfaces/zulip_core.interfaces';
/**
* Zulip消息接口

View File

@@ -50,8 +50,10 @@ import { MessageFilterService } from './services/message_filter.service';
import { ZulipEventProcessorService } from './services/zulip_event_processor.service';
import { SessionCleanupService } from './services/session_cleanup.service';
import { ChatController } from './controllers/chat.controller';
import { WebSocketDocsController } from './controllers/websocket-docs.controller';
import { ZulipCoreModule } from '../../core/zulip/zulip-core.module';
import { WebSocketDocsController } from './controllers/websocket_docs.controller';
import { ZulipAccountsController } from './controllers/zulip_accounts.controller';
import { ZulipCoreModule } from '../../core/zulip_core/zulip_core.module';
import { ZulipAccountsModule } from '../../core/db/zulip_accounts/zulip_accounts.module';
import { RedisModule } from '../../core/redis/redis.module';
import { LoggerModule } from '../../core/utils/logger/logger.module';
import { LoginCoreModule } from '../../core/login_core/login_core.module';
@@ -61,6 +63,8 @@ import { AuthModule } from '../auth/auth.module';
imports: [
// Zulip核心服务模块 - 提供技术实现相关的核心服务
ZulipCoreModule,
// Zulip账号关联模块 - 提供账号关联管理功能
ZulipAccountsModule.forRoot(),
// Redis模块 - 提供会话状态缓存和数据存储
RedisModule,
// 日志模块 - 提供统一的日志记录服务
@@ -89,6 +93,8 @@ import { AuthModule } from '../auth/auth.module';
ChatController,
// WebSocket API文档控制器
WebSocketDocsController,
// Zulip账号关联管理控制器
ZulipAccountsController,
],
exports: [
// 导出主服务供其他模块使用

View File

@@ -39,8 +39,9 @@ import {
IZulipConfigService,
ZulipClientInstance,
SendMessageResult,
} from '../../core/zulip/interfaces/zulip-core.interfaces';
import { ApiKeySecurityService } from '../../core/zulip/services/api_key_security.service';
} from '../../core/zulip_core/interfaces/zulip_core.interfaces';
import { ApiKeySecurityService } from '../../core/zulip_core/services/api_key_security.service';
import { LoginCoreService } from '../../core/login_core/login_core.service';
describe('ZulipService', () => {
let service: ZulipService;
@@ -49,6 +50,7 @@ describe('ZulipService', () => {
let mockMessageFilter: jest.Mocked<MessageFilterService>;
let mockEventProcessor: jest.Mocked<ZulipEventProcessorService>;
let mockConfigManager: jest.Mocked<IZulipConfigService>;
let mockLoginCoreService: jest.Mocked<LoginCoreService>;
// 创建模拟的Zulip客户端实例
const createMockClientInstance = (overrides: Partial<ZulipClientInstance> = {}): ZulipClientInstance => ({
@@ -136,6 +138,14 @@ describe('ZulipService', () => {
validateConfig: jest.fn(),
} as any;
mockLoginCoreService = {
verifyToken: jest.fn(),
generateTokens: jest.fn(),
refreshTokens: jest.fn(),
revokeToken: jest.fn(),
validateTokenPayload: jest.fn(),
} as any;
const module: TestingModule = await Test.createTestingModule({
providers: [
ZulipService,
@@ -160,7 +170,7 @@ describe('ZulipService', () => {
useValue: mockConfigManager,
},
{
provide: ApiKeySecurityService,
provide: 'API_KEY_SECURITY_SERVICE',
useValue: {
extractApiKey: jest.fn(),
validateApiKey: jest.fn(),
@@ -172,10 +182,39 @@ describe('ZulipService', () => {
}),
},
},
{
provide: LoginCoreService,
useValue: mockLoginCoreService,
},
],
}).compile();
service = module.get<ZulipService>(ZulipService);
// 配置LoginCoreService的默认mock行为
mockLoginCoreService.verifyToken.mockImplementation(async (token: string) => {
// 模拟token验证逻辑
if (token.startsWith('invalid')) {
throw new Error('Invalid token');
}
// 从token中提取用户信息模拟JWT解析
const userId = `user_${token.substring(0, 8)}`;
const username = `Player_${userId.substring(5, 10)}`;
const email = `${userId}@example.com`;
return {
sub: userId,
username,
email,
role: 1, // 数字类型的角色
type: 'access' as const,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600,
iss: 'whale-town',
aud: 'whale-town-users',
};
});
});
it('should be defined', () => {

View File

@@ -6,6 +6,12 @@
* - 整合各个子服务,提供统一的业务接口
* - 处理游戏客户端与Zulip之间的核心业务逻辑
*
* 职责分离:
* - 业务协调:整合会话管理、消息过滤、事件处理等子服务
* - 流程控制:管理玩家登录登出的完整业务流程
* - 接口适配在游戏协议和Zulip协议之间进行转换
* - 错误处理:统一处理业务异常和降级策略
*
* 主要方法:
* - handlePlayerLogin(): 处理玩家登录和Zulip客户端初始化
* - handlePlayerLogout(): 处理玩家登出和资源清理
@@ -17,9 +23,15 @@
* - 会话管理和状态维护
* - 消息格式转换和过滤
*
* 最近修改:
* - 2026-01-07: 代码规范优化 - 注释规范检查和修正 (修改者: moyin)
* - 2026-01-07: 代码规范优化 - 拆分过长方法提取validateLoginParams和createUserSession私有方法 (修改者: moyin)
* - 2026-01-07: 代码规范优化 - 完善文件头注释和修改记录 (修改者: moyin)
*
* @author angjustinl
* @version 1.1.0
* @version 1.2.0
* @since 2026-01-06
* @lastModified 2026-01-07
*/
import { Injectable, Logger, Inject } from '@nestjs/common';
@@ -30,9 +42,9 @@ import { ZulipEventProcessorService } from './services/zulip_event_processor.ser
import {
IZulipClientPoolService,
IZulipConfigService,
} from '../../core/zulip/interfaces/zulip-core.interfaces';
import { ApiKeySecurityService } from '../../core/zulip/services/api_key_security.service';
import { LoginService } from '../auth/services/login.service';
IApiKeySecurityService,
} from '../../core/zulip_core/interfaces/zulip_core.interfaces';
import { LoginCoreService } from '../../core/login_core/login_core.service';
/**
* 玩家登录请求接口
@@ -116,8 +128,9 @@ export class ZulipService {
private readonly eventProcessor: ZulipEventProcessorService,
@Inject('ZULIP_CONFIG_SERVICE')
private readonly configManager: IZulipConfigService,
private readonly apiKeySecurityService: ApiKeySecurityService,
private readonly loginService: LoginService,
@Inject('API_KEY_SECURITY_SERVICE')
private readonly apiKeySecurityService: IApiKeySecurityService,
private readonly loginCoreService: LoginCoreService,
) {
this.logger.log('ZulipService初始化完成');
@@ -144,6 +157,18 @@ export class ZulipService {
*
* @throws UnauthorizedException 当Token验证失败时
* @throws InternalServerErrorException 当系统操作失败时
*
* @example
* ```typescript
* const loginRequest: PlayerLoginRequest = {
* token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
* socketId: 'socket_12345'
* };
* const result = await zulipService.handlePlayerLogin(loginRequest);
* if (result.success) {
* console.log(`用户 ${result.username} 登录成功`);
* }
* ```
*/
async handlePlayerLogin(request: PlayerLoginRequest): Promise<LoginResponse> {
const startTime = Date.now();
@@ -156,28 +181,15 @@ export class ZulipService {
try {
// 1. 验证请求参数
if (!request.token || !request.token.trim()) {
this.logger.warn('登录失败Token为空', {
operation: 'handlePlayerLogin',
socketId: request.socketId,
});
const paramValidation = this.validateLoginParams(request);
if (!paramValidation.isValid) {
return {
success: false,
error: 'Token不能为空',
error: paramValidation.error,
};
}
if (!request.socketId || !request.socketId.trim()) {
this.logger.warn('登录失败socketId为空', {
operation: 'handlePlayerLogin',
});
return {
success: false,
error: 'socketId不能为空',
};
}
// 2. 验证游戏Token并获取用户信息 调用认证服务验证Token
// 2. 验证游戏Token并获取用户信息
const userInfo = await this.validateGameToken(request.token);
if (!userInfo) {
this.logger.warn('登录失败Token验证失败', {
@@ -190,80 +202,28 @@ export class ZulipService {
};
}
// 3. 生成会话ID
const sessionId = randomUUID();
// 调试日志:检查用户信息
this.logger.log('用户信息检查', {
operation: 'handlePlayerLogin',
userId: userInfo.userId,
hasZulipApiKey: !!userInfo.zulipApiKey,
zulipApiKeyLength: userInfo.zulipApiKey?.length || 0,
zulipEmail: userInfo.zulipEmail,
email: userInfo.email,
});
// 4. 创建Zulip客户端如果有API Key
let zulipQueueId = `queue_${sessionId}`;
// 3. 创建Zulip客户端和会话
const sessionResult = await this.createUserSession(request.socketId, userInfo);
if (userInfo.zulipApiKey) {
try {
const zulipConfig = this.configManager.getZulipConfig();
const clientInstance = await this.zulipClientPool.createUserClient(userInfo.userId, {
username: userInfo.zulipEmail || userInfo.email,
apiKey: userInfo.zulipApiKey,
realm: zulipConfig.zulipServerUrl,
});
if (clientInstance.queueId) {
zulipQueueId = clientInstance.queueId;
}
this.logger.log('Zulip客户端创建成功', {
operation: 'handlePlayerLogin',
userId: userInfo.userId,
queueId: zulipQueueId,
});
} catch (zulipError) {
const err = zulipError as Error;
this.logger.warn('Zulip客户端创建失败使用本地模式', {
operation: 'handlePlayerLogin',
userId: userInfo.userId,
error: err.message,
});
// Zulip客户端创建失败不影响登录使用本地模式
}
}
// 5. 创建游戏会话
const session = await this.sessionManager.createSession(
request.socketId,
userInfo.userId,
zulipQueueId,
userInfo.username,
this.DEFAULT_MAP,
{ x: 400, y: 300 },
);
const duration = Date.now() - startTime;
this.logger.log('玩家登录处理完成', {
operation: 'handlePlayerLogin',
socketId: request.socketId,
sessionId,
sessionId: sessionResult.sessionId,
userId: userInfo.userId,
username: userInfo.username,
currentMap: session.currentMap,
currentMap: sessionResult.currentMap,
duration,
timestamp: new Date().toISOString(),
});
return {
success: true,
sessionId,
sessionId: sessionResult.sessionId,
userId: userInfo.userId,
username: userInfo.username,
currentMap: session.currentMap,
currentMap: sessionResult.currentMap,
};
} catch (error) {
@@ -285,6 +245,108 @@ export class ZulipService {
}
}
/**
* 验证登录请求参数
*
* @param request 登录请求
* @returns 验证结果
* @private
*/
private validateLoginParams(request: PlayerLoginRequest): { isValid: boolean; error?: string } {
if (!request.token || !request.token.trim()) {
this.logger.warn('登录失败Token为空', {
operation: 'validateLoginParams',
socketId: request.socketId,
});
return {
isValid: false,
error: 'Token不能为空',
};
}
if (!request.socketId || !request.socketId.trim()) {
this.logger.warn('登录失败socketId为空', {
operation: 'validateLoginParams',
});
return {
isValid: false,
error: 'socketId不能为空',
};
}
return { isValid: true };
}
/**
* 创建用户会话和Zulip客户端
*
* @param socketId Socket连接ID
* @param userInfo 用户信息
* @returns 会话创建结果
* @private
*/
private async createUserSession(socketId: string, userInfo: any): Promise<{ sessionId: string; currentMap: string }> {
// 生成会话ID
const sessionId = randomUUID();
// 调试日志:检查用户信息
this.logger.log('用户信息检查', {
operation: 'createUserSession',
userId: userInfo.userId,
hasZulipApiKey: !!userInfo.zulipApiKey,
zulipApiKeyLength: userInfo.zulipApiKey?.length || 0,
zulipEmail: userInfo.zulipEmail,
email: userInfo.email,
});
// 创建Zulip客户端如果有API Key
let zulipQueueId = `queue_${sessionId}`;
if (userInfo.zulipApiKey) {
try {
const zulipConfig = this.configManager.getZulipConfig();
const clientInstance = await this.zulipClientPool.createUserClient(userInfo.userId, {
username: userInfo.zulipEmail || userInfo.email,
apiKey: userInfo.zulipApiKey,
realm: zulipConfig.zulipServerUrl,
});
if (clientInstance.queueId) {
zulipQueueId = clientInstance.queueId;
}
this.logger.log('Zulip客户端创建成功', {
operation: 'createUserSession',
userId: userInfo.userId,
queueId: zulipQueueId,
});
} catch (zulipError) {
const err = zulipError as Error;
this.logger.warn('Zulip客户端创建失败使用本地模式', {
operation: 'createUserSession',
userId: userInfo.userId,
error: err.message,
});
// Zulip客户端创建失败不影响登录使用本地模式
}
}
// 创建游戏会话
const session = await this.sessionManager.createSession(
socketId,
userInfo.userId,
zulipQueueId,
userInfo.username,
this.DEFAULT_MAP,
{ x: 400, y: 300 },
);
return {
sessionId,
currentMap: session.currentMap,
};
}
/**
* 验证游戏Token
*
@@ -308,8 +370,8 @@ export class ZulipService {
});
try {
// 1. 使用LoginService验证JWT token
const payload = await this.loginService.verifyToken(token, 'access');
// 1. 使用LoginCoreService验证JWT token
const payload = await this.loginCoreService.verifyToken(token, 'access');
if (!payload || !payload.sub) {
this.logger.warn('Token载荷无效', {

View File

@@ -6,6 +6,12 @@
* - 实现游戏协议到Zulip协议的转换
* - 提供统一的消息路由和权限控制
*
* 职责分离:
* - 连接管理处理WebSocket连接的建立、维护和断开
* - 协议转换:在游戏客户端协议和内部业务协议之间转换
* - 权限控制:验证用户身份和消息发送权限
* - 消息路由:将消息分发到正确的业务处理服务
*
* 主要方法:
* - handleConnection(): 处理客户端连接建立
* - handleDisconnect(): 处理客户端连接断开
@@ -18,9 +24,13 @@
* - 消息协议转换和路由分发
* - 连接状态管理和权限验证
*
* 最近修改:
* - 2026-01-07: 代码规范优化 - 完善文件头注释和修改记录 (修改者: moyin)
*
* @author angjustinl
* @version 1.0.0
* @version 1.0.1
* @since 2025-12-25
* @lastModified 2026-01-07
*/
import {