/** * Redis缓存服务 * @file cacheService.js * @description 提供高性能Redis缓存功能,优化数据访问速度 */ const redis = require('redis'); const logger = require('../utils/logger'); /** * 缓存配置 */ const CACHE_CONFIG = { host: process.env.REDIS_HOST || 'localhost', port: process.env.REDIS_PORT || 6379, password: process.env.REDIS_PASSWORD || '', db: process.env.REDIS_DB || 0, // 默认TTL配置(秒) ttl: { short: 5 * 60, // 5分钟 medium: 30 * 60, // 30分钟 long: 2 * 60 * 60, // 2小时 daily: 24 * 60 * 60 // 24小时 }, // 键名前缀 prefix: 'nxxm:', // 连接配置 connect_timeout: 10000, command_timeout: 5000, retry_unfulfilled_commands: true, retry_delay_on_failure: 100, max_retry_delay: 3000 }; /** * Redis缓存服务类 */ class CacheService { constructor() { this.client = null; this.isConnected = false; this.stats = { hits: 0, misses: 0, errors: 0, totalOperations: 0 }; } /** * 初始化Redis连接 */ async init() { try { this.client = redis.createClient({ url: `redis://${CACHE_CONFIG.host}:${CACHE_CONFIG.port}`, password: CACHE_CONFIG.password || undefined, database: CACHE_CONFIG.db, socket: { connectTimeout: CACHE_CONFIG.connect_timeout, commandTimeout: CACHE_CONFIG.command_timeout, reconnectStrategy: (retries) => { if (retries > 10) { logger.error('Redis重连次数超过限制,停止重连'); return false; } return Math.min(retries * 100, 3000); } } }); // 事件监听 this.client.on('connect', () => { logger.info('Redis连接已建立'); this.isConnected = true; }); this.client.on('ready', () => { logger.info('Redis客户端已就绪'); }); this.client.on('error', (error) => { logger.error('Redis连接错误:', error); this.isConnected = false; this.stats.errors++; }); this.client.on('end', () => { logger.warn('Redis连接已断开'); this.isConnected = false; }); this.client.on('reconnecting', () => { logger.info('正在重新连接Redis...'); }); // 连接到Redis await this.client.connect(); logger.info('Redis缓存服务初始化成功'); } catch (error) { logger.error('Redis缓存服务初始化失败:', error); this.isConnected = false; throw error; } } /** * 生成缓存键名 * @param {string} key 键名 * @param {string} namespace 命名空间 * @returns {string} 完整键名 */ generateKey(key, namespace = 'default') { return `${CACHE_CONFIG.prefix}${namespace}:${key}`; } /** * 设置缓存 * @param {string} key 键名 * @param {*} value 值 * @param {number} ttl 过期时间(秒) * @param {string} namespace 命名空间 * @returns {boolean} 设置结果 */ async set(key, value, ttl = CACHE_CONFIG.ttl.medium, namespace = 'default') { if (!this.isConnected) { logger.warn('Redis未连接,缓存设置跳过'); return false; } try { const fullKey = this.generateKey(key, namespace); const serializedValue = JSON.stringify(value); await this.client.setEx(fullKey, ttl, serializedValue); this.stats.totalOperations++; logger.debug(`缓存设置成功: ${fullKey}, TTL: ${ttl}s`); return true; } catch (error) { logger.error(`缓存设置失败 ${key}:`, error); this.stats.errors++; return false; } } /** * 获取缓存 * @param {string} key 键名 * @param {string} namespace 命名空间 * @returns {*} 缓存值或null */ async get(key, namespace = 'default') { if (!this.isConnected) { logger.warn('Redis未连接,缓存获取跳过'); return null; } try { const fullKey = this.generateKey(key, namespace); const value = await this.client.get(fullKey); this.stats.totalOperations++; if (value === null) { this.stats.misses++; logger.debug(`缓存未命中: ${fullKey}`); return null; } this.stats.hits++; logger.debug(`缓存命中: ${fullKey}`); return JSON.parse(value); } catch (error) { logger.error(`缓存获取失败 ${key}:`, error); this.stats.errors++; return null; } } /** * 删除缓存 * @param {string} key 键名 * @param {string} namespace 命名空间 * @returns {boolean} 删除结果 */ async del(key, namespace = 'default') { if (!this.isConnected) { return false; } try { const fullKey = this.generateKey(key, namespace); const result = await this.client.del(fullKey); this.stats.totalOperations++; logger.debug(`缓存删除: ${fullKey}, 结果: ${result}`); return result > 0; } catch (error) { logger.error(`缓存删除失败 ${key}:`, error); this.stats.errors++; return false; } } /** * 检查缓存是否存在 * @param {string} key 键名 * @param {string} namespace 命名空间 * @returns {boolean} 是否存在 */ async exists(key, namespace = 'default') { if (!this.isConnected) { return false; } try { const fullKey = this.generateKey(key, namespace); const result = await this.client.exists(fullKey); this.stats.totalOperations++; return result === 1; } catch (error) { logger.error(`缓存存在检查失败 ${key}:`, error); this.stats.errors++; return false; } } /** * 设置过期时间 * @param {string} key 键名 * @param {number} ttl 过期时间(秒) * @param {string} namespace 命名空间 * @returns {boolean} 设置结果 */ async expire(key, ttl, namespace = 'default') { if (!this.isConnected) { return false; } try { const fullKey = this.generateKey(key, namespace); const result = await this.client.expire(fullKey, ttl); this.stats.totalOperations++; return result === 1; } catch (error) { logger.error(`设置过期时间失败 ${key}:`, error); this.stats.errors++; return false; } } /** * 获取剩余过期时间 * @param {string} key 键名 * @param {string} namespace 命名空间 * @returns {number} 剩余秒数,-1表示永不过期,-2表示键不存在 */ async ttl(key, namespace = 'default') { if (!this.isConnected) { return -2; } try { const fullKey = this.generateKey(key, namespace); const result = await this.client.ttl(fullKey); this.stats.totalOperations++; return result; } catch (error) { logger.error(`获取过期时间失败 ${key}:`, error); this.stats.errors++; return -2; } } /** * 清空指定命名空间的所有缓存 * @param {string} namespace 命名空间 * @returns {number} 删除的键数量 */ async clearNamespace(namespace) { if (!this.isConnected) { return 0; } try { const pattern = `${CACHE_CONFIG.prefix}${namespace}:*`; const keys = await this.client.keys(pattern); if (keys.length === 0) { return 0; } const result = await this.client.del(keys); this.stats.totalOperations++; logger.info(`清空命名空间 ${namespace}: 删除了 ${result} 个键`); return result; } catch (error) { logger.error(`清空命名空间失败 ${namespace}:`, error); this.stats.errors++; return 0; } } /** * 批量设置缓存 * @param {Object} keyValuePairs 键值对 * @param {number} ttl 过期时间 * @param {string} namespace 命名空间 * @returns {boolean} 设置结果 */ async mSet(keyValuePairs, ttl = CACHE_CONFIG.ttl.medium, namespace = 'default') { if (!this.isConnected) { return false; } try { const pipeline = this.client.multi(); Object.entries(keyValuePairs).forEach(([key, value]) => { const fullKey = this.generateKey(key, namespace); const serializedValue = JSON.stringify(value); pipeline.setEx(fullKey, ttl, serializedValue); }); await pipeline.exec(); this.stats.totalOperations += Object.keys(keyValuePairs).length; logger.debug(`批量缓存设置成功: ${Object.keys(keyValuePairs).length} 个键`); return true; } catch (error) { logger.error('批量缓存设置失败:', error); this.stats.errors++; return false; } } /** * 批量获取缓存 * @param {Array} keys 键名数组 * @param {string} namespace 命名空间 * @returns {Object} 键值对结果 */ async mGet(keys, namespace = 'default') { if (!this.isConnected) { return {}; } try { const fullKeys = keys.map(key => this.generateKey(key, namespace)); const values = await this.client.mGet(fullKeys); const result = {}; keys.forEach((key, index) => { const value = values[index]; if (value !== null) { try { result[key] = JSON.parse(value); this.stats.hits++; } catch (error) { logger.warn(`解析缓存值失败 ${key}:`, error); result[key] = null; } } else { result[key] = null; this.stats.misses++; } }); this.stats.totalOperations += keys.length; return result; } catch (error) { logger.error('批量缓存获取失败:', error); this.stats.errors++; return {}; } } /** * 缓存包装器 - 自动缓存函数结果 * @param {string} key 缓存键 * @param {Function} fn 异步函数 * @param {number} ttl 过期时间 * @param {string} namespace 命名空间 * @returns {*} 函数结果 */ async wrap(key, fn, ttl = CACHE_CONFIG.ttl.medium, namespace = 'default') { // 首先尝试从缓存获取 const cached = await this.get(key, namespace); if (cached !== null) { return cached; } try { // 缓存未命中,执行函数 const result = await fn(); // 将结果存入缓存 await this.set(key, result, ttl, namespace); return result; } catch (error) { logger.error(`缓存包装器执行失败 ${key}:`, error); throw error; } } /** * 获取缓存统计信息 * @returns {Object} 统计信息 */ getStats() { const hitRate = this.stats.totalOperations > 0 ? (this.stats.hits / (this.stats.hits + this.stats.misses)) * 100 : 0; return { ...this.stats, hitRate: hitRate.toFixed(2) + '%', isConnected: this.isConnected, config: { host: CACHE_CONFIG.host, port: CACHE_CONFIG.port, db: CACHE_CONFIG.db } }; } /** * 获取Redis服务器信息 * @returns {Object} 服务器信息 */ async getServerInfo() { if (!this.isConnected) { return null; } try { const info = await this.client.info(); const memory = await this.client.info('memory'); const stats = await this.client.info('stats'); return { version: this.extractInfoValue(info, 'redis_version'), uptime: this.extractInfoValue(info, 'uptime_in_seconds'), memory: { used: this.extractInfoValue(memory, 'used_memory_human'), peak: this.extractInfoValue(memory, 'used_memory_peak_human'), fragmentation: this.extractInfoValue(memory, 'mem_fragmentation_ratio') }, stats: { connections: this.extractInfoValue(stats, 'total_connections_received'), commands: this.extractInfoValue(stats, 'total_commands_processed'), hits: this.extractInfoValue(stats, 'keyspace_hits'), misses: this.extractInfoValue(stats, 'keyspace_misses') } }; } catch (error) { logger.error('获取Redis服务器信息失败:', error); return null; } } /** * 从INFO字符串中提取值 * @private */ extractInfoValue(infoString, key) { const regex = new RegExp(`${key}:(.+)`); const match = infoString.match(regex); return match ? match[1].trim() : ''; } /** * 健康检查 * @returns {Object} 健康状态 */ async healthCheck() { try { if (!this.isConnected) { return { status: 'unhealthy', message: 'Redis连接断开', timestamp: new Date() }; } // 执行简单的ping测试 const pong = await this.client.ping(); if (pong === 'PONG') { return { status: 'healthy', message: 'Redis连接正常', stats: this.getStats(), timestamp: new Date() }; } else { return { status: 'unhealthy', message: 'Redis ping响应异常', timestamp: new Date() }; } } catch (error) { logger.error('Redis健康检查失败:', error); return { status: 'unhealthy', message: 'Redis健康检查失败', error: error.message, timestamp: new Date() }; } } /** * 关闭连接 */ async close() { try { if (this.client && this.isConnected) { await this.client.quit(); logger.info('Redis连接已关闭'); } } catch (error) { logger.error('关闭Redis连接失败:', error); } } /** * 清空所有缓存 * @returns {boolean} 清空结果 */ async flushAll() { if (!this.isConnected) { return false; } try { await this.client.flushDb(); logger.info('Redis缓存已清空'); // 重置统计 this.stats = { hits: 0, misses: 0, errors: 0, totalOperations: 0 }; return true; } catch (error) { logger.error('清空Redis缓存失败:', error); this.stats.errors++; return false; } } } /** * 缓存键名常量 */ const CACHE_KEYS = { // 用户相关 USER_LIST: 'users:list', USER_PROFILE: (id) => `users:profile:${id}`, USER_PERMISSIONS: (id) => `users:permissions:${id}`, // 农场相关 FARM_LIST: 'farms:list', FARM_DETAIL: (id) => `farms:detail:${id}`, FARM_ANIMALS: (id) => `farms:animals:${id}`, FARM_DEVICES: (id) => `farms:devices:${id}`, // 设备相关 DEVICE_LIST: 'devices:list', DEVICE_STATUS: (id) => `devices:status:${id}`, DEVICE_METRICS: (id) => `devices:metrics:${id}`, // 统计数据 STATS_DASHBOARD: 'stats:dashboard', STATS_FARMS: 'stats:farms', STATS_DEVICES: 'stats:devices', STATS_ANIMALS: 'stats:animals', STATS_ALERTS: 'stats:alerts', // 系统配置 SYSTEM_CONFIG: 'system:config', MENU_PERMISSIONS: 'system:menus', // 搜索结果 SEARCH_RESULTS: (type, query) => `search:${type}:${Buffer.from(query).toString('base64')}` }; /** * 高级缓存功能 */ class AdvancedCache extends CacheService { /** * 缓存列表数据(带分页) * @param {string} key 基础键名 * @param {Array} data 数据数组 * @param {Object} pagination 分页信息 * @param {number} ttl 过期时间 */ async setListData(key, data, pagination = {}, ttl = CACHE_CONFIG.ttl.medium) { const cacheData = { data, pagination, timestamp: Date.now() }; return await this.set(key, cacheData, ttl, 'lists'); } /** * 获取列表数据 * @param {string} key 键名 * @returns {Object} 列表数据和分页信息 */ async getListData(key) { const cached = await this.get(key, 'lists'); if (cached && cached.data) { return { data: cached.data, pagination: cached.pagination || {}, fromCache: true, cacheTimestamp: cached.timestamp }; } return null; } /** * 智能缓存失效 * @param {string} entity 实体类型 * @param {string} operation 操作类型 * @param {number} entityId 实体ID */ async invalidateRelated(entity, operation, entityId = null) { const patterns = []; switch (entity) { case 'farm': patterns.push('farms:*', 'stats:*'); if (entityId) { patterns.push(`farms:detail:${entityId}`, `farms:animals:${entityId}`, `farms:devices:${entityId}`); } break; case 'device': patterns.push('devices:*', 'stats:*'); if (entityId) { patterns.push(`devices:status:${entityId}`, `devices:metrics:${entityId}`); } break; case 'animal': patterns.push('animals:*', 'stats:*'); break; case 'user': patterns.push('users:*'); if (entityId) { patterns.push(`users:profile:${entityId}`, `users:permissions:${entityId}`); } break; default: patterns.push('stats:*'); // 默认清理统计缓存 } let totalDeleted = 0; for (const pattern of patterns) { const deleted = await this.clearPattern(pattern); totalDeleted += deleted; } logger.info(`智能缓存失效: ${entity}/${operation}, 清理了 ${totalDeleted} 个缓存键`); return totalDeleted; } /** * 按模式清理缓存 * @private */ async clearPattern(pattern) { if (!this.isConnected) { return 0; } try { const fullPattern = `${CACHE_CONFIG.prefix}${pattern}`; const keys = await this.client.keys(fullPattern); if (keys.length === 0) { return 0; } const result = await this.client.del(keys); return result; } catch (error) { logger.error(`按模式清理缓存失败 ${pattern}:`, error); return 0; } } } // 创建缓存服务实例 const cacheService = new AdvancedCache(); // 优雅关闭处理 process.on('SIGINT', async () => { logger.info('收到SIGINT信号,正在关闭Redis连接...'); await cacheService.close(); process.exit(0); }); process.on('SIGTERM', async () => { logger.info('收到SIGTERM信号,正在关闭Redis连接...'); await cacheService.close(); process.exit(0); }); module.exports = { cacheService, CACHE_KEYS, CACHE_CONFIG };