import { Injectable, OnModuleInit, OnModuleDestroy, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import Redis from 'ioredis'; @Injectable() export class RedisService implements OnModuleInit, OnModuleDestroy { private readonly logger = new Logger(RedisService.name); private client: Redis; private _connected = false; constructor(private configService: ConfigService) {} async onModuleInit() { const url = this.configService.get('redis.url'); if (url) { this.client = new Redis(url, { lazyConnect: true, retryStrategy: () => null }); } else { this.client = new Redis({ host: this.configService.get('redis.host', 'localhost'), port: this.configService.get('redis.port', 6379), password: this.configService.get('redis.password'), db: this.configService.get('redis.db', 0), lazyConnect: true, retryStrategy: () => null, }); } this.client.on('connect', () => { this._connected = true; this.logger.log('Redis connected'); }); this.client.on('error', (err) => { this._connected = false; this.logger.warn(`Redis error: ${err.message}`); }); } async onModuleDestroy() { await this.client?.quit(); } isHealthy(): boolean { return this._connected && this.client?.status === 'ready'; } async get(key: string): Promise { return this.client.get(key); } async set(key: string, value: string, ttl?: number): Promise { if (ttl) { await this.client.set(key, value, 'EX', ttl); } else { await this.client.set(key, value); } } async del(key: string): Promise { await this.client.del(key); } async exists(key: string): Promise { return (await this.client.exists(key)) === 1; } async expire(key: string, ttl: number): Promise { await this.client.expire(key, ttl); } async ttl(key: string): Promise { return this.client.ttl(key); } async incr(key: string): Promise { return this.client.incr(key); } async setNx(key: string, value: string): Promise { return (await this.client.setnx(key, value)) === 1; } async lock(key: string, ttlSeconds: number): Promise { const token = Math.random().toString(36).substring(2); const result = await this.client.set(key, token, 'EX', ttlSeconds, 'NX'); return result === 'OK' ? token : null; } async unlock(key: string, token: string): Promise { const script = ` if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end `; const result = await this.client.eval(script, 1, key, token); return result === 1; } }