/** * Zulip账号管理核心服务 * * 功能描述: * - 自动创建Zulip用户账号 * - 生成API Key并安全存储 * - 处理账号创建失败场景 * - 管理用户账号与游戏账号的关联 * * 主要方法: * - createZulipAccount(): 创建新的Zulip用户账号 * - generateApiKey(): 为用户生成API Key * - validateZulipAccount(): 验证Zulip账号有效性 * - linkGameAccount(): 关联游戏账号与Zulip账号 * * 使用场景: * - 用户注册时自动创建Zulip账号 * - API Key管理和更新 * - 账号关联和映射存储 * * @author angjustinl * @version 1.0.0 * @since 2025-01-05 */ import { Injectable, Logger } from '@nestjs/common'; import { ZulipClientConfig } from '../interfaces/zulip-core.interfaces'; /** * Zulip账号创建请求接口 */ export interface CreateZulipAccountRequest { email: string; fullName: string; password?: string; shortName?: string; } /** * Zulip账号创建结果接口 */ export interface CreateZulipAccountResult { success: boolean; userId?: number; email?: string; apiKey?: string; error?: string; errorCode?: string; } /** * API Key生成结果接口 */ export interface GenerateApiKeyResult { success: boolean; apiKey?: string; error?: string; } /** * 账号验证结果接口 */ export interface ValidateAccountResult { success: boolean; isValid?: boolean; userInfo?: any; error?: string; } /** * 账号关联信息接口 */ export interface AccountLinkInfo { gameUserId: string; zulipUserId: number; zulipEmail: string; zulipApiKey: string; createdAt: Date; lastVerified?: Date; isActive: boolean; } /** * Zulip账号管理服务类 * * 职责: * - 处理Zulip用户账号的创建和管理 * - 管理API Key的生成和存储 * - 维护游戏账号与Zulip账号的关联关系 * - 提供账号验证和状态检查功能 * * 主要方法: * - createZulipAccount(): 创建新的Zulip用户账号 * - generateApiKey(): 为现有用户生成API Key * - validateZulipAccount(): 验证Zulip账号有效性 * - linkGameAccount(): 建立游戏账号与Zulip账号的关联 * - unlinkGameAccount(): 解除账号关联 * * 使用场景: * - 用户注册流程中自动创建Zulip账号 * - API Key管理和更新 * - 账号状态监控和维护 * - 跨平台账号同步 */ @Injectable() export class ZulipAccountService { private readonly logger = new Logger(ZulipAccountService.name); private adminClient: any = null; private readonly accountLinks = new Map(); constructor() { this.logger.log('ZulipAccountService初始化完成'); } /** * 初始化管理员客户端 * * 功能描述: * 使用管理员凭证初始化Zulip客户端,用于创建用户账号 * * @param adminConfig 管理员配置 * @returns Promise 是否初始化成功 */ async initializeAdminClient(adminConfig: ZulipClientConfig): Promise { this.logger.log('初始化Zulip管理员客户端', { operation: 'initializeAdminClient', realm: adminConfig.realm, timestamp: new Date().toISOString(), }); try { // 动态导入zulip-js const zulipInit = await this.loadZulipModule(); // 创建管理员客户端 this.adminClient = await zulipInit({ username: adminConfig.username, apiKey: adminConfig.apiKey, realm: adminConfig.realm, }); // 验证管理员权限 const profile = await this.adminClient.users.me.getProfile(); if (profile.result !== 'success') { throw new Error(`管理员客户端验证失败: ${profile.msg || '未知错误'}`); } this.logger.log('管理员客户端初始化成功', { operation: 'initializeAdminClient', adminEmail: profile.email, isAdmin: profile.is_admin, timestamp: new Date().toISOString(), }); return true; } catch (error) { const err = error as Error; this.logger.error('管理员客户端初始化失败', { operation: 'initializeAdminClient', error: err.message, timestamp: new Date().toISOString(), }, err.stack); return false; } } /** * 创建Zulip用户账号 * * 功能描述: * 使用管理员权限在Zulip服务器上创建新的用户账号 * * 业务逻辑: * 1. 验证管理员客户端是否已初始化 * 2. 检查邮箱是否已存在 * 3. 生成用户密码(如果未提供) * 4. 调用Zulip API创建用户 * 5. 为新用户生成API Key * 6. 返回创建结果 * * @param request 账号创建请求 * @returns Promise 创建结果 */ async createZulipAccount(request: CreateZulipAccountRequest): Promise { const startTime = Date.now(); this.logger.log('开始创建Zulip账号', { operation: 'createZulipAccount', email: request.email, fullName: request.fullName, timestamp: new Date().toISOString(), }); try { // 1. 验证管理员客户端 if (!this.adminClient) { throw new Error('管理员客户端未初始化'); } // 2. 验证请求参数 if (!request.email || !request.email.trim()) { throw new Error('邮箱地址不能为空'); } if (!request.fullName || !request.fullName.trim()) { throw new Error('用户全名不能为空'); } // 3. 检查邮箱格式 if (!this.isValidEmail(request.email)) { throw new Error('邮箱格式无效'); } // 4. 检查用户是否已存在 const existingUser = await this.checkUserExists(request.email); if (existingUser) { this.logger.warn('用户已存在', { operation: 'createZulipAccount', email: request.email, }); return { success: false, error: '用户已存在', errorCode: 'USER_ALREADY_EXISTS', }; } // 5. 生成密码(如果未提供) const password = request.password || this.generateRandomPassword(); const shortName = request.shortName || this.generateShortName(request.email); // 6. 创建用户参数 const createParams = { email: request.email, password: password, full_name: request.fullName, short_name: shortName, }; // 7. 调用Zulip API创建用户 const createResponse = await this.adminClient.users.create(createParams); if (createResponse.result !== 'success') { this.logger.warn('Zulip用户创建失败', { operation: 'createZulipAccount', email: request.email, error: createResponse.msg, }); return { success: false, error: createResponse.msg || '用户创建失败', errorCode: 'ZULIP_CREATE_FAILED', }; } // 8. 为新用户生成API Key const apiKeyResult = await this.generateApiKeyForUser(request.email, password); if (!apiKeyResult.success) { this.logger.warn('API Key生成失败,但用户已创建', { operation: 'createZulipAccount', email: request.email, error: apiKeyResult.error, }); // 用户已创建,但API Key生成失败 return { success: true, userId: createResponse.user_id, email: request.email, error: `用户创建成功,但API Key生成失败: ${apiKeyResult.error}`, errorCode: 'API_KEY_GENERATION_FAILED', }; } const duration = Date.now() - startTime; this.logger.log('Zulip账号创建成功', { operation: 'createZulipAccount', email: request.email, userId: createResponse.user_id, hasApiKey: !!apiKeyResult.apiKey, duration, timestamp: new Date().toISOString(), }); return { success: true, userId: createResponse.user_id, email: request.email, apiKey: apiKeyResult.apiKey, }; } catch (error) { const err = error as Error; const duration = Date.now() - startTime; this.logger.error('创建Zulip账号失败', { operation: 'createZulipAccount', email: request.email, error: err.message, duration, timestamp: new Date().toISOString(), }, err.stack); return { success: false, error: err.message, errorCode: 'ACCOUNT_CREATION_FAILED', }; } } /** * 为用户生成API Key * * 功能描述: * 使用用户凭证获取API Key * * @param email 用户邮箱 * @param password 用户密码 * @returns Promise 生成结果 */ async generateApiKeyForUser(email: string, password: string): Promise { this.logger.log('为用户生成API Key', { operation: 'generateApiKeyForUser', email, timestamp: new Date().toISOString(), }); try { // 动态导入zulip-js const zulipInit = await this.loadZulipModule(); // 使用用户凭证获取API Key const userClient = await zulipInit({ username: email, password: password, realm: this.getRealmFromAdminClient(), }); // 验证客户端并获取API Key const profile = await userClient.users.me.getProfile(); if (profile.result !== 'success') { throw new Error(`API Key获取失败: ${profile.msg || '未知错误'}`); } // 从客户端配置中提取API Key const apiKey = userClient.config?.apiKey; if (!apiKey) { throw new Error('无法从客户端配置中获取API Key'); } this.logger.log('API Key生成成功', { operation: 'generateApiKeyForUser', email, timestamp: new Date().toISOString(), }); return { success: true, apiKey: apiKey, }; } catch (error) { const err = error as Error; this.logger.error('API Key生成失败', { operation: 'generateApiKeyForUser', email, error: err.message, timestamp: new Date().toISOString(), }, err.stack); return { success: false, error: err.message, }; } } /** * 验证Zulip账号有效性 * * 功能描述: * 验证指定的Zulip账号是否存在且有效 * * @param email 用户邮箱 * @param apiKey 用户API Key(可选) * @returns Promise 验证结果 */ async validateZulipAccount(email: string, apiKey?: string): Promise { this.logger.log('验证Zulip账号', { operation: 'validateZulipAccount', email, hasApiKey: !!apiKey, timestamp: new Date().toISOString(), }); try { if (apiKey) { // 使用API Key验证 const zulipInit = await this.loadZulipModule(); const userClient = await zulipInit({ username: email, apiKey: apiKey, realm: this.getRealmFromAdminClient(), }); const profile = await userClient.users.me.getProfile(); if (profile.result === 'success') { this.logger.log('账号验证成功(API Key)', { operation: 'validateZulipAccount', email, userId: profile.user_id, }); return { success: true, isValid: true, userInfo: profile, }; } else { return { success: true, isValid: false, error: profile.msg || 'API Key验证失败', }; } } else { // 仅检查用户是否存在 const userExists = await this.checkUserExists(email); this.logger.log('账号存在性检查完成', { operation: 'validateZulipAccount', email, exists: userExists, }); return { success: true, isValid: userExists, }; } } catch (error) { const err = error as Error; this.logger.error('账号验证失败', { operation: 'validateZulipAccount', email, error: err.message, timestamp: new Date().toISOString(), }, err.stack); return { success: false, error: err.message, }; } } /** * 关联游戏账号与Zulip账号 * * 功能描述: * 建立游戏用户ID与Zulip账号的映射关系 * * @param gameUserId 游戏用户ID * @param zulipUserId Zulip用户ID * @param zulipEmail Zulip邮箱 * @param zulipApiKey Zulip API Key * @returns Promise 是否关联成功 */ async linkGameAccount( gameUserId: string, zulipUserId: number, zulipEmail: string, zulipApiKey: string, ): Promise { this.logger.log('关联游戏账号与Zulip账号', { operation: 'linkGameAccount', gameUserId, zulipUserId, zulipEmail, timestamp: new Date().toISOString(), }); try { // 验证参数 if (!gameUserId || !zulipUserId || !zulipEmail || !zulipApiKey) { throw new Error('关联参数不完整'); } // 创建关联信息 const linkInfo: AccountLinkInfo = { gameUserId, zulipUserId, zulipEmail, zulipApiKey, createdAt: new Date(), isActive: true, }; // 存储关联信息(实际项目中应存储到数据库) this.accountLinks.set(gameUserId, linkInfo); this.logger.log('账号关联成功', { operation: 'linkGameAccount', gameUserId, zulipUserId, zulipEmail, timestamp: new Date().toISOString(), }); return true; } catch (error) { const err = error as Error; this.logger.error('账号关联失败', { operation: 'linkGameAccount', gameUserId, zulipUserId, error: err.message, timestamp: new Date().toISOString(), }, err.stack); return false; } } /** * 解除游戏账号与Zulip账号的关联 * * @param gameUserId 游戏用户ID * @returns Promise 是否解除成功 */ async unlinkGameAccount(gameUserId: string): Promise { this.logger.log('解除账号关联', { operation: 'unlinkGameAccount', gameUserId, timestamp: new Date().toISOString(), }); try { const linkInfo = this.accountLinks.get(gameUserId); if (linkInfo) { linkInfo.isActive = false; this.accountLinks.delete(gameUserId); this.logger.log('账号关联解除成功', { operation: 'unlinkGameAccount', gameUserId, zulipEmail: linkInfo.zulipEmail, }); } return true; } catch (error) { const err = error as Error; this.logger.error('解除账号关联失败', { operation: 'unlinkGameAccount', gameUserId, error: err.message, timestamp: new Date().toISOString(), }, err.stack); return false; } } /** * 获取游戏账号的Zulip关联信息 * * @param gameUserId 游戏用户ID * @returns AccountLinkInfo | null 关联信息 */ getAccountLink(gameUserId: string): AccountLinkInfo | null { return this.accountLinks.get(gameUserId) || null; } /** * 获取所有账号关联信息 * * @returns AccountLinkInfo[] 所有关联信息 */ getAllAccountLinks(): AccountLinkInfo[] { return Array.from(this.accountLinks.values()).filter(link => link.isActive); } /** * 检查用户是否已存在 * * @param email 用户邮箱 * @returns Promise 用户是否存在 * @private */ private async checkUserExists(email: string): Promise { try { if (!this.adminClient) { return false; } // 获取所有用户列表 const usersResponse = await this.adminClient.users.retrieve(); if (usersResponse.result === 'success') { const users = usersResponse.members || []; return users.some((user: any) => user.email === email); } return false; } catch (error) { const err = error as Error; this.logger.warn('检查用户存在性失败', { operation: 'checkUserExists', email, error: err.message, }); return false; } } /** * 验证邮箱格式 * * @param email 邮箱地址 * @returns boolean 是否为有效邮箱 * @private */ private isValidEmail(email: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } /** * 生成随机密码 * * @returns string 随机密码 * @private */ private generateRandomPassword(): string { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'; let password = ''; for (let i = 0; i < 12; i++) { password += chars.charAt(Math.floor(Math.random() * chars.length)); } return password; } /** * 从邮箱生成短名称 * * @param email 邮箱地址 * @returns string 短名称 * @private */ private generateShortName(email: string): string { const localPart = email.split('@')[0]; // 移除特殊字符,只保留字母数字和下划线 return localPart.replace(/[^a-zA-Z0-9_]/g, '').toLowerCase(); } /** * 从管理员客户端获取Realm * * @returns string Realm URL * @private */ private getRealmFromAdminClient(): string { if (!this.adminClient || !this.adminClient.config) { throw new Error('管理员客户端未初始化或配置缺失'); } return this.adminClient.config.realm; } /** * 动态加载zulip-js模块 * * @returns Promise zulip-js初始化函数 * @private */ private async loadZulipModule(): Promise { try { // 使用动态导入加载zulip-js const zulipModule = await import('zulip-js'); return zulipModule.default || zulipModule; } catch (error) { const err = error as Error; this.logger.error('加载zulip-js模块失败', { operation: 'loadZulipModule', error: err.message, timestamp: new Date().toISOString(), }, err.stack); throw new Error(`加载zulip-js模块失败: ${err.message}`); } } }