Files
whale-town-end/src/core/zulip/config/zulip.config.ts
moyin 2d10131838 refactor:重构Zulip模块按业务功能模块化架构
- 将技术实现服务从business层迁移到core层
- 创建src/core/zulip/核心服务模块,包含API客户端、连接池等技术服务
- 保留src/business/zulip/业务逻辑,专注游戏相关的业务规则
- 通过依赖注入实现业务层与核心层的解耦
- 更新模块导入关系,确保架构分层清晰

重构后的架构符合单一职责原则,提高了代码的可维护性和可测试性
2025-12-31 15:44:36 +08:00

398 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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();
});