import { Injectable, OnModuleDestroy } from '@nestjs/common'; import { Redis } from 'ioredis'; /** * Redis 封装:统一 key 前缀(REDIS_KEY_PREFIX),支持 JSON 读写与按前缀删除。 * 多个 fork 项目共用一个 Redis 实例时,通过不同前缀隔离。 */ @Injectable() export class RedisService implements OnModuleDestroy { readonly prefix: string; private readonly client: Redis; constructor() { this.prefix = process.env.REDIS_KEY_PREFIX ?? 'sysmgr:'; this.client = new Redis({ host: process.env.REDIS_HOST ?? '127.0.0.1', port: Number(process.env.REDIS_PORT ?? 6379), db: Number(process.env.REDIS_DB ?? 0), password: process.env.REDIS_PASSWORD || undefined, }); } private key(k: string): string { return this.prefix + k; } async getJson(key: string): Promise { const raw = await this.client.get(this.key(key)); return raw ? (JSON.parse(raw) as T) : null; } async setJson(key: string, value: unknown, ttlSeconds?: number): Promise { const raw = JSON.stringify(value); if (ttlSeconds && ttlSeconds > 0) await this.client.set(this.key(key), raw, 'EX', ttlSeconds); else await this.client.set(this.key(key), raw); } async del(key: string): Promise { await this.client.del(this.key(key)); } /** 按前缀删除(SCAN 而非 KEYS,避免阻塞)。 */ async delByPrefix(prefix: string): Promise { const fullPrefix = this.prefix + prefix; let cursor = '0'; do { const [next, keys] = await this.client.scan(cursor, 'MATCH', `${fullPrefix}*`, 'COUNT', 200); cursor = next; if (keys.length > 0) await this.client.del(...keys); } while (cursor !== '0'); } async onModuleDestroy(): Promise { await this.client.quit(); } }