修改管理后台

This commit is contained in:
shenquanyi
2025-09-12 20:08:42 +08:00
parent 39d61c6f9b
commit 80a24c2d60
286 changed files with 75316 additions and 9452 deletions

View File

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