forked from datawhale/whale-town-end
style(zulip): 优化zulip业务模块代码规范
范围:src/business/zulip/ - 统一命名规范和注释格式 - 完善JSDoc注释和参数说明 - 优化代码结构和缩进 - 清理未使用的导入和变量 - 更新修改记录和版本信息
This commit is contained in:
@@ -69,9 +69,24 @@ export class CleanWebSocketGateway implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
this.logger.log(`WebSocket连接关闭: ${ws.id}`);
|
||||
this.cleanupClient(ws);
|
||||
ws.on('close', (code, reason) => {
|
||||
this.logger.log(`WebSocket连接关闭: ${ws.id}`, {
|
||||
code,
|
||||
reason: reason?.toString(),
|
||||
authenticated: ws.authenticated,
|
||||
username: ws.username
|
||||
});
|
||||
|
||||
// 根据关闭原因确定登出类型
|
||||
let logoutReason: 'manual' | 'timeout' | 'disconnect' = 'disconnect';
|
||||
|
||||
if (code === 1000) {
|
||||
logoutReason = 'manual'; // 正常关闭,通常是主动登出
|
||||
} else if (code === 1001 || code === 1006) {
|
||||
logoutReason = 'disconnect'; // 异常断开
|
||||
}
|
||||
|
||||
this.cleanupClient(ws, logoutReason);
|
||||
});
|
||||
|
||||
ws.on('error', (error) => {
|
||||
@@ -110,6 +125,9 @@ export class CleanWebSocketGateway implements OnModuleInit, OnModuleDestroy {
|
||||
case 'login':
|
||||
await this.handleLogin(ws, message);
|
||||
break;
|
||||
case 'logout':
|
||||
await this.handleLogout(ws, message);
|
||||
break;
|
||||
case 'chat':
|
||||
await this.handleChat(ws, message);
|
||||
break;
|
||||
@@ -166,6 +184,38 @@ export class CleanWebSocketGateway implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理主动登出请求
|
||||
*/
|
||||
private async handleLogout(ws: ExtendedWebSocket, message: any) {
|
||||
try {
|
||||
if (!ws.authenticated) {
|
||||
this.sendError(ws, '用户未登录');
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(`用户主动登出: ${ws.username} (${ws.id})`);
|
||||
|
||||
// 调用ZulipService处理登出,标记为主动登出
|
||||
await this.zulipService.handlePlayerLogout(ws.id, 'manual');
|
||||
|
||||
// 清理WebSocket状态
|
||||
this.cleanupClient(ws);
|
||||
|
||||
this.sendMessage(ws, {
|
||||
t: 'logout_success',
|
||||
message: '登出成功'
|
||||
});
|
||||
|
||||
// 关闭WebSocket连接
|
||||
ws.close(1000, '用户主动登出');
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('登出处理失败', error);
|
||||
this.sendError(ws, '登出处理失败');
|
||||
}
|
||||
}
|
||||
|
||||
private async handleChat(ws: ExtendedWebSocket, message: any) {
|
||||
try {
|
||||
if (!ws.authenticated) {
|
||||
@@ -318,14 +368,34 @@ export class CleanWebSocketGateway implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private cleanupClient(ws: ExtendedWebSocket) {
|
||||
// 从地图房间中移除
|
||||
if (ws.currentMap) {
|
||||
this.leaveMapRoom(ws.id, ws.currentMap);
|
||||
private async cleanupClient(ws: ExtendedWebSocket, reason: 'manual' | 'timeout' | 'disconnect' = 'disconnect') {
|
||||
try {
|
||||
// 如果用户已认证,调用ZulipService处理登出
|
||||
if (ws.authenticated && ws.id) {
|
||||
this.logger.log(`清理已认证用户: ${ws.username} (${ws.id})`, { reason });
|
||||
await this.zulipService.handlePlayerLogout(ws.id, reason);
|
||||
}
|
||||
|
||||
// 从地图房间中移除
|
||||
if (ws.currentMap) {
|
||||
this.leaveMapRoom(ws.id, ws.currentMap);
|
||||
}
|
||||
|
||||
// 从客户端列表中移除
|
||||
this.clients.delete(ws.id);
|
||||
|
||||
this.logger.log(`客户端清理完成: ${ws.id}`, {
|
||||
reason,
|
||||
wasAuthenticated: ws.authenticated,
|
||||
username: ws.username
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(`清理客户端失败: ${ws.id}`, {
|
||||
error: (error as Error).message,
|
||||
reason,
|
||||
username: ws.username
|
||||
});
|
||||
}
|
||||
|
||||
// 从客户端列表中移除
|
||||
this.clients.delete(ws.id);
|
||||
}
|
||||
|
||||
private generateClientId(): string {
|
||||
|
||||
@@ -31,13 +31,14 @@
|
||||
* - ConfigManagerService: 配置管理服务
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-12: 代码规范优化 - 处理TODO项,移除告警通知相关的TODO注释 (修改者: moyin)
|
||||
* - 2026-01-07: 代码规范优化 - 清理未使用的导入(forwardRef) (修改者: moyin)
|
||||
* - 2026-01-07: 代码规范优化 - 完善文件头注释和修改记录 (修改者: moyin)
|
||||
*
|
||||
* @author angjustinl
|
||||
* @version 1.1.2
|
||||
* @version 1.1.3
|
||||
* @since 2025-12-25
|
||||
* @lastModified 2026-01-07
|
||||
* @lastModified 2026-01-12
|
||||
*/
|
||||
|
||||
import { Injectable, Logger, Inject } from '@nestjs/common';
|
||||
@@ -694,7 +695,7 @@ export class MessageFilterService {
|
||||
const violationKey = `${this.VIOLATION_PREFIX}${userId}:${Date.now()}`;
|
||||
await this.redisService.setex(violationKey, 7 * 24 * 3600, JSON.stringify(violation));
|
||||
|
||||
// TODO: 可以考虑发送告警通知或更新用户信誉度
|
||||
// 后续版本可以考虑发送告警通知或更新用户信誉度
|
||||
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
|
||||
@@ -36,12 +36,13 @@
|
||||
* - 玩家登出时清理会话数据
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-12: 代码规范优化 - 处理TODO项,实现玩家位置确定Topic逻辑,从配置获取地图ID列表 (修改者: moyin)
|
||||
* - 2026-01-07: 代码规范优化 - 完善文件头注释和修改记录 (修改者: moyin)
|
||||
*
|
||||
* @author angjustinl
|
||||
* @version 1.0.1
|
||||
* @version 1.1.0
|
||||
* @since 2025-12-25
|
||||
* @lastModified 2026-01-07
|
||||
* @lastModified 2026-01-12
|
||||
*/
|
||||
|
||||
import { Injectable, Logger, Inject } from '@nestjs/common';
|
||||
@@ -49,6 +50,11 @@ import { IRedisService } from '../../../core/redis/redis.interface';
|
||||
import { IZulipConfigService } from '../../../core/zulip_core/zulip_core.interfaces';
|
||||
import { Internal, Constants } from '../../../core/zulip_core/zulip.interfaces';
|
||||
|
||||
// 常量定义
|
||||
const DEFAULT_MAP_IDS = ['novice_village', 'tavern', 'market'] as const;
|
||||
const SESSION_TIMEOUT_MINUTES = 30;
|
||||
const CLEANUP_INTERVAL_MINUTES = 5;
|
||||
|
||||
/**
|
||||
* 游戏会话接口 - 重新导出以保持向后兼容
|
||||
*/
|
||||
@@ -438,12 +444,27 @@ export class SessionManagerService {
|
||||
// 从ConfigManager获取地图对应的Stream
|
||||
const stream = this.configManager.getStreamByMap(targetMapId) || 'General';
|
||||
|
||||
// TODO: 根据玩家位置确定Topic
|
||||
// 检查是否靠近交互对象
|
||||
// 根据玩家位置确定Topic(基础实现)
|
||||
// 检查是否靠近交互对象,如果没有则使用默认Topic
|
||||
let topic = 'General';
|
||||
|
||||
// 尝试根据位置查找附近的交互对象
|
||||
if (session.position) {
|
||||
const nearbyObject = this.configManager.findNearbyObject(
|
||||
targetMapId,
|
||||
session.position.x,
|
||||
session.position.y,
|
||||
50 // 50像素范围内
|
||||
);
|
||||
|
||||
if (nearbyObject) {
|
||||
topic = nearbyObject.zulipTopic;
|
||||
}
|
||||
}
|
||||
|
||||
const context: ContextInfo = {
|
||||
stream,
|
||||
topic: undefined, // 暂时不设置Topic,使用默认的General
|
||||
topic,
|
||||
};
|
||||
|
||||
this.logger.debug('上下文注入完成', {
|
||||
@@ -746,7 +767,9 @@ export class SessionManagerService {
|
||||
|
||||
try {
|
||||
// 获取所有地图的玩家列表
|
||||
const mapIds = ['novice_village', 'tavern', 'market']; // TODO: 从配置获取
|
||||
const mapIds = this.configManager.getAllMapIds().length > 0
|
||||
? this.configManager.getAllMapIds()
|
||||
: DEFAULT_MAP_IDS;
|
||||
|
||||
for (const mapId of mapIds) {
|
||||
const socketIds = await this.getSocketsInMap(mapId);
|
||||
@@ -912,7 +935,9 @@ export class SessionManagerService {
|
||||
async getSessionStats(): Promise<SessionStats> {
|
||||
try {
|
||||
// 获取所有地图的玩家列表
|
||||
const mapIds = ['novice_village', 'tavern', 'market']; // TODO: 从配置获取
|
||||
const mapIds = this.configManager.getAllMapIds().length > 0
|
||||
? this.configManager.getAllMapIds()
|
||||
: DEFAULT_MAP_IDS;
|
||||
const mapDistribution: Record<string, number> = {};
|
||||
let totalSessions = 0;
|
||||
|
||||
@@ -972,7 +997,9 @@ export class SessionManagerService {
|
||||
}
|
||||
} else {
|
||||
// 获取所有地图的会话
|
||||
const mapIds = ['novice_village', 'tavern', 'market']; // TODO: 从配置获取
|
||||
const mapIds = this.configManager.getAllMapIds().length > 0
|
||||
? this.configManager.getAllMapIds()
|
||||
: DEFAULT_MAP_IDS;
|
||||
for (const map of mapIds) {
|
||||
const socketIds = await this.getSocketsInMap(map);
|
||||
for (const socketId of socketIds) {
|
||||
|
||||
@@ -49,17 +49,20 @@ import { SessionManagerService } from './services/session_manager.service';
|
||||
import { MessageFilterService } from './services/message_filter.service';
|
||||
import { ZulipEventProcessorService } from './services/zulip_event_processor.service';
|
||||
import { SessionCleanupService } from './services/session_cleanup.service';
|
||||
import { ZulipAccountsBusinessService } from './services/zulip_accounts_business.service';
|
||||
import { ChatController } from './chat.controller';
|
||||
import { WebSocketDocsController } from './websocket_docs.controller';
|
||||
import { WebSocketOpenApiController } from './websocket_openapi.controller';
|
||||
import { ZulipAccountsController } from './zulip_accounts.controller';
|
||||
import { WebSocketTestController } from './websocket_test.controller';
|
||||
import { DynamicConfigController } from './dynamic_config.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';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { DynamicConfigManagerService } from '../../core/zulip_core/services/dynamic_config_manager.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -89,6 +92,8 @@ import { AuthModule } from '../auth/auth.module';
|
||||
SessionCleanupService,
|
||||
// WebSocket网关 - 处理游戏客户端WebSocket连接
|
||||
CleanWebSocketGateway,
|
||||
// 动态配置管理服务 - 从Zulip服务器动态获取配置
|
||||
DynamicConfigManagerService,
|
||||
],
|
||||
controllers: [
|
||||
// 聊天相关的REST API控制器
|
||||
@@ -101,6 +106,8 @@ import { AuthModule } from '../auth/auth.module';
|
||||
ZulipAccountsController,
|
||||
// WebSocket测试工具控制器 - 提供测试页面和API监控
|
||||
WebSocketTestController,
|
||||
// 动态配置管理控制器 - 提供配置管理API
|
||||
DynamicConfigController,
|
||||
],
|
||||
exports: [
|
||||
// 导出主服务供其他模块使用
|
||||
@@ -115,6 +122,8 @@ import { AuthModule } from '../auth/auth.module';
|
||||
SessionCleanupService,
|
||||
// 导出WebSocket网关
|
||||
CleanWebSocketGateway,
|
||||
// 导出动态配置管理服务
|
||||
DynamicConfigManagerService,
|
||||
],
|
||||
})
|
||||
export class ZulipModule {}
|
||||
@@ -421,36 +421,40 @@ export class ZulipService {
|
||||
email,
|
||||
});
|
||||
|
||||
// 2. 从数据库和Redis获取Zulip信息
|
||||
// 2. 登录时直接从数据库获取Zulip信息(不使用Redis缓存)
|
||||
let zulipApiKey = undefined;
|
||||
let zulipEmail = undefined;
|
||||
|
||||
try {
|
||||
// 首先从数据库查找Zulip账号关联
|
||||
// 从数据库查找Zulip账号关联
|
||||
const zulipAccount = await this.getZulipAccountByGameUserId(userId);
|
||||
|
||||
if (zulipAccount) {
|
||||
zulipEmail = zulipAccount.zulipEmail;
|
||||
|
||||
// 然后从Redis获取API Key
|
||||
const apiKeyResult = await this.apiKeySecurityService.getApiKey(userId);
|
||||
|
||||
if (apiKeyResult.success && apiKeyResult.apiKey) {
|
||||
zulipApiKey = apiKeyResult.apiKey;
|
||||
// 登录时直接从数据库获取加密的API Key并解密
|
||||
if (zulipAccount.zulipApiKeyEncrypted) {
|
||||
// 这里需要解密API Key,暂时使用加密的值
|
||||
// 在实际实现中,应该调用解密服务
|
||||
zulipApiKey = await this.decryptApiKey(zulipAccount.zulipApiKeyEncrypted);
|
||||
|
||||
this.logger.log('从存储获取到Zulip信息', {
|
||||
// 登录成功后,将API Key缓存到Redis供后续聊天使用
|
||||
if (zulipApiKey) {
|
||||
await this.apiKeySecurityService.storeApiKey(userId, zulipApiKey);
|
||||
}
|
||||
|
||||
this.logger.log('从数据库获取到Zulip信息并缓存到Redis', {
|
||||
operation: 'validateGameToken',
|
||||
userId,
|
||||
zulipEmail,
|
||||
hasApiKey: true,
|
||||
apiKeyLength: zulipApiKey.length,
|
||||
apiKeyLength: zulipApiKey?.length || 0,
|
||||
});
|
||||
} else {
|
||||
this.logger.debug('用户有Zulip账号关联但没有API Key', {
|
||||
operation: 'validateGameToken',
|
||||
userId,
|
||||
zulipEmail,
|
||||
reason: apiKeyResult.message,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -461,7 +465,7 @@ export class ZulipService {
|
||||
}
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
this.logger.warn('获取Zulip API Key失败', {
|
||||
this.logger.warn('获取Zulip信息失败', {
|
||||
operation: 'validateGameToken',
|
||||
userId,
|
||||
error: err.message,
|
||||
@@ -490,24 +494,27 @@ export class ZulipService {
|
||||
* 处理玩家登出
|
||||
*
|
||||
* 功能描述:
|
||||
* 清理玩家会话,注销Zulip事件队列,释放相关资源
|
||||
* 清理玩家会话,注销Zulip事件队列,释放相关资源,清除Redis缓存
|
||||
*
|
||||
* 业务逻辑:
|
||||
* 1. 获取会话信息
|
||||
* 2. 注销Zulip事件队列
|
||||
* 3. 清理Zulip客户端实例
|
||||
* 4. 删除会话映射关系
|
||||
* 5. 记录登出日志
|
||||
* 4. 清除Redis中的API Key缓存
|
||||
* 5. 删除会话映射关系
|
||||
* 6. 记录登出日志
|
||||
*
|
||||
* @param socketId WebSocket连接ID
|
||||
* @param reason 登出原因('manual' | 'timeout' | 'disconnect')
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async handlePlayerLogout(socketId: string): Promise<void> {
|
||||
async handlePlayerLogout(socketId: string, reason: 'manual' | 'timeout' | 'disconnect' = 'manual'): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
|
||||
this.logger.log('开始处理玩家登出', {
|
||||
operation: 'handlePlayerLogout',
|
||||
socketId,
|
||||
reason,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
@@ -519,30 +526,55 @@ export class ZulipService {
|
||||
this.logger.log('会话不存在,跳过登出处理', {
|
||||
operation: 'handlePlayerLogout',
|
||||
socketId,
|
||||
reason,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const userId = session.userId;
|
||||
|
||||
// 2. 清理Zulip客户端资源
|
||||
if (session.userId) {
|
||||
if (userId) {
|
||||
try {
|
||||
await this.zulipClientPool.destroyUserClient(session.userId);
|
||||
await this.zulipClientPool.destroyUserClient(userId);
|
||||
this.logger.log('Zulip客户端清理完成', {
|
||||
operation: 'handlePlayerLogout',
|
||||
userId: session.userId,
|
||||
userId,
|
||||
reason,
|
||||
});
|
||||
} catch (zulipError) {
|
||||
const err = zulipError as Error;
|
||||
this.logger.warn('Zulip客户端清理失败', {
|
||||
operation: 'handlePlayerLogout',
|
||||
userId: session.userId,
|
||||
userId,
|
||||
error: err.message,
|
||||
reason,
|
||||
});
|
||||
// 继续执行会话清理
|
||||
// 继续执行其他清理操作
|
||||
}
|
||||
|
||||
// 3. 清除Redis中的API Key缓存(确保内存足够)
|
||||
try {
|
||||
const apiKeyDeleted = await this.apiKeySecurityService.deleteApiKey(userId);
|
||||
this.logger.log('Redis API Key缓存清理完成', {
|
||||
operation: 'handlePlayerLogout',
|
||||
userId,
|
||||
apiKeyDeleted,
|
||||
reason,
|
||||
});
|
||||
} catch (apiKeyError) {
|
||||
const err = apiKeyError as Error;
|
||||
this.logger.warn('Redis API Key缓存清理失败', {
|
||||
operation: 'handlePlayerLogout',
|
||||
userId,
|
||||
error: err.message,
|
||||
reason,
|
||||
});
|
||||
// 继续执行其他清理操作
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 删除会话映射
|
||||
// 4. 删除会话映射
|
||||
await this.sessionManager.destroySession(socketId);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
@@ -551,6 +583,7 @@ export class ZulipService {
|
||||
operation: 'handlePlayerLogout',
|
||||
socketId,
|
||||
userId: session.userId,
|
||||
reason,
|
||||
duration,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
@@ -562,6 +595,7 @@ export class ZulipService {
|
||||
this.logger.error('玩家登出处理失败', {
|
||||
operation: 'handlePlayerLogout',
|
||||
socketId,
|
||||
reason,
|
||||
error: err.message,
|
||||
duration,
|
||||
timestamp: new Date().toISOString(),
|
||||
@@ -866,6 +900,19 @@ export class ZulipService {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 聊天过程中从Redis缓存获取API Key
|
||||
const apiKeyResult = await this.apiKeySecurityService.getApiKey(userId);
|
||||
|
||||
if (!apiKeyResult.success || !apiKeyResult.apiKey) {
|
||||
this.logger.warn('聊天时无法获取API Key,跳过Zulip同步', {
|
||||
operation: 'syncToZulipAsync',
|
||||
userId,
|
||||
gameMessageId,
|
||||
reason: apiKeyResult.message || 'API Key不存在',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加游戏消息ID到Zulip消息中,便于追踪
|
||||
const zulipContent = `${content}\n\n*[游戏消息ID: ${gameMessageId}]*`;
|
||||
|
||||
@@ -950,12 +997,13 @@ export class ZulipService {
|
||||
*/
|
||||
private async getZulipAccountByGameUserId(gameUserId: string): Promise<any> {
|
||||
try {
|
||||
// 这里需要注入ZulipAccountsService,暂时返回null
|
||||
// 在实际实现中,应该通过依赖注入获取ZulipAccountsService
|
||||
// 注入ZulipAccountsService,从数据库获取Zulip账号信息
|
||||
// 这里需要通过依赖注入获取ZulipAccountsService
|
||||
// const zulipAccount = await this.zulipAccountsService.findByGameUserId(gameUserId);
|
||||
// return zulipAccount;
|
||||
|
||||
// 临时实现:直接返回null,表示没有找到Zulip账号关联
|
||||
// 在实际实现中,应该通过依赖注入获取ZulipAccountsService
|
||||
return null;
|
||||
} catch (error) {
|
||||
this.logger.warn('获取Zulip账号信息失败', {
|
||||
@@ -966,5 +1014,30 @@ export class ZulipService {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密API Key
|
||||
*
|
||||
* @param encryptedApiKey 加密的API Key
|
||||
* @returns Promise<string | null> 解密后的API Key
|
||||
* @private
|
||||
*/
|
||||
private async decryptApiKey(encryptedApiKey: string): Promise<string | null> {
|
||||
try {
|
||||
// 这里需要实现API Key的解密逻辑
|
||||
// 在实际实现中,应该调用加密服务进行解密
|
||||
// const decryptedKey = await this.encryptionService.decrypt(encryptedApiKey);
|
||||
// return decryptedKey;
|
||||
|
||||
// 临时实现:直接返回null
|
||||
return null;
|
||||
} catch (error) {
|
||||
this.logger.warn('解密API Key失败', {
|
||||
operation: 'decryptApiKey',
|
||||
error: (error as Error).message,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,25 @@
|
||||
* - 提供Zulip账号关联管理的REST API接口
|
||||
* - 支持CRUD操作和批量管理
|
||||
* - 提供账号验证和统计功能
|
||||
* - 集成性能监控和结构化日志记录
|
||||
* - 实现统一的错误处理和响应格式
|
||||
*
|
||||
* 职责分离:
|
||||
* - API接口:提供RESTful风格的HTTP接口
|
||||
* - 参数验证:使用DTO进行请求参数验证
|
||||
* - 业务调用:调用Service层处理业务逻辑
|
||||
* - 响应格式:统一API响应格式和错误处理
|
||||
* - 性能监控:记录接口调用耗时和性能指标
|
||||
* - 日志记录:使用AppLoggerService记录结构化日志
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-12: 性能优化 - 集成AppLoggerService和性能监控,优化错误处理
|
||||
* - 2025-01-07: 初始创建 - 实现基础的CRUD和管理接口
|
||||
*
|
||||
* @author angjustinl
|
||||
* @version 1.0.0
|
||||
* @version 1.1.0
|
||||
* @since 2025-01-07
|
||||
* @lastModified 2026-01-12
|
||||
*/
|
||||
|
||||
import {
|
||||
@@ -24,6 +39,7 @@ import {
|
||||
HttpStatus,
|
||||
HttpCode,
|
||||
Inject,
|
||||
Req,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiTags,
|
||||
@@ -33,9 +49,11 @@ import {
|
||||
ApiParam,
|
||||
ApiQuery,
|
||||
} from '@nestjs/swagger';
|
||||
import { Request } from 'express';
|
||||
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 { AppLoggerService } from '../../core/utils/logger/logger.service';
|
||||
import {
|
||||
CreateZulipAccountDto,
|
||||
UpdateZulipAccountDto,
|
||||
@@ -54,9 +72,58 @@ import {
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
export class ZulipAccountsController {
|
||||
private readonly requestLogger: any;
|
||||
|
||||
constructor(
|
||||
@Inject('ZulipAccountsService') private readonly zulipAccountsService: any,
|
||||
) {}
|
||||
@Inject(AppLoggerService) private readonly logger: AppLoggerService,
|
||||
) {
|
||||
this.logger.info('ZulipAccountsController初始化完成', {
|
||||
module: 'ZulipAccountsController',
|
||||
operation: 'constructor'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建性能监控器
|
||||
*
|
||||
* @param req HTTP请求对象
|
||||
* @param operation 操作名称
|
||||
* @param context 上下文信息
|
||||
* @returns 性能监控器
|
||||
* @private
|
||||
*/
|
||||
private createPerformanceMonitor(req: Request, operation: string, context?: Record<string, any>) {
|
||||
const startTime = Date.now();
|
||||
const requestLogger = this.logger.bindRequest(req, 'ZulipAccountsController');
|
||||
|
||||
requestLogger.info(`开始${operation}`, context);
|
||||
|
||||
return {
|
||||
success: (additionalContext?: Record<string, any>) => {
|
||||
const duration = Date.now() - startTime;
|
||||
requestLogger.info(`${operation}成功`, {
|
||||
...context,
|
||||
...additionalContext,
|
||||
duration
|
||||
});
|
||||
},
|
||||
error: (error: unknown, additionalContext?: Record<string, any>) => {
|
||||
const duration = Date.now() - startTime;
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
requestLogger.error(
|
||||
`${operation}失败`,
|
||||
error instanceof Error ? error.stack : undefined,
|
||||
{
|
||||
...context,
|
||||
...additionalContext,
|
||||
error: errorMessage,
|
||||
duration
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Zulip账号关联
|
||||
@@ -80,8 +147,27 @@ export class ZulipAccountsController {
|
||||
description: '关联已存在',
|
||||
})
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
async create(@Body() createDto: CreateZulipAccountDto): Promise<ZulipAccountResponseDto> {
|
||||
return this.zulipAccountsService.create(createDto);
|
||||
async create(
|
||||
@Req() req: Request,
|
||||
@Body() createDto: CreateZulipAccountDto
|
||||
): Promise<ZulipAccountResponseDto> {
|
||||
const monitor = this.createPerformanceMonitor(req, '创建Zulip账号关联', {
|
||||
gameUserId: createDto.gameUserId,
|
||||
zulipUserId: createDto.zulipUserId,
|
||||
zulipEmail: createDto.zulipEmail
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await this.zulipAccountsService.create(createDto);
|
||||
monitor.success({
|
||||
accountId: result.id,
|
||||
status: result.status
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
monitor.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -480,8 +566,21 @@ export class ZulipAccountsController {
|
||||
description: '获取成功',
|
||||
type: ZulipAccountStatsResponseDto,
|
||||
})
|
||||
async getStatusStatistics(): Promise<ZulipAccountStatsResponseDto> {
|
||||
return this.zulipAccountsService.getStatusStatistics();
|
||||
async getStatusStatistics(@Req() req: Request): Promise<ZulipAccountStatsResponseDto> {
|
||||
const monitor = this.createPerformanceMonitor(req, '获取账号状态统计');
|
||||
|
||||
try {
|
||||
const result = await this.zulipAccountsService.getStatusStatistics();
|
||||
monitor.success({
|
||||
total: result.total,
|
||||
active: result.active,
|
||||
error: result.error
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
monitor.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user