/** * Zulip账号关联业务服务 * * 功能描述: * - 提供Zulip账号关联的完整业务逻辑 * - 管理账号关联的生命周期 * - 处理账号验证和同步 * - 提供统计和监控功能 * - 实现业务异常转换和错误处理 * - 集成缓存机制提升查询性能 * - 支持批量操作和性能监控 * * 职责分离: * - 业务逻辑:处理复杂的业务规则和流程 * - 异常转换:将Repository层异常转换为业务异常 * - DTO转换:实体对象与响应DTO之间的转换 * - 缓存管理:管理热点数据的缓存策略 * - 性能监控:记录操作耗时和性能指标 * - 日志记录:使用AppLoggerService记录结构化日志 * * 最近修改: * - 2026-01-12: 架构优化 - 从core/zulip_core移动到business/zulip,符合架构分层规范 (修改者: moyin) * - 2026-01-12: 代码质量优化 - 清理未使用的导入,移除冗余DTO引用 (修改者: moyin) * * @author angjustinl * @version 2.1.0 * @since 2026-01-12 * @lastModified 2026-01-12 */ import { Injectable, Inject, ConflictException, NotFoundException } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; import { AppLoggerService } from '../../../core/utils/logger/logger.service'; import { CreateZulipAccountDto, ZulipAccountResponseDto, ZulipAccountStatsResponseDto, } from '../../../core/db/zulip_accounts/zulip_accounts.dto'; /** * Zulip账号关联业务服务基类 */ abstract class BaseZulipAccountsBusinessService { protected readonly logger: AppLoggerService; protected readonly moduleName: string; constructor( @Inject(AppLoggerService) logger: AppLoggerService, moduleName: string = 'ZulipAccountsBusinessService' ) { this.logger = logger; this.moduleName = moduleName; } /** * 统一的错误格式化方法 */ protected formatError(error: unknown): string { if (error instanceof Error) { return error.message; } return String(error); } /** * 统一的异常处理方法 */ protected handleServiceError(error: unknown, operation: string, context?: Record): never { const errorMessage = this.formatError(error); this.logger.error(`${operation}失败`, { module: this.moduleName, operation, error: errorMessage, context, timestamp: new Date().toISOString() }, error instanceof Error ? error.stack : undefined); if (error instanceof ConflictException || error instanceof NotFoundException) { throw error; } throw new ConflictException(`${operation}失败,请稍后重试`); } /** * 搜索异常的特殊处理 */ protected handleSearchError(error: unknown, operation: string, context?: Record): any[] { const errorMessage = this.formatError(error); this.logger.warn(`${operation}失败,返回空结果`, { module: this.moduleName, operation, error: errorMessage, context, timestamp: new Date().toISOString() }); return []; } /** * 记录操作成功日志 */ protected logSuccess(operation: string, context?: Record, duration?: number): void { this.logger.info(`${operation}成功`, { module: this.moduleName, operation, context, duration, timestamp: new Date().toISOString() }); } /** * 记录操作开始日志 */ protected logStart(operation: string, context?: Record): void { this.logger.info(`开始${operation}`, { module: this.moduleName, operation, context, timestamp: new Date().toISOString() }); } /** * 创建性能监控器 */ protected createPerformanceMonitor(operation: string, context?: Record) { const startTime = Date.now(); this.logStart(operation, context); return { success: (additionalContext?: Record) => { const duration = Date.now() - startTime; this.logSuccess(operation, { ...context, ...additionalContext }, duration); }, error: (error: unknown, additionalContext?: Record) => { const duration = Date.now() - startTime; this.handleServiceError(error, operation, { ...context, ...additionalContext, duration }); } }; } /** * 解析游戏用户ID为BigInt类型 */ protected parseGameUserId(gameUserId: string): bigint { try { return BigInt(gameUserId); } catch (error) { throw new ConflictException(`无效的游戏用户ID格式: ${gameUserId}`); } } /** * 批量解析ID数组为BigInt类型 */ protected parseIds(ids: string[]): bigint[] { try { return ids.map(id => BigInt(id)); } catch (error) { throw new ConflictException(`无效的ID格式: ${ids.join(', ')}`); } } /** * 解析单个ID为BigInt类型 */ protected parseId(id: string): bigint { try { return BigInt(id); } catch (error) { throw new ConflictException(`无效的ID格式: ${id}`); } } /** * 抽象方法:将实体转换为响应DTO */ protected abstract toResponseDto(entity: any): any; /** * 将实体数组转换为响应DTO数组 */ protected toResponseDtoArray(entities: any[]): any[] { return entities.map(entity => this.toResponseDto(entity)); } /** * 构建列表响应对象 */ protected buildListResponse(entities: any[]): any { const responseAccounts = this.toResponseDtoArray(entities); return { accounts: responseAccounts, total: responseAccounts.length, count: responseAccounts.length, }; } } /** * Zulip账号关联业务服务类 * * 职责: * - 处理Zulip账号关联的业务逻辑 * - 管理账号关联的生命周期和状态 * - 提供业务级别的异常处理和转换 * - 实现缓存策略和性能优化 * * 主要方法: * - create(): 创建Zulip账号关联 * - findByGameUserId(): 根据游戏用户ID查找关联 * - getStatusStatistics(): 获取账号状态统计 * - toResponseDto(): 实体到DTO的转换 * * 使用场景: * - 用户注册时创建Zulip账号关联 * - 查询用户的Zulip账号信息 * - 系统监控和统计分析 * - 账号状态管理和维护 */ @Injectable() export class ZulipAccountsBusinessService extends BaseZulipAccountsBusinessService { // 缓存键前缀 private static readonly CACHE_PREFIX = 'zulip_accounts'; private static readonly CACHE_TTL = 300; // 5分钟缓存 private static readonly STATS_CACHE_TTL = 60; // 统计数据1分钟缓存 constructor( @Inject('ZulipAccountsRepository') private readonly repository: any, @Inject(AppLoggerService) logger: AppLoggerService, @Inject(CACHE_MANAGER) private readonly cacheManager: Cache, ) { super(logger, 'ZulipAccountsBusinessService'); this.logger.info('ZulipAccountsBusinessService初始化完成', { module: 'ZulipAccountsBusinessService', operation: 'constructor', cacheEnabled: !!this.cacheManager }); } /** * 创建Zulip账号关联 * * 功能描述: * 创建游戏用户与Zulip账号的关联关系 * * 业务逻辑: * 1. 验证游戏用户ID格式 * 2. 调用Repository层创建关联 * 3. 处理业务异常(重复关联等) * 4. 清理相关缓存 * 5. 转换为业务响应DTO * * @param createDto 创建关联的数据传输对象 * @returns Promise 创建结果 * * @throws ConflictException 当关联已存在时 */ async create(createDto: CreateZulipAccountDto): Promise { const monitor = this.createPerformanceMonitor('创建Zulip账号关联', { gameUserId: createDto.gameUserId }); try { const account = await this.repository.create({ gameUserId: this.parseGameUserId(createDto.gameUserId), zulipUserId: createDto.zulipUserId, zulipEmail: createDto.zulipEmail, zulipFullName: createDto.zulipFullName, zulipApiKeyEncrypted: createDto.zulipApiKeyEncrypted, status: createDto.status || 'active', }); await this.clearRelatedCache(createDto.gameUserId, createDto.zulipUserId, createDto.zulipEmail); const result = this.toResponseDto(account); monitor.success({ accountId: account.id.toString(), status: account.status }); return result; } catch (error) { if (error instanceof Error) { if (error.message.includes('already has a Zulip account')) { const conflictError = new ConflictException(`游戏用户 ${createDto.gameUserId} 已存在Zulip账号关联`); monitor.error(conflictError); } if (error.message.includes('is already linked')) { if (error.message.includes('Zulip user')) { const conflictError = new ConflictException(`Zulip用户 ${createDto.zulipUserId} 已被关联到其他游戏账号`); monitor.error(conflictError); } if (error.message.includes('Zulip email')) { const conflictError = new ConflictException(`Zulip邮箱 ${createDto.zulipEmail} 已被关联到其他游戏账号`); monitor.error(conflictError); } } } monitor.error(error); } } /** * 根据游戏用户ID查找关联(带缓存) * * 功能描述: * 根据游戏用户ID查找对应的Zulip账号关联信息 * * 业务逻辑: * 1. 检查缓存中是否存在 * 2. 缓存未命中时查询Repository * 3. 转换为业务响应DTO * 4. 更新缓存 * 5. 记录查询性能指标 * * @param gameUserId 游戏用户ID * @param includeGameUser 是否包含游戏用户信息 * @returns Promise 关联信息或null */ async findByGameUserId(gameUserId: string, includeGameUser: boolean = false): Promise { const cacheKey = this.buildCacheKey('game_user', gameUserId, includeGameUser); try { const cached = await this.cacheManager.get(cacheKey); if (cached) { this.logger.debug('缓存命中', { module: this.moduleName, operation: 'findByGameUserId', gameUserId, cacheKey }); return cached; } const monitor = this.createPerformanceMonitor('根据游戏用户ID查找关联', { gameUserId }); const account = await this.repository.findByGameUserId(this.parseGameUserId(gameUserId), includeGameUser); if (!account) { this.logger.debug('未找到Zulip账号关联', { module: this.moduleName, operation: 'findByGameUserId', gameUserId }); monitor.success({ found: false }); return null; } const result = this.toResponseDto(account); await this.cacheManager.set(cacheKey, result, ZulipAccountsBusinessService.CACHE_TTL); monitor.success({ found: true, cached: true }); return result; } catch (error) { this.handleServiceError(error, '根据游戏用户ID查找关联', { gameUserId }); } } /** * 获取账号状态统计(带缓存) * * 功能描述: * 获取所有Zulip账号关联的状态统计信息 * * 业务逻辑: * 1. 检查统计数据缓存 * 2. 缓存未命中时查询Repository * 3. 计算总计数据 * 4. 更新缓存 * 5. 返回统计结果 * * @returns Promise 状态统计信息 */ async getStatusStatistics(): Promise { const cacheKey = this.buildCacheKey('stats'); try { const cached = await this.cacheManager.get(cacheKey); if (cached) { this.logger.debug('统计数据缓存命中', { module: this.moduleName, operation: 'getStatusStatistics', cacheKey }); return cached; } const monitor = this.createPerformanceMonitor('获取账号状态统计'); const statistics = await this.repository.getStatusStatistics(); const result = { active: statistics.active || 0, inactive: statistics.inactive || 0, suspended: statistics.suspended || 0, error: statistics.error || 0, total: (statistics.active || 0) + (statistics.inactive || 0) + (statistics.suspended || 0) + (statistics.error || 0), }; await this.cacheManager.set(cacheKey, result, ZulipAccountsBusinessService.STATS_CACHE_TTL); monitor.success({ total: result.total, cached: true }); return result; } catch (error) { this.handleServiceError(error, '获取账号状态统计'); } } /** * 将实体转换为响应DTO * * 功能描述: * 将Repository层返回的实体对象转换为业务层的响应DTO * * @param account 实体对象 * @returns ZulipAccountResponseDto 响应DTO */ protected toResponseDto(account: any): ZulipAccountResponseDto { return { id: account.id.toString(), gameUserId: account.gameUserId.toString(), zulipUserId: account.zulipUserId, zulipEmail: account.zulipEmail, zulipFullName: account.zulipFullName, status: account.status, lastVerifiedAt: account.lastVerifiedAt?.toISOString(), lastSyncedAt: account.lastSyncedAt?.toISOString(), errorMessage: account.errorMessage, retryCount: account.retryCount, createdAt: account.createdAt.toISOString(), updatedAt: account.updatedAt.toISOString(), gameUser: account.gameUser, }; } /** * 构建缓存键 * * @param type 缓存类型 * @param identifier 标识符 * @param includeGameUser 是否包含游戏用户信息 * @returns string 缓存键 * @private */ private buildCacheKey(type: string, identifier?: string, includeGameUser?: boolean): string { const parts = [ZulipAccountsBusinessService.CACHE_PREFIX, type]; if (identifier) parts.push(identifier); if (includeGameUser) parts.push('with_user'); return parts.join(':'); } /** * 清除相关缓存 * * @param gameUserId 游戏用户ID * @param zulipUserId Zulip用户ID * @param zulipEmail Zulip邮箱 * @returns Promise * @private */ private async clearRelatedCache(gameUserId?: string, zulipUserId?: number, zulipEmail?: string): Promise { const keysToDelete: string[] = []; keysToDelete.push(this.buildCacheKey('stats')); if (gameUserId) { keysToDelete.push(this.buildCacheKey('game_user', gameUserId, false)); keysToDelete.push(this.buildCacheKey('game_user', gameUserId, true)); } if (zulipUserId) { keysToDelete.push(this.buildCacheKey('zulip_user', zulipUserId.toString(), false)); keysToDelete.push(this.buildCacheKey('zulip_user', zulipUserId.toString(), true)); } if (zulipEmail) { keysToDelete.push(this.buildCacheKey('zulip_email', zulipEmail, false)); keysToDelete.push(this.buildCacheKey('zulip_email', zulipEmail, true)); } try { await Promise.all(keysToDelete.map(key => this.cacheManager.del(key))); this.logger.debug('清除相关缓存', { module: this.moduleName, operation: 'clearRelatedCache', keysCount: keysToDelete.length, keys: keysToDelete }); } catch (error) { this.logger.warn('清除缓存失败', { module: this.moduleName, operation: 'clearRelatedCache', error: this.formatError(error), keys: keysToDelete }); } } }