399 lines
11 KiB
TypeScript
399 lines
11 KiB
TypeScript
/**
|
||
* @file Redis服务类
|
||
* @author hotok
|
||
* @date 2025-06-29
|
||
* @lastEditor hotok
|
||
* @lastEditTime 2025-06-29
|
||
* @description 精简的Redis连接服务类,支持连接管理、状态跟踪和健康检查
|
||
*/
|
||
|
||
import { createClient } from 'redis';
|
||
import { redisConfig, getRedisUrl } from '@/config/redis.config';
|
||
import { Logger } from '@/plugins/logger/logger.service';
|
||
import type {
|
||
RedisConnectionStatus,
|
||
RedisConnectionInfo,
|
||
RedisHealthCheckResult
|
||
} from '@/type/redis.type';
|
||
|
||
/**
|
||
* Redis服务类
|
||
* 使用单例模式管理Redis连接
|
||
*/
|
||
export class RedisService {
|
||
/** 单例实例 */
|
||
private static instance: RedisService | null = null;
|
||
|
||
/** Redis客户端实例 */
|
||
private _client: any = null;
|
||
|
||
/** 连接状态信息 */
|
||
private _connectionInfo: RedisConnectionInfo;
|
||
|
||
/** 初始化标志 */
|
||
private _isInitialized = false;
|
||
|
||
/**
|
||
* 私有构造函数,防止外部实例化
|
||
*/
|
||
private constructor() {
|
||
this._connectionInfo = {
|
||
status: 'disconnected',
|
||
host: redisConfig.host,
|
||
port: redisConfig.port,
|
||
database: redisConfig.database,
|
||
connectName: redisConfig.connectName,
|
||
isConnected: false,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取单例实例
|
||
*/
|
||
public static getInstance(): RedisService {
|
||
if (!RedisService.instance) {
|
||
RedisService.instance = new RedisService();
|
||
}
|
||
return RedisService.instance;
|
||
}
|
||
|
||
/**
|
||
* 获取Redis客户端实例
|
||
*/
|
||
public get client(): any {
|
||
if (!this._client) {
|
||
throw new Error('Redis未初始化,请先调用 initialize() 方法');
|
||
}
|
||
return this._client;
|
||
}
|
||
|
||
/**
|
||
* 获取连接状态信息
|
||
*/
|
||
public get connectionInfo(): RedisConnectionInfo {
|
||
return { ...this._connectionInfo };
|
||
}
|
||
|
||
/**
|
||
* 检查是否已初始化
|
||
*/
|
||
public get isInitialized(): boolean {
|
||
return this._isInitialized;
|
||
}
|
||
|
||
/**
|
||
* 验证Redis配置
|
||
*/
|
||
private validateConfig(): void {
|
||
if (!redisConfig.host || !redisConfig.port) {
|
||
throw new Error('Redis配置无效:缺少host或port');
|
||
}
|
||
|
||
if (redisConfig.port < 1 || redisConfig.port > 65535) {
|
||
throw new Error(`Redis端口号无效: ${redisConfig.port}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新连接状态
|
||
*/
|
||
private updateConnectionStatus(status: RedisConnectionStatus, error?: string): void {
|
||
this._connectionInfo.status = status;
|
||
this._connectionInfo.error = error;
|
||
this._connectionInfo.isConnected = status === 'connected';
|
||
|
||
if (status === 'connected') {
|
||
this._connectionInfo.connectedAt = new Date();
|
||
this._connectionInfo.error = undefined;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化Redis连接
|
||
*/
|
||
public async initialize(): Promise<any> {
|
||
// 防止重复初始化
|
||
if (this._isInitialized && this._client) {
|
||
Logger.info('Redis 已初始化,返回现有实例');
|
||
return this._client;
|
||
}
|
||
|
||
try {
|
||
this.validateConfig();
|
||
this.updateConnectionStatus('connecting');
|
||
|
||
// 创建Redis客户端
|
||
this._client = createClient({
|
||
name: redisConfig.connectName,
|
||
username: redisConfig.username,
|
||
password: redisConfig.password,
|
||
database: redisConfig.database,
|
||
url: getRedisUrl(),
|
||
});
|
||
|
||
// 连接Redis
|
||
await this._client.connect();
|
||
|
||
// 测试连接
|
||
await this._client.ping();
|
||
|
||
this._isInitialized = true;
|
||
this.updateConnectionStatus('connected');
|
||
|
||
Logger.info({
|
||
message: 'Redis 初始化成功',
|
||
host: redisConfig.host,
|
||
port: redisConfig.port,
|
||
database: redisConfig.database,
|
||
connectName: redisConfig.connectName,
|
||
});
|
||
|
||
return this._client;
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||
this.updateConnectionStatus('error', errorMessage);
|
||
Logger.error(new Error(`Redis 初始化失败: ${errorMessage}`));
|
||
throw new Error(`Redis 初始化失败: ${errorMessage}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查连接状态
|
||
*/
|
||
public async checkConnection(): Promise<boolean> {
|
||
try {
|
||
if (!this._client || !this._connectionInfo.isConnected) {
|
||
return false;
|
||
}
|
||
|
||
await this._client.ping();
|
||
return true;
|
||
} catch (error) {
|
||
Logger.error(error instanceof Error ? error : new Error('Redis连接检查失败'));
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 执行健康检查
|
||
*/
|
||
public async healthCheck(): Promise<RedisHealthCheckResult> {
|
||
const startTime = Date.now();
|
||
|
||
try {
|
||
if (!this._client) {
|
||
return {
|
||
status: 'unhealthy',
|
||
responseTime: 0,
|
||
connectionInfo: this.connectionInfo,
|
||
error: 'Redis客户端未初始化',
|
||
};
|
||
}
|
||
|
||
// 执行ping测试
|
||
await this._client.ping();
|
||
const responseTime = Date.now() - startTime;
|
||
|
||
return {
|
||
status: 'healthy',
|
||
responseTime,
|
||
connectionInfo: this.connectionInfo,
|
||
};
|
||
} catch (error) {
|
||
const responseTime = Date.now() - startTime;
|
||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||
|
||
return {
|
||
status: 'unhealthy',
|
||
responseTime,
|
||
connectionInfo: this.connectionInfo,
|
||
error: errorMessage,
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 优雅关闭连接
|
||
*/
|
||
public async close(): Promise<void> {
|
||
try {
|
||
if (this._client && this._connectionInfo.isConnected) {
|
||
await this._client.quit();
|
||
this._client = null;
|
||
this.updateConnectionStatus('disconnected');
|
||
this._isInitialized = false;
|
||
Logger.info('Redis连接已关闭');
|
||
}
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||
Logger.error(new Error(`关闭Redis连接时出错: ${errorMessage}`));
|
||
throw new Error(`关闭Redis连接失败: ${errorMessage}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 重新连接
|
||
*/
|
||
public async reconnect(): Promise<any> {
|
||
Logger.info('正在重新连接Redis...');
|
||
|
||
// 先关闭现有连接
|
||
await this.close();
|
||
|
||
// 重新初始化连接
|
||
return await this.initialize();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ==============================================
|
||
* 主要导出 - 推荐使用的API
|
||
* ==============================================
|
||
*/
|
||
|
||
/**
|
||
* Redis服务单例实例
|
||
*
|
||
* @description 获取RedisService的单例实例,推荐的使用方式
|
||
* @example
|
||
* ```typescript
|
||
* import { redisService } from '@/plugins/redis/redis.service';
|
||
*
|
||
* // 初始化Redis
|
||
* await redisService.initialize();
|
||
*
|
||
* // 获取客户端实例
|
||
* const client = redisService.client;
|
||
*
|
||
* // 检查连接状态
|
||
* const isConnected = await redisService.checkConnection();
|
||
* ```
|
||
*/
|
||
export const redisService = RedisService.getInstance();
|
||
|
||
/**
|
||
* ==============================================
|
||
* 向后兼容导出 - 保持原有函数式API
|
||
* ==============================================
|
||
*/
|
||
|
||
/**
|
||
* 创建并初始化Redis连接
|
||
*
|
||
* @description 向后兼容的初始化方法,内部调用redisService.initialize()
|
||
* @returns {Promise<any>} 返回初始化后的Redis客户端实例
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* import { createRedisClient } from '@/plugins/redis/redis.service';
|
||
*
|
||
* const client = await createRedisClient();
|
||
* ```
|
||
*
|
||
* @deprecated 推荐使用 redisService.initialize() 替代
|
||
*/
|
||
export const createRedisClient = () => redisService.initialize();
|
||
|
||
/**
|
||
* 获取Redis连接状态信息
|
||
*
|
||
* @description 向后兼容的状态获取方法,内部调用redisService.connectionInfo
|
||
* @returns {RedisConnectionInfo} 返回Redis连接状态信息
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* import { getRedisConnectionInfo } from '@/plugins/redis/redis.service';
|
||
*
|
||
* const info = getRedisConnectionInfo();
|
||
* console.log(`Redis状态: ${info.status}`);
|
||
* ```
|
||
*
|
||
* @deprecated 推荐使用 redisService.connectionInfo 替代
|
||
*/
|
||
export const getRedisConnectionInfo = () => redisService.connectionInfo;
|
||
|
||
/**
|
||
* 检查Redis连接状态
|
||
*
|
||
* @description 向后兼容的连接检查方法,内部调用redisService.checkConnection()
|
||
* @returns {Promise<boolean>} 返回连接是否正常
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* import { checkRedisConnection } from '@/plugins/redis/redis.service';
|
||
*
|
||
* const isConnected = await checkRedisConnection();
|
||
* if (!isConnected) {
|
||
* console.log('Redis连接异常');
|
||
* }
|
||
* ```
|
||
*
|
||
* @deprecated 推荐使用 redisService.checkConnection() 替代
|
||
*/
|
||
export const checkRedisConnection = () => redisService.checkConnection();
|
||
|
||
/**
|
||
* 优雅关闭Redis连接
|
||
*
|
||
* @description 向后兼容的连接关闭方法,内部调用redisService.close()
|
||
* @returns {Promise<void>} 返回关闭操作的Promise
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* import { closeRedisConnection } from '@/plugins/redis/redis.service';
|
||
*
|
||
* // 应用关闭时清理资源
|
||
* process.on('SIGTERM', async () => {
|
||
* await closeRedisConnection();
|
||
* process.exit(0);
|
||
* });
|
||
* ```
|
||
*
|
||
* @deprecated 推荐使用 redisService.close() 替代
|
||
*/
|
||
export const closeRedisConnection = () => redisService.close();
|
||
|
||
/**
|
||
* 重新连接Redis
|
||
*
|
||
* @description 向后兼容的重连方法,内部调用redisService.reconnect()
|
||
* @returns {Promise<any>} 返回重新连接后的客户端实例
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* import { reconnectRedis } from '@/plugins/redis/redis.service';
|
||
*
|
||
* try {
|
||
* const client = await reconnectRedis();
|
||
* console.log('Redis重连成功');
|
||
* } catch (error) {
|
||
* console.error('Redis重连失败:', error);
|
||
* }
|
||
* ```
|
||
*
|
||
* @deprecated 推荐使用 redisService.reconnect() 替代
|
||
*/
|
||
export const reconnectRedis = () => redisService.reconnect();
|
||
|
||
/**
|
||
* 获取Redis客户端实例
|
||
*
|
||
* @description 向后兼容的客户端获取方法,内部调用redisService.client
|
||
* @returns {any} 返回Redis客户端实例
|
||
* @throws {Error} 如果Redis未初始化则抛出错误
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* import { redis } from '@/plugins/redis/redis.service';
|
||
*
|
||
* // 确保先初始化
|
||
* await createRedisClient();
|
||
*
|
||
* // 获取客户端实例
|
||
* const client = redis();
|
||
* const result = await client.get('key');
|
||
* ```
|
||
*
|
||
* @deprecated 推荐使用 redisService.client 替代
|
||
*/
|
||
export const redis = () => redisService.client;
|