收口 Phase 0 三处「声明未生效」: - 三级鉴权落地:权限集存 Redis(key 前缀隔离多项目),@RequirePermissions 挂到全部受控接口,super_admin 角色绕过权限码与 data_scope - 审计/登录日志自动落库:@AuditLog 装饰器 + 拦截器异步写入(写操作+登录登出),登录成功/失败均记 login_log(脱敏) - seed 脚本:一键初始化租户 touyun/用户 touyun/超管 super_admin/全量资源树(48 资源全量授权) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
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<T>(key: string): Promise<T | null> {
|
||
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<void> {
|
||
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<void> {
|
||
await this.client.del(this.key(key));
|
||
}
|
||
|
||
/** 按前缀删除(SCAN 而非 KEYS,避免阻塞)。 */
|
||
async delByPrefix(prefix: string): Promise<void> {
|
||
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<void> {
|
||
await this.client.quit();
|
||
}
|
||
}
|