diff --git a/src/app.js b/src/app.js index e7dfd7e7..2f6d09cb 100644 --- a/src/app.js +++ b/src/app.js @@ -10,6 +10,7 @@ const config = require('../config/config') const logger = require('./utils/logger') const redis = require('./models/redis') const pricingService = require('./services/pricingService') +const cacheMonitor = require('./utils/cacheMonitor') // Import routes const apiRoutes = require('./routes/api') @@ -49,6 +50,9 @@ class Application { logger.info('🔄 Initializing pricing service...') await pricingService.initialize() + // 📊 初始化缓存监控 + await this.initializeCacheMonitoring() + // 🔧 初始化管理员凭据 logger.info('🔄 Initializing admin credentials...') await this.initializeAdmin() @@ -456,6 +460,40 @@ class Application { } } + // 📊 初始化缓存监控 + async initializeCacheMonitoring() { + try { + logger.info('🔄 Initializing cache monitoring...') + + // 注册各个服务的缓存实例 + const services = [ + { name: 'claudeAccount', service: require('./services/claudeAccountService') }, + { name: 'claudeConsole', service: require('./services/claudeConsoleAccountService') }, + { name: 'bedrockAccount', service: require('./services/bedrockAccountService') } + ] + + // 注册已加载的服务缓存 + for (const { name, service } of services) { + if (service && (service._decryptCache || service.decryptCache)) { + const cache = service._decryptCache || service.decryptCache + cacheMonitor.registerCache(`${name}_decrypt`, cache) + logger.info(`✅ Registered ${name} decrypt cache for monitoring`) + } + } + + // 初始化时打印一次统计 + setTimeout(() => { + const stats = cacheMonitor.getGlobalStats() + logger.info(`📊 Cache System - Registered: ${stats.cacheCount} caches`) + }, 5000) + + logger.success('✅ Cache monitoring initialized') + } catch (error) { + logger.error('❌ Failed to initialize cache monitoring:', error) + // 不阻止应用启动 + } + } + startCleanupTasks() { // 🧹 每小时清理一次过期数据 setInterval(async () => { diff --git a/src/models/redis.js b/src/models/redis.js index 554638c5..a4a03b10 100644 --- a/src/models/redis.js +++ b/src/models/redis.js @@ -191,7 +191,9 @@ class RedisClient { outputTokens = 0, cacheCreateTokens = 0, cacheReadTokens = 0, - model = 'unknown' + model = 'unknown', + ephemeral5mTokens = 0, // 新增:5分钟缓存 tokens + ephemeral1hTokens = 0 // 新增:1小时缓存 tokens ) { const key = `usage:${keyId}` const now = new Date() @@ -245,6 +247,9 @@ class RedisClient { pipeline.hincrby(key, 'totalCacheCreateTokens', finalCacheCreateTokens) pipeline.hincrby(key, 'totalCacheReadTokens', finalCacheReadTokens) pipeline.hincrby(key, 'totalAllTokens', totalTokens) // 包含所有类型的总token + // 详细缓存类型统计(新增) + pipeline.hincrby(key, 'totalEphemeral5mTokens', ephemeral5mTokens) + pipeline.hincrby(key, 'totalEphemeral1hTokens', ephemeral1hTokens) // 请求计数 pipeline.hincrby(key, 'totalRequests', 1) @@ -256,6 +261,9 @@ class RedisClient { pipeline.hincrby(daily, 'cacheReadTokens', finalCacheReadTokens) pipeline.hincrby(daily, 'allTokens', totalTokens) pipeline.hincrby(daily, 'requests', 1) + // 详细缓存类型统计 + pipeline.hincrby(daily, 'ephemeral5mTokens', ephemeral5mTokens) + pipeline.hincrby(daily, 'ephemeral1hTokens', ephemeral1hTokens) // 每月统计 pipeline.hincrby(monthly, 'tokens', coreTokens) @@ -265,6 +273,9 @@ class RedisClient { pipeline.hincrby(monthly, 'cacheReadTokens', finalCacheReadTokens) pipeline.hincrby(monthly, 'allTokens', totalTokens) pipeline.hincrby(monthly, 'requests', 1) + // 详细缓存类型统计 + pipeline.hincrby(monthly, 'ephemeral5mTokens', ephemeral5mTokens) + pipeline.hincrby(monthly, 'ephemeral1hTokens', ephemeral1hTokens) // 按模型统计 - 每日 pipeline.hincrby(modelDaily, 'inputTokens', finalInputTokens) @@ -289,6 +300,9 @@ class RedisClient { pipeline.hincrby(keyModelDaily, 'cacheReadTokens', finalCacheReadTokens) pipeline.hincrby(keyModelDaily, 'allTokens', totalTokens) pipeline.hincrby(keyModelDaily, 'requests', 1) + // 详细缓存类型统计 + pipeline.hincrby(keyModelDaily, 'ephemeral5mTokens', ephemeral5mTokens) + pipeline.hincrby(keyModelDaily, 'ephemeral1hTokens', ephemeral1hTokens) // API Key级别的模型统计 - 每月 pipeline.hincrby(keyModelMonthly, 'inputTokens', finalInputTokens) @@ -297,6 +311,9 @@ class RedisClient { pipeline.hincrby(keyModelMonthly, 'cacheReadTokens', finalCacheReadTokens) pipeline.hincrby(keyModelMonthly, 'allTokens', totalTokens) pipeline.hincrby(keyModelMonthly, 'requests', 1) + // 详细缓存类型统计 + pipeline.hincrby(keyModelMonthly, 'ephemeral5mTokens', ephemeral5mTokens) + pipeline.hincrby(keyModelMonthly, 'ephemeral1hTokens', ephemeral1hTokens) // 小时级别统计 pipeline.hincrby(hourly, 'tokens', coreTokens) diff --git a/src/services/apiKeyService.js b/src/services/apiKeyService.js index 9f4033b4..48759aa8 100644 --- a/src/services/apiKeyService.js +++ b/src/services/apiKeyService.js @@ -466,11 +466,31 @@ class ApiKeyService { const totalTokens = inputTokens + outputTokens + cacheCreateTokens + cacheReadTokens - // 计算费用(支持详细的缓存类型) - const pricingService = require('./pricingService') - const costInfo = pricingService.calculateCost(usageObject, model) + // 计算费用(支持详细的缓存类型)- 添加错误处理 + let costInfo = { totalCost: 0, ephemeral5mCost: 0, ephemeral1hCost: 0 } + try { + const pricingService = require('./pricingService') + // 确保 pricingService 已初始化 + if (!pricingService.pricingData) { + logger.warn('⚠️ PricingService not initialized, initializing now...') + await pricingService.initialize() + } + costInfo = pricingService.calculateCost(usageObject, model) + } catch (pricingError) { + logger.error('❌ Failed to calculate cost:', pricingError) + // 继续执行,不要因为费用计算失败而跳过统计记录 + } - // 记录API Key级别的使用统计 + // 提取详细的缓存创建数据 + let ephemeral5mTokens = 0 + let ephemeral1hTokens = 0 + + if (usageObject.cache_creation && typeof usageObject.cache_creation === 'object') { + ephemeral5mTokens = usageObject.cache_creation.ephemeral_5m_input_tokens || 0 + ephemeral1hTokens = usageObject.cache_creation.ephemeral_1h_input_tokens || 0 + } + + // 记录API Key级别的使用统计 - 这个必须执行 await redis.incrementTokenUsage( keyId, totalTokens, @@ -478,7 +498,9 @@ class ApiKeyService { outputTokens, cacheCreateTokens, cacheReadTokens, - model + model, + ephemeral5mTokens, // 传递5分钟缓存 tokens + ephemeral1hTokens // 传递1小时缓存 tokens ) // 记录费用统计 diff --git a/src/services/bedrockAccountService.js b/src/services/bedrockAccountService.js index a4fdbde3..b5e9e1a9 100644 --- a/src/services/bedrockAccountService.js +++ b/src/services/bedrockAccountService.js @@ -4,12 +4,28 @@ const redis = require('../models/redis') const logger = require('../utils/logger') const config = require('../../config/config') const bedrockRelayService = require('./bedrockRelayService') +const LRUCache = require('../utils/lruCache') class BedrockAccountService { constructor() { // 加密相关常量 this.ENCRYPTION_ALGORITHM = 'aes-256-cbc' this.ENCRYPTION_SALT = 'salt' + + // 🚀 性能优化:缓存派生的加密密钥,避免每次重复计算 + this._encryptionKeyCache = null + + // 🔄 解密结果缓存,提高解密性能 + this._decryptCache = new LRUCache(500) + + // 🧹 定期清理缓存(每10分钟) + setInterval( + () => { + this._decryptCache.cleanup() + logger.info('🧹 Bedrock decrypt cache cleanup completed', this._decryptCache.getStats()) + }, + 10 * 60 * 1000 + ) } // 🏢 创建Bedrock账户 @@ -336,10 +352,22 @@ class BedrockAccountService { } } + // 🔑 生成加密密钥(缓存优化) + _generateEncryptionKey() { + if (!this._encryptionKeyCache) { + this._encryptionKeyCache = crypto + .createHash('sha256') + .update(config.security.encryptionKey) + .digest() + logger.info('🔑 Bedrock encryption key derived and cached for performance optimization') + } + return this._encryptionKeyCache + } + // 🔐 加密AWS凭证 _encryptAwsCredentials(credentials) { try { - const key = crypto.createHash('sha256').update(config.security.encryptionKey).digest() + const key = this._generateEncryptionKey() const iv = crypto.randomBytes(16) const cipher = crypto.createCipheriv(this.ENCRYPTION_ALGORITHM, key, iv) @@ -368,15 +396,35 @@ class BedrockAccountService { // 检查是否为加密格式 (有 encrypted 和 iv 字段) if (encryptedData.encrypted && encryptedData.iv) { + // 🎯 检查缓存 + const cacheKey = crypto + .createHash('sha256') + .update(JSON.stringify(encryptedData)) + .digest('hex') + const cached = this._decryptCache.get(cacheKey) + if (cached !== undefined) { + return cached + } + // 加密数据 - 进行解密 - const key = crypto.createHash('sha256').update(config.security.encryptionKey).digest() + const key = this._generateEncryptionKey() const iv = Buffer.from(encryptedData.iv, 'hex') const decipher = crypto.createDecipheriv(this.ENCRYPTION_ALGORITHM, key, iv) let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8') decrypted += decipher.final('utf8') - return JSON.parse(decrypted) + const result = JSON.parse(decrypted) + + // 💾 存入缓存(5分钟过期) + this._decryptCache.set(cacheKey, result, 5 * 60 * 1000) + + // 📊 定期打印缓存统计 + if ((this._decryptCache.hits + this._decryptCache.misses) % 1000 === 0) { + this._decryptCache.printStats() + } + + return result } else if (encryptedData.accessKeyId) { // 纯文本数据 - 直接返回 (向后兼容) logger.warn('⚠️ 发现未加密的AWS凭证,建议更新账户以启用加密') diff --git a/src/services/claudeAccountService.js b/src/services/claudeAccountService.js index 03fcb72d..6577535d 100644 --- a/src/services/claudeAccountService.js +++ b/src/services/claudeAccountService.js @@ -15,6 +15,7 @@ const { logRefreshSkipped } = require('../utils/tokenRefreshLogger') const tokenRefreshService = require('./tokenRefreshService') +const LRUCache = require('../utils/lruCache') class ClaudeAccountService { constructor() { @@ -28,6 +29,18 @@ class ClaudeAccountService { // 🚀 性能优化:缓存派生的加密密钥,避免每次重复计算 // scryptSync 是 CPU 密集型操作,缓存可以减少 95%+ 的 CPU 占用 this._encryptionKeyCache = null + + // 🔄 解密结果缓存,提高解密性能 + this._decryptCache = new LRUCache(500) + + // 🧹 定期清理缓存(每10分钟) + setInterval( + () => { + this._decryptCache.cleanup() + logger.info('🧹 Claude decrypt cache cleanup completed', this._decryptCache.getStats()) + }, + 10 * 60 * 1000 + ) } // 🏢 创建Claude账户 @@ -897,7 +910,16 @@ class ClaudeAccountService { return '' } + // 🎯 检查缓存 + const cacheKey = crypto.createHash('sha256').update(encryptedData).digest('hex') + const cached = this._decryptCache.get(cacheKey) + if (cached !== undefined) { + return cached + } + try { + let decrypted = '' + // 检查是否是新格式(包含IV) if (encryptedData.includes(':')) { // 新格式:iv:encryptedData @@ -908,8 +930,17 @@ class ClaudeAccountService { const encrypted = parts[1] const decipher = crypto.createDecipheriv(this.ENCRYPTION_ALGORITHM, key, iv) - let decrypted = decipher.update(encrypted, 'hex', 'utf8') + decrypted = decipher.update(encrypted, 'hex', 'utf8') decrypted += decipher.final('utf8') + + // 💾 存入缓存(5分钟过期) + this._decryptCache.set(cacheKey, decrypted, 5 * 60 * 1000) + + // 📊 定期打印缓存统计 + if ((this._decryptCache.hits + this._decryptCache.misses) % 1000 === 0) { + this._decryptCache.printStats() + } + return decrypted } } @@ -918,8 +949,12 @@ class ClaudeAccountService { // 注意:在新版本Node.js中这将失败,但我们会捕获错误 try { const decipher = crypto.createDecipher('aes-256-cbc', config.security.encryptionKey) - let decrypted = decipher.update(encryptedData, 'hex', 'utf8') + decrypted = decipher.update(encryptedData, 'hex', 'utf8') decrypted += decipher.final('utf8') + + // 💾 旧格式也存入缓存 + this._decryptCache.set(cacheKey, decrypted, 5 * 60 * 1000) + return decrypted } catch (oldError) { // 如果旧方式也失败,返回原数据 diff --git a/src/services/claudeConsoleAccountService.js b/src/services/claudeConsoleAccountService.js index 24f84f65..fd211651 100644 --- a/src/services/claudeConsoleAccountService.js +++ b/src/services/claudeConsoleAccountService.js @@ -5,6 +5,7 @@ const { HttpsProxyAgent } = require('https-proxy-agent') const redis = require('../models/redis') const logger = require('../utils/logger') const config = require('../../config/config') +const LRUCache = require('../utils/lruCache') class ClaudeConsoleAccountService { constructor() { @@ -17,8 +18,23 @@ class ClaudeConsoleAccountService { this.SHARED_ACCOUNTS_KEY = 'shared_claude_console_accounts' // 🚀 性能优化:缓存派生的加密密钥,避免每次重复计算 - // scryptSync 是 CPU 密集型操作,缓存可以减少 95%+ 的 CPU 占用 + // scryptSync 是 CPU 密集型操作,缓存可以减少 95%+ 的 CPU 密集型操作 this._encryptionKeyCache = null + + // 🔄 解密结果缓存,提高解密性能 + this._decryptCache = new LRUCache(500) + + // 🧹 定期清理缓存(每10分钟) + setInterval( + () => { + this._decryptCache.cleanup() + logger.info( + '🧹 Claude Console decrypt cache cleanup completed', + this._decryptCache.getStats() + ) + }, + 10 * 60 * 1000 + ) } // 🏢 创建Claude Console账户 @@ -516,6 +532,13 @@ class ClaudeConsoleAccountService { return '' } + // 🎯 检查缓存 + const cacheKey = crypto.createHash('sha256').update(encryptedData).digest('hex') + const cached = this._decryptCache.get(cacheKey) + if (cached !== undefined) { + return cached + } + try { if (encryptedData.includes(':')) { const parts = encryptedData.split(':') @@ -527,6 +550,15 @@ class ClaudeConsoleAccountService { const decipher = crypto.createDecipheriv(this.ENCRYPTION_ALGORITHM, key, iv) let decrypted = decipher.update(encrypted, 'hex', 'utf8') decrypted += decipher.final('utf8') + + // 💾 存入缓存(5分钟过期) + this._decryptCache.set(cacheKey, decrypted, 5 * 60 * 1000) + + // 📊 定期打印缓存统计 + if ((this._decryptCache.hits + this._decryptCache.misses) % 1000 === 0) { + this._decryptCache.printStats() + } + return decrypted } } diff --git a/src/services/geminiAccountService.js b/src/services/geminiAccountService.js index 050eac9f..5b71d566 100644 --- a/src/services/geminiAccountService.js +++ b/src/services/geminiAccountService.js @@ -13,6 +13,7 @@ const { logRefreshSkipped } = require('../utils/tokenRefreshLogger') const tokenRefreshService = require('./tokenRefreshService') +const LRUCache = require('../utils/lruCache') // Gemini CLI OAuth 配置 - 这些是公开的 Gemini CLI 凭据 const OAUTH_CLIENT_ID = '681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com' @@ -24,9 +25,20 @@ const ALGORITHM = 'aes-256-cbc' const ENCRYPTION_SALT = 'gemini-account-salt' const IV_LENGTH = 16 +// 🚀 性能优化:缓存派生的加密密钥,避免每次重复计算 +// scryptSync 是 CPU 密集型操作,缓存可以减少 95%+ 的 CPU 占用 +let _encryptionKeyCache = null + +// 🔄 解密结果缓存,提高解密性能 +const decryptCache = new LRUCache(500) + // 生成加密密钥(使用与 claudeAccountService 相同的方法) function generateEncryptionKey() { - return crypto.scryptSync(config.security.encryptionKey, ENCRYPTION_SALT, 32) + if (!_encryptionKeyCache) { + _encryptionKeyCache = crypto.scryptSync(config.security.encryptionKey, ENCRYPTION_SALT, 32) + logger.info('🔑 Gemini encryption key derived and cached for performance optimization') + } + return _encryptionKeyCache } // Gemini 账户键前缀 @@ -52,6 +64,14 @@ function decrypt(text) { if (!text) { return '' } + + // 🎯 检查缓存 + const cacheKey = crypto.createHash('sha256').update(text).digest('hex') + const cached = decryptCache.get(cacheKey) + if (cached !== undefined) { + return cached + } + try { const key = generateEncryptionKey() // IV 是固定长度的 32 个十六进制字符(16 字节) @@ -63,13 +83,32 @@ function decrypt(text) { const decipher = crypto.createDecipheriv(ALGORITHM, key, iv) let decrypted = decipher.update(encryptedText) decrypted = Buffer.concat([decrypted, decipher.final()]) - return decrypted.toString() + const result = decrypted.toString() + + // 💾 存入缓存(5分钟过期) + decryptCache.set(cacheKey, result, 5 * 60 * 1000) + + // 📊 定期打印缓存统计 + if ((decryptCache.hits + decryptCache.misses) % 1000 === 0) { + decryptCache.printStats() + } + + return result } catch (error) { logger.error('Decryption error:', error) return '' } } +// 🧹 定期清理缓存(每10分钟) +setInterval( + () => { + decryptCache.cleanup() + logger.info('🧹 Gemini decrypt cache cleanup completed', decryptCache.getStats()) + }, + 10 * 60 * 1000 +) + // 创建 OAuth2 客户端 function createOAuth2Client(redirectUri = null) { // 如果没有提供 redirectUri,使用默认值 @@ -1248,6 +1287,10 @@ module.exports = { getOnboardTier, onboardUser, setupUser, + encrypt, + decrypt, + generateEncryptionKey, + decryptCache, // 暴露缓存对象以便测试和监控 countTokens, generateContent, generateContentStream, diff --git a/src/services/openaiAccountService.js b/src/services/openaiAccountService.js index a1265295..5326abb2 100644 --- a/src/services/openaiAccountService.js +++ b/src/services/openaiAccountService.js @@ -14,6 +14,7 @@ const { logTokenUsage, logRefreshSkipped } = require('../utils/tokenRefreshLogger') +const LRUCache = require('../utils/lruCache') // const tokenRefreshService = require('./tokenRefreshService') // 加密相关常量 @@ -21,9 +22,20 @@ const ALGORITHM = 'aes-256-cbc' const ENCRYPTION_SALT = 'openai-account-salt' const IV_LENGTH = 16 +// 🚀 性能优化:缓存派生的加密密钥,避免每次重复计算 +// scryptSync 是 CPU 密集型操作,缓存可以减少 95%+ 的 CPU 占用 +let _encryptionKeyCache = null + +// 🔄 解密结果缓存,提高解密性能 +const decryptCache = new LRUCache(500) + // 生成加密密钥(使用与 claudeAccountService 相同的方法) function generateEncryptionKey() { - return crypto.scryptSync(config.security.encryptionKey, ENCRYPTION_SALT, 32) + if (!_encryptionKeyCache) { + _encryptionKeyCache = crypto.scryptSync(config.security.encryptionKey, ENCRYPTION_SALT, 32) + logger.info('🔑 OpenAI encryption key derived and cached for performance optimization') + } + return _encryptionKeyCache } // OpenAI 账户键前缀 @@ -49,6 +61,14 @@ function decrypt(text) { if (!text) { return '' } + + // 🎯 检查缓存 + const cacheKey = crypto.createHash('sha256').update(text).digest('hex') + const cached = decryptCache.get(cacheKey) + if (cached !== undefined) { + return cached + } + try { const key = generateEncryptionKey() // IV 是固定长度的 32 个十六进制字符(16 字节) @@ -60,13 +80,32 @@ function decrypt(text) { const decipher = crypto.createDecipheriv(ALGORITHM, key, iv) let decrypted = decipher.update(encryptedText) decrypted = Buffer.concat([decrypted, decipher.final()]) - return decrypted.toString() + const result = decrypted.toString() + + // 💾 存入缓存(5分钟过期) + decryptCache.set(cacheKey, result, 5 * 60 * 1000) + + // 📊 定期打印缓存统计 + if ((decryptCache.hits + decryptCache.misses) % 1000 === 0) { + decryptCache.printStats() + } + + return result } catch (error) { logger.error('Decryption error:', error) return '' } } +// 🧹 定期清理缓存(每10分钟) +setInterval( + () => { + decryptCache.cleanup() + logger.info('🧹 OpenAI decrypt cache cleanup completed', decryptCache.getStats()) + }, + 10 * 60 * 1000 +) + // 刷新访问令牌 async function refreshAccessToken(refreshToken, proxy = null) { try { @@ -693,5 +732,7 @@ module.exports = { updateAccountUsage, recordUsage, // 别名,指向updateAccountUsage encrypt, - decrypt + decrypt, + generateEncryptionKey, + decryptCache // 暴露缓存对象以便测试和监控 } diff --git a/src/utils/cacheMonitor.js b/src/utils/cacheMonitor.js new file mode 100644 index 00000000..ab24bdfc --- /dev/null +++ b/src/utils/cacheMonitor.js @@ -0,0 +1,294 @@ +/** + * 缓存监控和管理工具 + * 提供统一的缓存监控、统计和安全清理功能 + */ + +const logger = require('./logger') +const crypto = require('crypto') + +class CacheMonitor { + constructor() { + this.monitors = new Map() // 存储所有被监控的缓存实例 + this.startTime = Date.now() + this.totalHits = 0 + this.totalMisses = 0 + this.totalEvictions = 0 + + // 🔒 安全配置 + this.securityConfig = { + maxCacheAge: 15 * 60 * 1000, // 最大缓存年龄 15 分钟 + forceCleanupInterval: 30 * 60 * 1000, // 强制清理间隔 30 分钟 + memoryThreshold: 100 * 1024 * 1024, // 内存阈值 100MB + sensitiveDataPatterns: [/password/i, /token/i, /secret/i, /key/i, /credential/i] + } + + // 🧹 定期执行安全清理 + this.setupSecurityCleanup() + + // 📊 定期报告统计信息 + this.setupPeriodicReporting() + } + + /** + * 注册缓存实例进行监控 + * @param {string} name - 缓存名称 + * @param {LRUCache} cache - 缓存实例 + */ + registerCache(name, cache) { + if (this.monitors.has(name)) { + logger.warn(`⚠️ Cache ${name} is already registered, updating reference`) + } + + this.monitors.set(name, { + cache, + registeredAt: Date.now(), + lastCleanup: Date.now(), + totalCleanups: 0 + }) + + logger.info(`📦 Registered cache for monitoring: ${name}`) + } + + /** + * 获取所有缓存的综合统计 + */ + getGlobalStats() { + const stats = { + uptime: Math.floor((Date.now() - this.startTime) / 1000), // 秒 + cacheCount: this.monitors.size, + totalSize: 0, + totalHits: 0, + totalMisses: 0, + totalEvictions: 0, + averageHitRate: 0, + caches: {} + } + + for (const [name, monitor] of this.monitors) { + const cacheStats = monitor.cache.getStats() + stats.totalSize += cacheStats.size + stats.totalHits += cacheStats.hits + stats.totalMisses += cacheStats.misses + stats.totalEvictions += cacheStats.evictions + + stats.caches[name] = { + ...cacheStats, + lastCleanup: new Date(monitor.lastCleanup).toISOString(), + totalCleanups: monitor.totalCleanups, + age: Math.floor((Date.now() - monitor.registeredAt) / 1000) // 秒 + } + } + + const totalRequests = stats.totalHits + stats.totalMisses + stats.averageHitRate = + totalRequests > 0 ? ((stats.totalHits / totalRequests) * 100).toFixed(2) + '%' : '0%' + + return stats + } + + /** + * 🔒 执行安全清理 + * 清理过期数据和潜在的敏感信息 + */ + performSecurityCleanup() { + logger.info('🔒 Starting security cleanup for all caches') + + for (const [name, monitor] of this.monitors) { + try { + const cache = monitor.cache + const beforeSize = cache.cache.size + + // 执行常规清理 + cache.cleanup() + + // 检查缓存年龄,如果太老则完全清空 + const cacheAge = Date.now() - monitor.registeredAt + if (cacheAge > this.securityConfig.maxCacheAge * 2) { + logger.warn( + `⚠️ Cache ${name} is too old (${Math.floor(cacheAge / 60000)}min), performing full clear` + ) + cache.clear() + } + + monitor.lastCleanup = Date.now() + monitor.totalCleanups++ + + const afterSize = cache.cache.size + if (beforeSize !== afterSize) { + logger.info(`🧹 Cache ${name}: Cleaned ${beforeSize - afterSize} items`) + } + } catch (error) { + logger.error(`❌ Error cleaning cache ${name}:`, error) + } + } + } + + /** + * 📊 生成详细报告 + */ + generateReport() { + const stats = this.getGlobalStats() + + logger.info('═══════════════════════════════════════════') + logger.info('📊 Cache System Performance Report') + logger.info('═══════════════════════════════════════════') + logger.info(`⏱️ Uptime: ${this.formatUptime(stats.uptime)}`) + logger.info(`📦 Active Caches: ${stats.cacheCount}`) + logger.info(`📈 Total Cache Size: ${stats.totalSize} items`) + logger.info(`🎯 Global Hit Rate: ${stats.averageHitRate}`) + logger.info(`✅ Total Hits: ${stats.totalHits.toLocaleString()}`) + logger.info(`❌ Total Misses: ${stats.totalMisses.toLocaleString()}`) + logger.info(`🗑️ Total Evictions: ${stats.totalEvictions.toLocaleString()}`) + logger.info('───────────────────────────────────────────') + + // 详细的每个缓存统计 + for (const [name, cacheStats] of Object.entries(stats.caches)) { + logger.info(`\n📦 ${name}:`) + logger.info( + ` Size: ${cacheStats.size}/${cacheStats.maxSize} | Hit Rate: ${cacheStats.hitRate}` + ) + logger.info( + ` Hits: ${cacheStats.hits} | Misses: ${cacheStats.misses} | Evictions: ${cacheStats.evictions}` + ) + logger.info( + ` Age: ${this.formatUptime(cacheStats.age)} | Cleanups: ${cacheStats.totalCleanups}` + ) + } + logger.info('═══════════════════════════════════════════') + } + + /** + * 🧹 设置定期安全清理 + */ + setupSecurityCleanup() { + // 每 10 分钟执行一次安全清理 + setInterval( + () => { + this.performSecurityCleanup() + }, + 10 * 60 * 1000 + ) + + // 每 30 分钟强制完整清理 + setInterval(() => { + logger.warn('⚠️ Performing forced complete cleanup for security') + for (const [name, monitor] of this.monitors) { + monitor.cache.clear() + logger.info(`🗑️ Force cleared cache: ${name}`) + } + }, this.securityConfig.forceCleanupInterval) + } + + /** + * 📊 设置定期报告 + */ + setupPeriodicReporting() { + // 每 5 分钟生成一次简单统计 + setInterval( + () => { + const stats = this.getGlobalStats() + logger.info( + `📊 Quick Stats - Caches: ${stats.cacheCount}, Size: ${stats.totalSize}, Hit Rate: ${stats.averageHitRate}` + ) + }, + 5 * 60 * 1000 + ) + + // 每 30 分钟生成一次详细报告 + setInterval( + () => { + this.generateReport() + }, + 30 * 60 * 1000 + ) + } + + /** + * 格式化运行时间 + */ + formatUptime(seconds) { + const hours = Math.floor(seconds / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const secs = seconds % 60 + + if (hours > 0) { + return `${hours}h ${minutes}m ${secs}s` + } else if (minutes > 0) { + return `${minutes}m ${secs}s` + } else { + return `${secs}s` + } + } + + /** + * 🔐 生成安全的缓存键 + * 使用 SHA-256 哈希避免暴露原始数据 + */ + static generateSecureCacheKey(data) { + return crypto.createHash('sha256').update(data).digest('hex') + } + + /** + * 🛡️ 验证缓存数据安全性 + * 检查是否包含敏感信息 + */ + validateCacheSecurity(data) { + const dataStr = typeof data === 'string' ? data : JSON.stringify(data) + + for (const pattern of this.securityConfig.sensitiveDataPatterns) { + if (pattern.test(dataStr)) { + logger.warn('⚠️ Potential sensitive data detected in cache') + return false + } + } + + return true + } + + /** + * 💾 获取内存使用估算 + */ + estimateMemoryUsage() { + let totalBytes = 0 + + for (const [name, monitor] of this.monitors) { + const cache = monitor.cache.cache + for (const [key, item] of cache) { + // 粗略估算:key 长度 + value 序列化长度 + totalBytes += key.length * 2 // UTF-16 + totalBytes += JSON.stringify(item).length * 2 + } + } + + return { + bytes: totalBytes, + mb: (totalBytes / (1024 * 1024)).toFixed(2), + warning: totalBytes > this.securityConfig.memoryThreshold + } + } + + /** + * 🚨 紧急清理 + * 在内存压力大时使用 + */ + emergencyCleanup() { + logger.error('🚨 EMERGENCY CLEANUP INITIATED') + + for (const [name, monitor] of this.monitors) { + const cache = monitor.cache + const beforeSize = cache.cache.size + + // 清理一半的缓存项(LRU 会保留最近使用的) + const targetSize = Math.floor(cache.maxSize / 2) + while (cache.cache.size > targetSize) { + const firstKey = cache.cache.keys().next().value + cache.cache.delete(firstKey) + } + + logger.warn(`🚨 Emergency cleaned ${name}: ${beforeSize} -> ${cache.cache.size} items`) + } + } +} + +// 导出单例 +module.exports = new CacheMonitor() diff --git a/src/utils/lruCache.js b/src/utils/lruCache.js new file mode 100644 index 00000000..0fd5f008 --- /dev/null +++ b/src/utils/lruCache.js @@ -0,0 +1,134 @@ +/** + * LRU (Least Recently Used) 缓存实现 + * 用于缓存解密结果,提高性能同时控制内存使用 + */ +class LRUCache { + constructor(maxSize = 500) { + this.maxSize = maxSize + this.cache = new Map() + this.hits = 0 + this.misses = 0 + this.evictions = 0 + this.lastCleanup = Date.now() + this.cleanupInterval = 5 * 60 * 1000 // 5分钟清理一次过期项 + } + + /** + * 获取缓存值 + * @param {string} key - 缓存键 + * @returns {*} 缓存的值,如果不存在则返回 undefined + */ + get(key) { + // 定期清理 + if (Date.now() - this.lastCleanup > this.cleanupInterval) { + this.cleanup() + } + + const item = this.cache.get(key) + if (!item) { + this.misses++ + return undefined + } + + // 检查是否过期 + if (item.expiry && Date.now() > item.expiry) { + this.cache.delete(key) + this.misses++ + return undefined + } + + // 更新访问时间,将元素移到最后(最近使用) + this.cache.delete(key) + this.cache.set(key, { + ...item, + lastAccessed: Date.now() + }) + + this.hits++ + return item.value + } + + /** + * 设置缓存值 + * @param {string} key - 缓存键 + * @param {*} value - 要缓存的值 + * @param {number} ttl - 生存时间(毫秒),默认5分钟 + */ + set(key, value, ttl = 5 * 60 * 1000) { + // 如果缓存已满,删除最少使用的项 + if (this.cache.size >= this.maxSize && !this.cache.has(key)) { + const firstKey = this.cache.keys().next().value + this.cache.delete(firstKey) + this.evictions++ + } + + this.cache.set(key, { + value, + createdAt: Date.now(), + lastAccessed: Date.now(), + expiry: ttl ? Date.now() + ttl : null + }) + } + + /** + * 清理过期项 + */ + cleanup() { + const now = Date.now() + let cleanedCount = 0 + + for (const [key, item] of this.cache.entries()) { + if (item.expiry && now > item.expiry) { + this.cache.delete(key) + cleanedCount++ + } + } + + this.lastCleanup = now + if (cleanedCount > 0) { + console.log(`🧹 LRU Cache: Cleaned ${cleanedCount} expired items`) + } + } + + /** + * 清空缓存 + */ + clear() { + const size = this.cache.size + this.cache.clear() + this.hits = 0 + this.misses = 0 + this.evictions = 0 + console.log(`🗑️ LRU Cache: Cleared ${size} items`) + } + + /** + * 获取缓存统计信息 + */ + getStats() { + const total = this.hits + this.misses + const hitRate = total > 0 ? ((this.hits / total) * 100).toFixed(2) : 0 + + return { + size: this.cache.size, + maxSize: this.maxSize, + hits: this.hits, + misses: this.misses, + evictions: this.evictions, + hitRate: `${hitRate}%`, + total + } + } + + /** + * 打印缓存统计信息 + */ + printStats() { + const stats = this.getStats() + console.log( + `📊 LRU Cache Stats: Size: ${stats.size}/${stats.maxSize}, Hit Rate: ${stats.hitRate}, Hits: ${stats.hits}, Misses: ${stats.misses}, Evictions: ${stats.evictions}` + ) + } +} + +module.exports = LRUCache