/** * Zulip配置模块 * * 功能描述: * - 定义Zulip集成系统的配置接口 * - 提供配置验证功能 * - 支持环境变量和配置文件两种配置方式 * - 实现配置热重载 * * 配置来源优先级: * 1. 环境变量(最高优先级) * 2. 配置文件 * 3. 默认值(最低优先级) * * 依赖模块: * - @nestjs/config: NestJS配置模块 * * @author angjustinl * @version 1.0.0 * @since 2025-12-25 */ import { registerAs } from '@nestjs/config'; /** * Zulip服务器配置接口 */ export interface ZulipServerConfig { /** Zulip服务器URL */ serverUrl: string; /** Zulip机器人邮箱 */ botEmail: string; /** Zulip机器人API Key */ botApiKey: string; } /** * WebSocket配置接口 */ export interface WebSocketConfig { /** WebSocket端口 */ port: number; /** WebSocket命名空间 */ namespace: string; /** 心跳间隔(毫秒) */ pingInterval: number; /** 心跳超时(毫秒) */ pingTimeout: number; } /** * 消息配置接口 */ export interface MessageConfig { /** 消息频率限制(条/分钟) */ rateLimit: number; /** 消息最大长度 */ maxLength: number; /** 是否启用内容过滤 */ contentFilterEnabled: boolean; /** 敏感词列表文件路径 */ sensitiveWordsPath?: string; } /** * 会话配置接口 */ export interface SessionConfig { /** 会话超时时间(分钟) */ timeout: number; /** 清理间隔(分钟) */ cleanupInterval: number; /** 最大连接数 */ maxConnections: number; } /** * 错误处理配置接口 */ export interface ErrorHandlingConfig { /** 是否启用降级模式 */ degradedModeEnabled: boolean; /** 是否启用自动重连 */ autoReconnectEnabled: boolean; /** 最大重连尝试次数 */ maxReconnectAttempts: number; /** 重连基础延迟(毫秒) */ reconnectBaseDelay: number; /** API超时时间(毫秒) */ apiTimeout: number; /** 最大重试次数 */ maxRetries: number; } /** * 监控配置接口 */ export interface MonitoringConfig { /** 健康检查间隔(毫秒) */ healthCheckInterval: number; /** 错误率阈值(0-1) */ errorRateThreshold: number; /** API响应时间阈值(毫秒) */ responseTimeThreshold: number; /** 内存使用阈值(0-1) */ memoryThreshold: number; } /** * 安全配置接口 */ export interface SecurityConfig { /** API Key加密密钥(生产环境必须配置) */ apiKeyEncryptionKey?: string; /** 允许的Stream列表(空表示允许所有) */ allowedStreams: string[]; } /** * 完整的Zulip配置接口 */ export interface ZulipConfiguration { /** Zulip服务器配置 */ server: ZulipServerConfig; /** WebSocket配置 */ websocket: WebSocketConfig; /** 消息配置 */ message: MessageConfig; /** 会话配置 */ session: SessionConfig; /** 错误处理配置 */ errorHandling: ErrorHandlingConfig; /** 监控配置 */ monitoring: MonitoringConfig; /** 安全配置 */ security: SecurityConfig; } /** * 配置验证结果接口 */ export interface ConfigValidationResult { /** 是否有效 */ valid: boolean; /** 错误信息列表 */ errors: string[]; /** 警告信息列表 */ warnings: string[]; } /** * 默认配置值 */ export const DEFAULT_ZULIP_CONFIG: ZulipConfiguration = { server: { serverUrl: 'https://your-zulip-server.com', botEmail: 'bot@example.com', botApiKey: '', }, websocket: { port: 3000, namespace: '/game', pingInterval: 25000, pingTimeout: 5000, }, message: { rateLimit: 10, maxLength: 10000, contentFilterEnabled: true, }, session: { timeout: 30, cleanupInterval: 5, maxConnections: 1000, }, errorHandling: { degradedModeEnabled: false, autoReconnectEnabled: true, maxReconnectAttempts: 5, reconnectBaseDelay: 5000, apiTimeout: 30000, maxRetries: 3, }, monitoring: { healthCheckInterval: 60000, errorRateThreshold: 0.1, responseTimeThreshold: 5000, memoryThreshold: 0.9, }, security: { allowedStreams: [], }, }; /** * 从环境变量加载Zulip配置 * * 功能描述: * 从环境变量读取配置值,未设置的使用默认值 * * @returns ZulipConfiguration 完整的Zulip配置对象 */ export function loadZulipConfigFromEnv(): ZulipConfiguration { return { server: { serverUrl: process.env.ZULIP_SERVER_URL || DEFAULT_ZULIP_CONFIG.server.serverUrl, botEmail: process.env.ZULIP_BOT_EMAIL || DEFAULT_ZULIP_CONFIG.server.botEmail, botApiKey: process.env.ZULIP_BOT_API_KEY || DEFAULT_ZULIP_CONFIG.server.botApiKey, }, websocket: { port: parseInt(process.env.WEBSOCKET_PORT || String(DEFAULT_ZULIP_CONFIG.websocket.port), 10), namespace: process.env.WEBSOCKET_NAMESPACE || DEFAULT_ZULIP_CONFIG.websocket.namespace, pingInterval: parseInt(process.env.WEBSOCKET_PING_INTERVAL || String(DEFAULT_ZULIP_CONFIG.websocket.pingInterval), 10), pingTimeout: parseInt(process.env.WEBSOCKET_PING_TIMEOUT || String(DEFAULT_ZULIP_CONFIG.websocket.pingTimeout), 10), }, message: { rateLimit: parseInt(process.env.ZULIP_MESSAGE_RATE_LIMIT || String(DEFAULT_ZULIP_CONFIG.message.rateLimit), 10), maxLength: parseInt(process.env.ZULIP_MESSAGE_MAX_LENGTH || String(DEFAULT_ZULIP_CONFIG.message.maxLength), 10), contentFilterEnabled: process.env.ZULIP_CONTENT_FILTER_ENABLED !== 'false', sensitiveWordsPath: process.env.ZULIP_SENSITIVE_WORDS_PATH, }, session: { timeout: parseInt(process.env.ZULIP_SESSION_TIMEOUT || String(DEFAULT_ZULIP_CONFIG.session.timeout), 10), cleanupInterval: parseInt(process.env.ZULIP_CLEANUP_INTERVAL || String(DEFAULT_ZULIP_CONFIG.session.cleanupInterval), 10), maxConnections: parseInt(process.env.ZULIP_MAX_CONNECTIONS || String(DEFAULT_ZULIP_CONFIG.session.maxConnections), 10), }, errorHandling: { degradedModeEnabled: process.env.ZULIP_DEGRADED_MODE_ENABLED === 'true', autoReconnectEnabled: process.env.ZULIP_AUTO_RECONNECT_ENABLED !== 'false', maxReconnectAttempts: parseInt(process.env.ZULIP_MAX_RECONNECT_ATTEMPTS || String(DEFAULT_ZULIP_CONFIG.errorHandling.maxReconnectAttempts), 10), reconnectBaseDelay: parseInt(process.env.ZULIP_RECONNECT_BASE_DELAY || String(DEFAULT_ZULIP_CONFIG.errorHandling.reconnectBaseDelay), 10), apiTimeout: parseInt(process.env.ZULIP_API_TIMEOUT || String(DEFAULT_ZULIP_CONFIG.errorHandling.apiTimeout), 10), maxRetries: parseInt(process.env.ZULIP_MAX_RETRIES || String(DEFAULT_ZULIP_CONFIG.errorHandling.maxRetries), 10), }, monitoring: { healthCheckInterval: parseInt(process.env.MONITORING_HEALTH_CHECK_INTERVAL || String(DEFAULT_ZULIP_CONFIG.monitoring.healthCheckInterval), 10), errorRateThreshold: parseFloat(process.env.MONITORING_ERROR_RATE_THRESHOLD || String(DEFAULT_ZULIP_CONFIG.monitoring.errorRateThreshold)), responseTimeThreshold: parseInt(process.env.MONITORING_RESPONSE_TIME_THRESHOLD || String(DEFAULT_ZULIP_CONFIG.monitoring.responseTimeThreshold), 10), memoryThreshold: parseFloat(process.env.MONITORING_MEMORY_THRESHOLD || String(DEFAULT_ZULIP_CONFIG.monitoring.memoryThreshold)), }, security: { apiKeyEncryptionKey: process.env.ZULIP_API_KEY_ENCRYPTION_KEY, allowedStreams: (process.env.ZULIP_ALLOWED_STREAMS || '').split(',').filter(s => s.trim()), }, }; } /** * 验证Zulip配置 * * 功能描述: * 验证配置的完整性和有效性,返回验证结果 * * 验证规则: * 1. 必填字段不能为空 * 2. 数值字段必须在有效范围内 * 3. URL格式必须正确 * 4. 生产环境必须配置API Key加密密钥 * * @param config Zulip配置对象 * @param isProduction 是否为生产环境 * @returns ConfigValidationResult 验证结果 */ export function validateZulipConfig( config: ZulipConfiguration, isProduction: boolean = false ): ConfigValidationResult { const errors: string[] = []; const warnings: string[] = []; // 验证服务器配置 if (!config.server.serverUrl) { errors.push('缺少Zulip服务器URL (ZULIP_SERVER_URL)'); } else if (!isValidUrl(config.server.serverUrl)) { errors.push('Zulip服务器URL格式无效'); } if (!config.server.botEmail) { warnings.push('未配置Zulip机器人邮箱 (ZULIP_BOT_EMAIL)'); } else if (!isValidEmail(config.server.botEmail)) { errors.push('Zulip机器人邮箱格式无效'); } if (!config.server.botApiKey) { warnings.push('未配置Zulip机器人API Key (ZULIP_BOT_API_KEY),将使用本地模式'); } // 验证WebSocket配置 if (config.websocket.port < 1 || config.websocket.port > 65535) { errors.push('WebSocket端口必须在1-65535范围内'); } if (!config.websocket.namespace || !config.websocket.namespace.startsWith('/')) { errors.push('WebSocket命名空间必须以/开头'); } // 验证消息配置 if (config.message.rateLimit < 1) { errors.push('消息频率限制必须大于0'); } if (config.message.maxLength < 1 || config.message.maxLength > 100000) { errors.push('消息最大长度必须在1-100000范围内'); } // 验证会话配置 if (config.session.timeout < 1) { errors.push('会话超时时间必须大于0'); } if (config.session.cleanupInterval < 1) { errors.push('清理间隔必须大于0'); } if (config.session.maxConnections < 1) { errors.push('最大连接数必须大于0'); } // 验证错误处理配置 if (config.errorHandling.maxReconnectAttempts < 0) { errors.push('最大重连尝试次数不能为负数'); } if (config.errorHandling.apiTimeout < 1000) { warnings.push('API超时时间过短,建议至少1000毫秒'); } // 验证监控配置 if (config.monitoring.errorRateThreshold < 0 || config.monitoring.errorRateThreshold > 1) { errors.push('错误率阈值必须在0-1范围内'); } if (config.monitoring.memoryThreshold < 0 || config.monitoring.memoryThreshold > 1) { errors.push('内存使用阈值必须在0-1范围内'); } // 生产环境特殊验证 if (isProduction) { if (!config.security.apiKeyEncryptionKey) { errors.push('生产环境必须配置API Key加密密钥 (ZULIP_API_KEY_ENCRYPTION_KEY)'); } else if (config.security.apiKeyEncryptionKey.length < 32) { errors.push('API Key加密密钥长度必须至少32字符'); } if (!config.server.botApiKey) { errors.push('生产环境必须配置Zulip机器人API Key'); } } return { valid: errors.length === 0, errors, warnings, }; } /** * 验证URL格式 * * @param url URL字符串 * @returns boolean 是否有效 */ function isValidUrl(url: string): boolean { try { new URL(url); return true; } catch { return false; } } /** * 验证邮箱格式 * * @param email 邮箱字符串 * @returns boolean 是否有效 */ function isValidEmail(email: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } /** * NestJS配置工厂函数 * * 功能描述: * 用于@nestjs/config模块的配置注册 * * 使用方式: * ConfigModule.forRoot({ * load: [zulipConfig], * }) */ export const zulipConfig = registerAs('zulip', () => { return loadZulipConfigFromEnv(); });