refactor:重构Zulip模块按业务功能模块化架构

- 将技术实现服务从business层迁移到core层
- 创建src/core/zulip/核心服务模块,包含API客户端、连接池等技术服务
- 保留src/business/zulip/业务逻辑,专注游戏相关的业务规则
- 通过依赖注入实现业务层与核心层的解耦
- 更新模块导入关系,确保架构分层清晰

重构后的架构符合单一职责原则,提高了代码的可维护性和可测试性
This commit is contained in:
moyin
2025-12-31 15:44:36 +08:00
parent 5140bd1a54
commit 2d10131838
36 changed files with 2773 additions and 125 deletions

View File

@@ -0,0 +1,397 @@
/**
* 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();
});