sysmgr-nest/src/common/redis/redis.service.ts
liuyun 7340658305 feat: enforce RBAC, audit/login logging, and seed script
收口 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>
2026-10-10 23:19:41 +08:00

57 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}
}