api-server/src/infrastructure/redis/redis.service.ts

109 lines
3.0 KiB
TypeScript
Raw Normal View History

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<string>('redis.url');
if (url) {
this.client = new Redis(url, { lazyConnect: true, retryStrategy: () => null });
} else {
this.client = new Redis({
host: this.configService.get<string>('redis.host', 'localhost'),
port: this.configService.get<number>('redis.port', 6379),
password: this.configService.get<string>('redis.password'),
db: this.configService.get<number>('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}`);
});
try {
await this.client.connect();
} catch (err: any) {
this.logger.warn(`Redis connect failed: ${err.message}`);
}
}
async onModuleDestroy() {
await this.client?.quit();
}
isHealthy(): boolean {
return this._connected && this.client?.status === 'ready';
}
async get(key: string): Promise<string | null> {
return this.client.get(key);
}
async set(key: string, value: string, ttl?: number): Promise<void> {
if (ttl) {
await this.client.set(key, value, 'EX', ttl);
} else {
await this.client.set(key, value);
}
}
async del(key: string): Promise<void> {
await this.client.del(key);
}
async exists(key: string): Promise<boolean> {
return (await this.client.exists(key)) === 1;
}
async expire(key: string, ttl: number): Promise<void> {
await this.client.expire(key, ttl);
}
async ttl(key: string): Promise<number> {
return this.client.ttl(key);
}
async incr(key: string): Promise<number> {
return this.client.incr(key);
}
async setNx(key: string, value: string): Promise<boolean> {
return (await this.client.setnx(key, value)) === 1;
}
async lock(key: string, ttlSeconds: number): Promise<string | null> {
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 keys(pattern: string): Promise<string[]> {
return this.client.keys(pattern);
}
async unlock(key: string, token: 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, key, token);
return result === 1;
}
}