100 lines
2.7 KiB
TypeScript
100 lines
2.7 KiB
TypeScript
import { Injectable, Inject, OnModuleInit, OnModuleDestroy, Logger } from '@nestjs/common';
|
|
import Redis from 'ioredis';
|
|
|
|
interface RedisOptions {
|
|
host: string;
|
|
port: number;
|
|
password?: string;
|
|
db?: number;
|
|
}
|
|
|
|
@Injectable()
|
|
export class RedisService implements OnModuleInit, OnModuleDestroy {
|
|
private readonly logger = new Logger(RedisService.name);
|
|
private client: Redis;
|
|
|
|
constructor(@Inject('REDIS_OPTIONS') private readonly options: RedisOptions) {}
|
|
|
|
async onModuleInit() {
|
|
this.client = new Redis({
|
|
host: this.options.host,
|
|
port: this.options.port,
|
|
password: this.options.password,
|
|
db: this.options.db ?? 16,
|
|
retryStrategy: (times) => Math.min(times * 50, 2000),
|
|
});
|
|
|
|
this.client.on('error', (err) => this.logger.error('Redis error', err));
|
|
this.client.on('connect', () => this.logger.log('Connected to Redis'));
|
|
}
|
|
|
|
async onModuleDestroy() {
|
|
await this.client.quit();
|
|
}
|
|
|
|
getClient(): Redis {
|
|
return this.client;
|
|
}
|
|
|
|
async get(key: string): Promise<string | null> {
|
|
return this.client.get(key);
|
|
}
|
|
|
|
async set(key: string, value: string, ttlSeconds?: number): Promise<void> {
|
|
if (ttlSeconds) {
|
|
await this.client.setex(key, ttlSeconds, value);
|
|
} else {
|
|
await this.client.set(key, value);
|
|
}
|
|
}
|
|
|
|
async getJson<T>(key: string): Promise<T | null> {
|
|
const value = await this.get(key);
|
|
if (!value) return null;
|
|
try {
|
|
return JSON.parse(value) as T;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async setJson<T>(key: string, value: T, ttlSeconds?: number): Promise<void> {
|
|
await this.set(key, JSON.stringify(value), ttlSeconds);
|
|
}
|
|
|
|
async acquireLock(lockKey: string, ttlSeconds: number = 30): Promise<string | null> {
|
|
const lockValue = `${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
const ttlMs = Math.round(ttlSeconds * 1000);
|
|
const result = await this.client.set(lockKey, lockValue, 'PX', ttlMs, 'NX');
|
|
return result === 'OK' ? lockValue : null;
|
|
}
|
|
|
|
async releaseLock(lockKey: string, lockValue: string): Promise<boolean> {
|
|
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, lockKey, lockValue);
|
|
return result === 1;
|
|
}
|
|
|
|
async incr(key: string): Promise<number> {
|
|
return this.client.incr(key);
|
|
}
|
|
|
|
async incrByFloat(key: string, increment: number): Promise<string> {
|
|
return this.client.incrbyfloat(key, increment);
|
|
}
|
|
|
|
async keys(pattern: string): Promise<string[]> {
|
|
return this.client.keys(pattern);
|
|
}
|
|
|
|
async del(key: string): Promise<number> {
|
|
return this.client.del(key);
|
|
}
|
|
}
|