From cd2a1972886bcc19901ee77ef7ba6035de082f11 Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Mon, 19 Jan 2026 17:43:59 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(chat):=20=E6=B7=BB=E5=8A=A0=E5=9C=B0?= =?UTF-8?q?=E5=9B=BE=E5=88=87=E6=8D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 范围: src/gateway/chat/ - 新增 change_map 事件处理 - 实现 handleChangeMap() 方法 - 支持玩家在不同地图间切换 - 自动更新房间成员和广播通知 - 完善地图切换的错误处理 功能说明: - 玩家可以通过 WebSocket 发送 change_map 事件切换地图 - 自动处理房间加入/离开逻辑 - 向旧地图广播玩家离开,向新地图广播玩家加入 - 支持携带初始位置坐标,默认使用 (400, 300) --- src/gateway/chat/chat.gateway.ts | 84 +++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/src/gateway/chat/chat.gateway.ts b/src/gateway/chat/chat.gateway.ts index 725ea0d..dace8b6 100644 --- a/src/gateway/chat/chat.gateway.ts +++ b/src/gateway/chat/chat.gateway.ts @@ -169,6 +169,9 @@ export class ChatWebSocketGateway implements OnModuleInit, OnModuleDestroy, ICha case 'position': await this.handlePosition(ws, message); break; + case 'change_map': + await this.handleChangeMap(ws, message); + break; default: this.logger.warn(`未知消息类型: ${messageType}`); this.sendError(ws, `未知消息类型: ${messageType}`); @@ -254,7 +257,7 @@ export class ChatWebSocketGateway implements OnModuleInit, OnModuleDestroy, ICha * 处理聊天消息 * * @param ws WebSocket 连接实例 - * @param message 聊天消息(包含 content, scope) + * @param message 聊天消息(包含 content, scope, mapId) */ private async handleChat(ws: ExtendedWebSocket, message: any) { if (!ws.authenticated) { @@ -271,7 +274,8 @@ export class ChatWebSocketGateway implements OnModuleInit, OnModuleDestroy, ICha const result = await this.chatService.sendChatMessage({ socketId: ws.id, content: message.content, - scope: message.scope || 'local' + scope: message.scope || 'local', + mapId: message.mapId || ws.currentMap, // 支持指定目标地图 }); if (result.success) { @@ -335,6 +339,82 @@ export class ChatWebSocketGateway implements OnModuleInit, OnModuleDestroy, ICha } } + /** + * 处理切换地图 + * + * @param ws WebSocket 连接实例 + * @param message 切换地图消息(包含 mapId) + */ + private async handleChangeMap(ws: ExtendedWebSocket, message: any) { + if (!ws.authenticated) { + this.sendError(ws, '请先登录'); + return; + } + + if (!message.mapId) { + this.sendError(ws, '地图ID不能为空'); + return; + } + + try { + const oldMapId = ws.currentMap; + const newMapId = message.mapId; + + // 如果地图相同,直接返回成功 + if (oldMapId === newMapId) { + this.sendMessage(ws, { + t: 'map_changed', + mapId: newMapId, + message: '已在当前地图' + }); + return; + } + + // 更新房间 + this.leaveMapRoom(ws.id, oldMapId); + this.joinMapRoom(ws.id, newMapId); + ws.currentMap = newMapId; + + // 更新会话中的地图信息(使用默认位置) + await this.chatService.updatePlayerPosition({ + socketId: ws.id, + x: message.x || 400, + y: message.y || 300, + mapId: newMapId + }); + + // 通知客户端切换成功 + this.sendMessage(ws, { + t: 'map_changed', + mapId: newMapId, + oldMapId: oldMapId, + message: '地图切换成功' + }); + + // 向旧地图广播玩家离开 + this.broadcastToMap(oldMapId, { + t: 'player_left', + userId: ws.userId, + username: ws.username, + mapId: oldMapId + }); + + // 向新地图广播玩家加入 + this.broadcastToMap(newMapId, { + t: 'player_joined', + userId: ws.userId, + username: ws.username, + mapId: newMapId + }, ws.id); + + this.logger.log(`用户切换地图: ${ws.username} (${oldMapId} -> ${newMapId})`); + + } catch (error) { + this.logger.error('切换地图处理失败', error); + this.sendError(ws, '切换地图处理失败'); + } + } + /** * 处理连接关闭 * From 963e6ca90f32f7d692264a177f5094fe69b65300 Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Mon, 19 Jan 2026 17:59:58 +0800 Subject: [PATCH 2/5] =?UTF-8?q?refactor(auth):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E6=B3=A8=E5=86=8C=E6=97=B6=E7=9A=84Zulip?= =?UTF-8?q?=E5=86=85=E5=AD=98=E5=85=B3=E8=81=94=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 范围: src/business/auth/ 涉及文件: - src/business/auth/login.service.ts - src/business/auth/register.service.ts 主要改进: - 移除登录时建立Zulip内存关联的代码 - 移除注册时建立Zulip内存关联的代码 - 改为在WebSocket连接时由Zulip客户端创建内存关联 - 优化了内存关联的时机,避免不必要的提前创建 技术说明: - 原逻辑在登录/注册时就建立内存关联,但用户可能不会立即使用Zulip - 新逻辑延迟到WebSocket连接时创建,更加合理和高效 - 减少了登录/注册流程的复杂度和耦合度 --- src/business/auth/login.service.ts | 8 +------- src/business/auth/register.service.ts | 10 +--------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/business/auth/login.service.ts b/src/business/auth/login.service.ts index e451810..1c46ca1 100644 --- a/src/business/auth/login.service.ts +++ b/src/business/auth/login.service.ts @@ -714,13 +714,7 @@ export class LoginService { apiKeyResult.apiKey! ); - // 4. 更新内存关联 - await this.zulipAccountService.linkGameAccount( - user.id.toString(), - zulipAccount.zulipUserId, - zulipAccount.zulipEmail, - apiKeyResult.apiKey! - ); + // 注意:不在登录时建立内存关联,Zulip客户端将在WebSocket连接时创建 const duration = Date.now() - startTime; diff --git a/src/business/auth/register.service.ts b/src/business/auth/register.service.ts index 1e88f27..baf885b 100644 --- a/src/business/auth/register.service.ts +++ b/src/business/auth/register.service.ts @@ -533,15 +533,7 @@ export class RegisterService { status: 'active', }); - // 6. 建立游戏账号与Zulip账号的内存关联(用于当前会话) - if (finalApiKey) { - await this.zulipAccountService.linkGameAccount( - gameUser.id.toString(), - createResult.userId, // 已在上面验证不为 undefined - createResult.email!, - finalApiKey - ); - } + // 注意:不在注册时建立内存关联,Zulip客户端将在WebSocket连接时创建 const duration = Date.now() - startTime; From 1849415b1168075000fa4d2c7866eefa444556c7 Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Mon, 19 Jan 2026 18:29:27 +0800 Subject: [PATCH 3/5] =?UTF-8?q?test(chat):=20=E4=BF=AE=E5=A4=8D=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=96=87=E4=BB=B6Mock=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 范围: src/business/chat/ 涉及文件: - chat.module.spec.ts - chat.service.spec.ts 主要改进: - 添加缺失的ZulipAccountsService Mock配置 - 修复handlePlayerLogout测试,删除过时的deleteApiKey断言 - 删除不再需要的API Key清理失败测试用例 - 添加getUserClient Mock方法 - 设置默认Mock行为,提高测试稳定性 --- src/business/chat/chat.module.spec.ts | 16 +++++++-- src/business/chat/chat.service.spec.ts | 45 +++++++++++++------------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/business/chat/chat.module.spec.ts b/src/business/chat/chat.module.spec.ts index 1bcc759..cb42ae3 100644 --- a/src/business/chat/chat.module.spec.ts +++ b/src/business/chat/chat.module.spec.ts @@ -7,9 +7,12 @@ * - 接口导出验证 * * @author moyin - * @version 1.0.0 + * @version 1.0.1 * @since 2026-01-14 - * @lastModified 2026-01-14 + * @lastModified 2026-01-19 + * + * 修改记录: + * - 2026-01-19 moyin: Bug修复 - 添加缺失的ZulipAccountsService Mock配置 */ import { Test, TestingModule } from '@nestjs/testing'; @@ -33,6 +36,7 @@ describe('ChatModule', () => { createUserClient: jest.fn(), destroyUserClient: jest.fn(), sendMessage: jest.fn(), + getUserClient: jest.fn(), }; const mockZulipConfigService = { @@ -61,6 +65,10 @@ describe('ChatModule', () => { verifyToken: jest.fn(), }; + const mockZulipAccountsService = { + findByGameUserId: jest.fn(), + }; + beforeEach(async () => { // 禁用日志输出 jest.spyOn(Logger.prototype, 'log').mockImplementation(); @@ -97,6 +105,10 @@ describe('ChatModule', () => { provide: LoginCoreService, useValue: mockLoginCoreService, }, + { + provide: 'ZulipAccountsService', + useValue: mockZulipAccountsService, + }, ], }).compile(); diff --git a/src/business/chat/chat.service.spec.ts b/src/business/chat/chat.service.spec.ts index 21b68ca..9dac974 100644 --- a/src/business/chat/chat.service.spec.ts +++ b/src/business/chat/chat.service.spec.ts @@ -8,9 +8,12 @@ * - Token验证和错误处理 * * @author moyin - * @version 1.0.0 + * @version 1.0.1 * @since 2026-01-14 - * @lastModified 2026-01-14 + * @lastModified 2026-01-19 + * + * 修改记录: + * - 2026-01-19 moyin: 修复handlePlayerLogout测试,删除不再调用的deleteApiKey断言和过时测试用例 */ import { Test, TestingModule } from '@nestjs/testing'; @@ -51,6 +54,7 @@ describe('ChatService', () => { createUserClient: jest.fn(), destroyUserClient: jest.fn(), sendMessage: jest.fn(), + getUserClient: jest.fn(), }; const mockApiKeySecurityService = { @@ -62,6 +66,10 @@ describe('ChatService', () => { verifyToken: jest.fn(), }; + const mockZulipAccountsService = { + findByGameUserId: jest.fn(), + }; + mockWebSocketGateway = { broadcastToMap: jest.fn(), sendToPlayer: jest.fn(), @@ -90,6 +98,10 @@ describe('ChatService', () => { provide: LoginCoreService, useValue: mockLoginCoreService, }, + { + provide: 'ZulipAccountsService', + useValue: mockZulipAccountsService, + }, ], }).compile(); @@ -100,6 +112,14 @@ describe('ChatService', () => { apiKeySecurityService = module.get('API_KEY_SECURITY_SERVICE'); loginCoreService = module.get(LoginCoreService); + // 设置默认的mock行为 + // ZulipAccountsService默认返回null(用户没有Zulip账号) + const zulipAccountsService = module.get('ZulipAccountsService'); + zulipAccountsService.findByGameUserId.mockResolvedValue(null); + + // ZulipClientPool的getUserClient默认返回null + zulipClientPool.getUserClient.mockResolvedValue(null); + // 设置WebSocket网关 service.setWebSocketGateway(mockWebSocketGateway); @@ -220,14 +240,12 @@ describe('ChatService', () => { createdAt: new Date(), }); zulipClientPool.destroyUserClient.mockResolvedValue(undefined); - apiKeySecurityService.deleteApiKey.mockResolvedValue(undefined); sessionService.destroySession.mockResolvedValue(true); await service.handlePlayerLogout(socketId, 'manual'); expect(sessionService.getSession).toHaveBeenCalledWith(socketId); expect(zulipClientPool.destroyUserClient).toHaveBeenCalledWith(userId); - expect(apiKeySecurityService.deleteApiKey).toHaveBeenCalledWith(userId); expect(sessionService.destroySession).toHaveBeenCalledWith(socketId); }); @@ -257,25 +275,6 @@ describe('ChatService', () => { expect(sessionService.destroySession).toHaveBeenCalled(); }); - - it('应该处理API Key清理失败', async () => { - sessionService.getSession.mockResolvedValue({ - socketId, - userId, - username: 'testuser', - zulipQueueId: 'queue_123', - currentMap: 'whale_port', - position: { x: 400, y: 300 }, - lastActivity: new Date(), - createdAt: new Date(), - }); - apiKeySecurityService.deleteApiKey.mockRejectedValue(new Error('Redis error')); - sessionService.destroySession.mockResolvedValue(true); - - await service.handlePlayerLogout(socketId); - - expect(sessionService.destroySession).toHaveBeenCalled(); - }); }); describe('sendChatMessage', () => { From cf1b37af78f8ec399dc2f31fd0634faaabedc17b Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Mon, 19 Jan 2026 18:29:53 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat(chat):=20=E5=AE=9E=E7=8E=B0=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=97=B6=E8=87=AA=E5=8A=A8=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?Zulip=E5=AE=A2=E6=88=B7=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 范围: src/business/chat/ 涉及文件: - chat.module.ts - chat.service.ts 主要功能: - 添加ZulipAccountsModule依赖,支持查询用户Zulip账号 - 实现initializeZulipClientForUser方法,登录时自动初始化Zulip客户端 - 从数据库获取用户Zulip账号信息和API Key - 优化会话创建流程,使用已创建的Zulip客户端队列ID - 移除登出时的API Key删除逻辑,保持持久化 - 支持基于目标地图的消息发送(mapId参数) 技术改进: - 分离Zulip客户端初始化逻辑,提高代码可维护性 - 添加完整的错误处理和日志记录 - 支持用户没有Zulip账号的场景(优雅降级) --- src/business/chat/chat.module.ts | 9 +- src/business/chat/chat.service.ts | 182 +++++++++++++++++++++++++----- 2 files changed, 158 insertions(+), 33 deletions(-) diff --git a/src/business/chat/chat.module.ts b/src/business/chat/chat.module.ts index e24e14d..b03e898 100644 --- a/src/business/chat/chat.module.ts +++ b/src/business/chat/chat.module.ts @@ -12,17 +12,19 @@ * - 依赖 ZulipCoreModule(核心层)提供Zulip技术服务 * - 依赖 RedisModule(核心层)提供缓存服务 * - 依赖 LoginCoreModule(核心层)提供Token验证 + * - 依赖 ZulipAccountsModule(核心层)提供Zulip账号数据访问 * * 导出接口: * - SESSION_QUERY_SERVICE: 会话查询接口(供其他 Business 模块使用) * * 最近修改: + * - 2026-01-15: 功能完善 - 添加ZulipAccountsModule依赖,支持登录时初始化Zulip客户端 (修改者: AI) * - 2026-01-14: 代码规范优化 - 完善文件头注释规范 (修改者: moyin) * * @author moyin - * @version 1.1.1 + * @version 1.2.0 * @since 2026-01-14 - * @lastModified 2026-01-14 + * @lastModified 2026-01-15 */ import { Module } from '@nestjs/common'; @@ -33,6 +35,7 @@ import { ChatCleanupService } from './services/chat_cleanup.service'; import { ZulipCoreModule } from '../../core/zulip_core/zulip_core.module'; import { RedisModule } from '../../core/redis/redis.module'; import { LoginCoreModule } from '../../core/login_core/login_core.module'; +import { ZulipAccountsModule } from '../../core/db/zulip_accounts/zulip_accounts.module'; import { SESSION_QUERY_SERVICE } from '../../core/session_core/session_core.interfaces'; @Module({ @@ -43,6 +46,8 @@ import { SESSION_QUERY_SERVICE } from '../../core/session_core/session_core.inte RedisModule, // 登录核心模块 LoginCoreModule, + // Zulip账号数据库模块 + ZulipAccountsModule.forRoot(), ], providers: [ // 主聊天服务 diff --git a/src/business/chat/chat.service.ts b/src/business/chat/chat.service.ts index 9f09599..556c989 100644 --- a/src/business/chat/chat.service.ts +++ b/src/business/chat/chat.service.ts @@ -14,15 +14,16 @@ * - ⚡ 低延迟聊天体验 * * 最近修改: + * - 2026-01-15: 功能完善 - WebSocket登录时自动初始化用户Zulip客户端 (修改者: AI) * - 2026-01-14: 代码规范优化 - 提取魔法数字为常量 (修改者: moyin) * - 2026-01-14: 代码规范优化 - 补充类级别JSDoc注释 (修改者: moyin) * - 2026-01-14: 代码规范优化 - 补充接口定义的JSDoc注释 (修改者: moyin) * - 2026-01-14: 代码规范优化 - 完善文件头注释和方法注释规范 (修改者: moyin) * * @author moyin - * @version 1.0.4 + * @version 1.1.0 * @since 2026-01-14 - * @lastModified 2026-01-14 + * @lastModified 2026-01-15 */ import { Injectable, Logger, Inject } from '@nestjs/common'; @@ -34,6 +35,8 @@ import { IApiKeySecurityService, } from '../../core/zulip_core/zulip_core.interfaces'; import { LoginCoreService } from '../../core/login_core/login_core.service'; +import { ZulipAccountsService } from '../../core/db/zulip_accounts/zulip_accounts.service'; +import { ZulipAccountsMemoryService } from '../../core/db/zulip_accounts/zulip_accounts_memory.service'; // ========== 接口定义 ========== @@ -47,6 +50,8 @@ export interface ChatMessageRequest { content: string; /** 消息范围:local(本地)、global(全局) */ scope: string; + /** 目标地图ID(可选,不传则使用会话当前地图) */ + mapId?: string; } /** @@ -179,6 +184,8 @@ export class ChatService { @Inject('API_KEY_SECURITY_SERVICE') private readonly apiKeySecurityService: IApiKeySecurityService, private readonly loginCoreService: LoginCoreService, + @Inject('ZulipAccountsService') + private readonly zulipAccountsService: ZulipAccountsService | ZulipAccountsMemoryService, ) { this.logger.log('ChatService初始化完成'); } @@ -217,7 +224,10 @@ export class ChatService { return { success: false, error: 'Token验证失败' }; } - // 3. 创建会话 + // 3. 初始化用户的Zulip客户端(从数据库获取Zulip账号信息) + await this.initializeZulipClientForUser(userInfo.userId); + + // 4. 创建会话 const sessionResult = await this.createUserSession(request.socketId, userInfo); this.logger.log('玩家登录成功', { @@ -256,20 +266,13 @@ export class ChatService { const userId = session.userId; - // 清理Zulip客户端 + // 清理Zulip客户端(注意:不删除Redis中的API Key,保持持久化) if (userId) { try { await this.zulipClientPool.destroyUserClient(userId); } catch (e) { this.logger.warn('Zulip客户端清理失败', { error: (e as Error).message }); } - - // 清理API Key缓存 - try { - await this.apiKeySecurityService.deleteApiKey(userId); - } catch (e) { - this.logger.warn('API Key缓存清理失败', { error: (e as Error).message }); - } } // 销毁会话 @@ -303,17 +306,20 @@ export class ChatService { return { success: false, error: '会话不存在,请重新登录' }; } - // 2. 获取上下文 - const context = await this.sessionService.injectContext(request.socketId); + // 2. 确定目标地图(优先使用请求中的mapId,否则使用会话当前地图) + const targetMapId = request.mapId || session.currentMap; + + // 3. 获取上下文 + const context = await this.sessionService.injectContext(request.socketId, targetMapId); const targetStream = context.stream; const targetTopic = context.topic || 'General'; - // 3. 消息验证 + // 4. 消息验证 const validationResult = await this.filterService.validateMessage( session.userId, request.content, targetStream, - session.currentMap, + targetMapId, ); if (!validationResult.allowed) { @@ -323,7 +329,7 @@ export class ChatService { const messageContent = validationResult.filteredContent || request.content; const messageId = `game_${Date.now()}_${session.userId}`; - // 4. 🚀 立即广播给游戏内玩家 + // 5. 🚀 立即广播给游戏内玩家(根据scope决定广播范围) const gameMessage: GameChatMessage = { t: 'chat_render', from: session.username, @@ -331,14 +337,15 @@ export class ChatService { bubble: true, timestamp: new Date().toISOString(), messageId, - mapId: session.currentMap, + mapId: targetMapId, scope: request.scope, }; - this.broadcastToGamePlayers(session.currentMap, gameMessage, request.socketId) + // local: 只广播给目标地图的玩家; global: 广播给所有玩家(暂时也用地图广播) + this.broadcastToGamePlayers(targetMapId, gameMessage, request.socketId) .catch(e => this.logger.warn('游戏内广播失败', { error: (e as Error).message })); - // 5. 🔄 异步同步到Zulip + // 6. 🔄 异步同步到Zulip this.syncToZulipAsync(session.userId, targetStream, targetTopic, messageContent, messageId) .catch(e => this.logger.warn('Zulip同步失败', { error: (e as Error).message })); @@ -421,6 +428,121 @@ export class ChatService { // ========== 私有方法 ========== + /** + * 初始化用户的Zulip客户端 + * + * 功能描述: + * 1. 从数据库获取用户的Zulip账号信息 + * 2. 检查Redis中是否已有API Key缓存 + * 3. 如果Redis中没有,从数据库标记判断是否需要重新获取 + * 4. 创建Zulip客户端实例 + * + * @param userId 用户ID + */ + private async initializeZulipClientForUser(userId: string): Promise { + this.logger.log('开始初始化用户Zulip客户端', { + operation: 'initializeZulipClientForUser', + userId, + }); + + try { + // 1. 从数据库获取用户的Zulip账号信息 + const zulipAccount = await this.zulipAccountsService.findByGameUserId(userId); + + if (!zulipAccount) { + this.logger.debug('用户没有关联的Zulip账号,跳过Zulip客户端初始化', { + operation: 'initializeZulipClientForUser', + userId, + }); + return; + } + + if (zulipAccount.status !== 'active') { + this.logger.warn('用户Zulip账号状态异常,跳过初始化', { + operation: 'initializeZulipClientForUser', + userId, + status: zulipAccount.status, + }); + return; + } + + // 2. 检查Redis中是否已有API Key + const existingApiKey = await this.apiKeySecurityService.getApiKey(userId); + + if (existingApiKey.success && existingApiKey.apiKey) { + this.logger.log('Redis中已有API Key缓存,直接创建Zulip客户端', { + operation: 'initializeZulipClientForUser', + userId, + zulipEmail: zulipAccount.zulipEmail, + }); + + // 创建Zulip客户端 + await this.createZulipClientWithApiKey( + userId, + zulipAccount.zulipEmail, + existingApiKey.apiKey + ); + return; + } + + // 3. Redis中没有API Key,记录警告 + // 注意:由于登录时没有用户密码,无法重新生成API Key + // API Key应该在用户注册时存储到Redis,如果丢失需要用户重新绑定Zulip账号 + this.logger.warn('Redis中没有用户的Zulip API Key缓存,无法创建Zulip客户端', { + operation: 'initializeZulipClientForUser', + userId, + zulipEmail: zulipAccount.zulipEmail, + hint: '用户可能需要重新绑定Zulip账号', + }); + + } catch (error) { + const err = error as Error; + this.logger.error('初始化用户Zulip客户端失败', { + operation: 'initializeZulipClientForUser', + userId, + error: err.message, + }); + // 不抛出异常,允许用户继续登录(只是没有Zulip功能) + } + } + + /** + * 使用API Key创建Zulip客户端 + * + * @param userId 用户ID + * @param zulipEmail Zulip邮箱 + * @param apiKey API Key + */ + private async createZulipClientWithApiKey( + userId: string, + zulipEmail: string, + apiKey: string + ): Promise { + try { + const clientInstance = await this.zulipClientPool.createUserClient(userId, { + username: zulipEmail, + apiKey: apiKey, + realm: process.env.ZULIP_SERVER_URL || 'https://zulip.xinghangee.icu/', + }); + + this.logger.log('Zulip客户端创建成功', { + operation: 'createZulipClientWithApiKey', + userId, + zulipEmail, + queueId: clientInstance.queueId, + }); + } catch (error) { + const err = error as Error; + this.logger.error('创建Zulip客户端失败', { + operation: 'createZulipClientWithApiKey', + userId, + zulipEmail, + error: err.message, + }); + throw error; + } + } + private async validateGameToken(token: string) { try { const payload = await this.loginCoreService.verifyToken(token, 'access'); @@ -441,20 +563,18 @@ export class ChatService { private async createUserSession(socketId: string, userInfo: any) { const sessionId = randomUUID(); + + // 尝试获取已创建的Zulip客户端的队列ID let zulipQueueId = `queue_${sessionId}`; - - // 尝试创建Zulip客户端 - if (userInfo.zulipApiKey) { - try { - const clientInstance = await this.zulipClientPool.createUserClient(userInfo.userId, { - username: userInfo.zulipEmail || userInfo.email, - apiKey: userInfo.zulipApiKey, - realm: process.env.ZULIP_SERVER_URL || 'https://zulip.xinghangee.icu/', - }); - if (clientInstance.queueId) zulipQueueId = clientInstance.queueId; - } catch (e) { - this.logger.warn('Zulip客户端创建失败', { error: (e as Error).message }); + try { + const existingClient = await this.zulipClientPool.getUserClient(userInfo.userId); + if (existingClient?.queueId) { + zulipQueueId = existingClient.queueId; } + } catch (e) { + this.logger.debug('获取Zulip客户端队列ID失败,使用默认值', { + error: (e as Error).message + }); } const session = await this.sessionService.createSession( From 7cac8ad8a59b4b9c463c1706c40bc43b20dd51f4 Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Mon, 19 Jan 2026 18:35:45 +0800 Subject: [PATCH 5/5] =?UTF-8?q?docs(ai-reading):=20=E5=AE=8C=E5=96=84AI?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A3=80=E6=9F=A5=E8=A7=84=E8=8C=83=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 范围: docs/ai-reading/ 涉及文件: - README.md - step7-code-commit.md 主要改进: - 增强执行前强制性检查要求,添加明确的检查点 - 完善Step 0的执行流程和验证机制 - 强化代码提交原则,明确提交所有Git变更的规范 - 优化文档结构,提升可读性和执行指导性 - 添加更清晰的警告信息和错误示例 --- docs/ai-reading/README.md | 143 +++++++++++++++++++++++++-- docs/ai-reading/step7-code-commit.md | 56 ++++++++++- 2 files changed, 186 insertions(+), 13 deletions(-) diff --git a/docs/ai-reading/README.md b/docs/ai-reading/README.md index a2ff2b1..0527379 100644 --- a/docs/ai-reading/README.md +++ b/docs/ai-reading/README.md @@ -1,9 +1,17 @@ # AI Code Inspection Guide - Whale Town Game Server -## 🎯 Pre-execution Setup +## ⚠️ 🚨 CRITICAL: MANDATORY PRE-EXECUTION REQUIREMENTS 🚨 ⚠️ -### 🚀 User Information Setup -**Before starting any inspection steps, run the user information script:** +**� AI MUST READ THIS SECTION FIRST - EXECUTION WITHOUT COMPLETING THESE STEPS IS STRICTLY FORBIDDEN 🔴** + +**⛔ STOP! Before executing ANY step (including Step 1), you MUST complete ALL items in this section! ⛔** + +--- + +## 🎯 Pre-execution Setup (MANDATORY - CANNOT BE SKIPPED) + +### 🚀 User Information Setup (STEP 0 - MUST EXECUTE FIRST) +**⚠️ CRITICAL: Before starting any inspection steps (including Step 1), MUST run the user information script:** ```bash # Enter AI-reading directory @@ -13,6 +21,13 @@ cd docs/ai-reading node tools/setup-user-info.js ``` +**🚨 VERIFICATION CHECKPOINT:** +- [ ] Have you executed `node tools/setup-user-info.js`? +- [ ] Does `docs/ai-reading/me.config.json` file exist? +- [ ] Have you read and confirmed the user's date and name from the config file? + +**⛔ IF ANY CHECKBOX ABOVE IS UNCHECKED, YOU CANNOT PROCEED TO STEP 1! ⛔** + #### Script Functions - Automatically get current date (YYYY-MM-DD format) - Check if config file exists or date matches @@ -43,13 +58,49 @@ const userName = config.name; // e.g.: "John" const modifyRecord = `- ${userDate}: Code standard optimization - Clean unused imports (Modified by: ${userName})`; ``` -### 🏗️ Project Characteristics +### 🏗️ Project Characteristics (MUST UNDERSTAND BEFORE STEP 1) +**⚠️ AI MUST read and understand these characteristics BEFORE executing Step 1:** + This project is a **NestJS Game Server** with the following features: - **Dual-mode Architecture**: Supports both database and memory modes - **Real-time Communication**: WebSocket-based real-time bidirectional communication - **Property Testing**: Admin modules use fast-check for randomized testing - **Layered Architecture**: Core layer (technical implementation) + Business layer (business logic) +**🚨 VERIFICATION CHECKPOINT:** +- [ ] Have you read and understood the project is a NestJS Game Server? +- [ ] Have you understood the dual-mode architecture? +- [ ] Have you understood the WebSocket real-time communication feature? +- [ ] Have you understood the property testing requirements? +- [ ] Have you understood the layered architecture? + +**⛔ IF ANY CHECKBOX ABOVE IS UNCHECKED, YOU CANNOT PROCEED TO STEP 1! ⛔** + +--- + +## 🚨 FINAL PRE-EXECUTION CHECKLIST (MUST COMPLETE BEFORE STEP 1) 🚨 + +**Before proceeding to Step 1, AI MUST confirm ALL of the following:** + +### ✅ Mandatory Completion Checklist: +- [ ] ✅ Executed `node tools/setup-user-info.js` script +- [ ] ✅ Confirmed `me.config.json` file exists and contains valid date and name +- [ ] ✅ Read and stored user's date from config file +- [ ] ✅ Read and stored user's name from config file +- [ ] ✅ Understood this is a NestJS Game Server project +- [ ] ✅ Understood the dual-mode architecture (database + memory) +- [ ] ✅ Understood the WebSocket real-time communication feature +- [ ] ✅ Understood the property testing requirements +- [ ] ✅ Understood the layered architecture (Core + Business) +- [ ] ✅ Read and understood the execution principles below + +### 🔴 CRITICAL RULE: +**IF ANY ITEM IN THE CHECKLIST ABOVE IS NOT COMPLETED, AI IS ABSOLUTELY FORBIDDEN FROM EXECUTING STEP 1 OR ANY OTHER STEPS!** + +**AI MUST explicitly confirm completion of ALL checklist items before proceeding to Step 1!** + +--- + ## 🔄 Execution Principles ### 🚨 Mid-step Start Requirements (Important) @@ -95,9 +146,19 @@ Start Executing Specified Step ``` User Requests Code Inspection ↓ -Collect User Info (date, name) +🚨 MANDATORY: Execute node tools/setup-user-info.js ↓ -Identify Project Characteristics +🚨 MANDATORY: Verify me.config.json exists + ↓ +🚨 MANDATORY: Read and Store User Info (date, name) + ↓ +🚨 MANDATORY: Understand Project Characteristics + ↓ +🚨 MANDATORY: Complete Pre-execution Checklist + ↓ +🚨 MANDATORY: Explicitly Confirm ALL Checklist Items Completed + ↓ +⛔ CHECKPOINT: Cannot proceed without completing above steps ⛔ ↓ Execute Step 1 → Provide Report → Wait for Confirmation ↓ @@ -132,7 +193,18 @@ Execute Step 7 → Provide Report → Wait for Confirmation ## 📚 Step Execution Guide +**🚨 REMINDER: Before executing Step 1, ensure you have completed ALL items in the "FINAL PRE-EXECUTION CHECKLIST" above! 🚨** + ### Step 1: Naming Convention Check +**⚠️ BEFORE STARTING STEP 1, AI MUST:** +1. ✅ Confirm `node tools/setup-user-info.js` has been executed +2. ✅ Confirm user date and name have been read from `me.config.json` +3. ✅ Confirm project characteristics have been understood +4. ✅ Explicitly state: "Pre-execution checklist completed, now starting Step 1" + +**ONLY AFTER EXPLICIT CONFIRMATION CAN STEP 1 BEGIN!** + +--- **Read when executing:** `step1-naming-convention.md` **Focus on:** Folder structure flattening, game server special file types **After completion:** Provide inspection report, wait for user confirmation @@ -203,8 +275,9 @@ Execute Step 7 → Provide Report → Wait for Confirmation ### Step 7: Code Commit **Read when executing:** `step7-code-commit.md` -**Focus on:** Git change verification, modification record consistency check, standardized commit process -**After completion:** Provide inspection report, wait for user confirmation +**Focus on:** Git change detection, branch management, commit message standards, merge document generation +**🚨 Important Principle:** Regardless of when or what type of changes were made, if Git detects changes, provide commit service +**After completion:** Provide commit report, generate merge document ## 📋 Unified Report Template @@ -394,4 +467,56 @@ When issues are discovered and modifications made in any step, must follow this --- -**Before starting execution, please first run `node tools/setup-user-info.js` to set user information!** \ No newline at end of file +## 🔴 FINAL WARNING 🔴 + +**⚠️ AI EXECUTION REQUIREMENTS SUMMARY:** + +1. **STEP 0 (MANDATORY)**: Execute `node tools/setup-user-info.js` +2. **STEP 0.1 (MANDATORY)**: Verify `me.config.json` exists and read user info +3. **STEP 0.2 (MANDATORY)**: Understand project characteristics +4. **STEP 0.3 (MANDATORY)**: Complete and explicitly confirm pre-execution checklist +5. **ONLY THEN**: Begin Step 1 + +**🚨 VIOLATION CONSEQUENCES:** +- If AI skips Step 0 and directly executes Step 1, user MUST stop AI and require restart +- If AI does not explicitly confirm checklist completion, user MUST stop AI and require confirmation +- If AI does not read user info from config file, all subsequent steps are INVALID + +**✅ CORRECT EXECUTION START:** +``` +AI: "I will now begin the code inspection process. + +Step 0 - Pre-execution Setup: +1. ✅ Executing user information setup script... + Command: cd docs/ai-reading && node tools/setup-user-info.js + +2. ✅ Verifying me.config.json exists... + File found: docs/ai-reading/me.config.json + +3. ✅ Reading user information... + User Date: 2026-01-19 + User Name: [Name from config] + +4. ✅ Understanding project characteristics... + - NestJS Game Server ✓ + - Dual-mode Architecture ✓ + - WebSocket Communication ✓ + - Property Testing ✓ + - Layered Architecture ✓ + +5. ✅ Pre-execution checklist completed! + +All mandatory pre-execution requirements have been satisfied. +Now proceeding to Step 1: Naming Convention Check..." +``` + +**⛔ INCORRECT EXECUTION START (FORBIDDEN):** +``` +AI: "I will start with Step 1: Naming Convention Check..." ❌ WRONG! +AI: "Let me check the naming conventions..." ❌ WRONG! +AI: "Starting code inspection..." ❌ WRONG! +``` + +--- + +**🎯 Remember: Step 0 is NOT optional - it is MANDATORY before ANY other step!** \ No newline at end of file diff --git a/docs/ai-reading/step7-code-commit.md b/docs/ai-reading/step7-code-commit.md index 7bd0e75..d0ab8eb 100644 --- a/docs/ai-reading/step7-code-commit.md +++ b/docs/ai-reading/step7-code-commit.md @@ -19,10 +19,36 @@ ## 🎯 检查目标 完成代码修改后的规范化提交流程,确保代码变更记录清晰、分支管理规范、提交信息符合项目标准。 +## 🚨 重要原则:提交所有变更 + +### 核心原则 +**无论变更是何时产生的、是什么类型的,只要 Git 检测到有变更,就应该帮助用户提交!** + +### 常见误区 +❌ **错误想法**:"这些变更不是本次代码检查产生的,所以不需要提交" +✅ **正确做法**:检查所有 Git 变更,分析变更类型,询问用户要提交哪些文件,然后用合适的方式提交 + +### 执行流程 +1. **检查 Git 状态**:`git status` 查看所有变更文件 +2. **分析变更内容**:`git diff` 查看每个文件的具体变更 +3. **分类变更类型**:判断是功能新增、Bug修复、代码优化等 +4. **询问用户意图**:确认要提交哪些文件、提交到哪个仓库 +5. **选择提交策略**:根据变更类型选择合适的分支命名和提交信息 +6. **执行提交操作**:创建分支、暂存文件、提交、推送 + +### 变更来源不重要 +变更可能来自: +- 本次代码检查的修改 ✓ +- 之前的功能开发 ✓ +- 其他时间的代码调整 ✓ +- 任何其他修改 ✓ + +**关键是:只要有变更,就应该提供提交服务!** + ## 📋 执行前置条件 -- 已完成前6个步骤的代码检查和修改 -- 所有修改的文件已更新修改记录和版本信息 -- 代码能够正常运行且通过测试 +- Git 工作区有变更文件(通过 `git status` 检测) +- 代码能够正常运行且通过测试(如适用) +- 用户明确要提交这些变更 ## 🚨 协作规范和范围控制 @@ -69,8 +95,30 @@ git diff git diff --cached ``` +### 🚨 重要:不要预判变更来源 +**AI 必须检查所有 Git 变更,不要因为变更不是"本次检查产生的"就忽略!** + +#### 错误示例 +``` +❌ AI: "检测到 chat.gateway.ts 有变更,但这是功能新增,不是代码规范检查产生的,所以不需要提交。" +``` + +#### 正确示例 +``` +✅ AI: "检测到以下文件有变更: +1. chat.gateway.ts - 功能新增(添加地图切换功能) +2. auth/login.service.ts - 代码优化 +3. chat/chat.service.ts - Bug修复 + +请问您要提交哪些文件?我可以帮您: +- 全部提交(可以分类提交不同类型的变更) +- 只提交部分文件 +- 按模块分别提交" +``` + ### 2. 文件修改记录校验 -**重要**:检查每个修改文件的头部信息是否与实际修改内容一致 +**注意**:如果变更不是本次代码检查产生的,文件头部可能没有更新修改记录,这是正常的。 +只需要检查变更内容,生成准确的提交信息即可。 #### 校验内容包括: - **修改记录**:最新的修改记录是否准确描述了本次变更