feat(iam): 角色权限管理 + 权限缓存 + 指标 + 鉴权中间件增强 + nextstep 文档

This commit is contained in:
SpecialX
2026-07-14 15:55:39 +08:00
parent e9d3030f2f
commit d260df864c
12 changed files with 2171 additions and 47 deletions

View File

@@ -1,5 +1,6 @@
import { Injectable } from "@nestjs/common";
import { getRedis } from "../../config/redis.js";
import { cacheMetrics } from "../observability/metrics.js";
const PERMISSION_CACHE_TTL_SECONDS = 300; // 5 分钟
@@ -22,14 +23,20 @@ export class PermissionCacheService {
async getPermissions(userId: string): Promise<string[] | null> {
const redis = getRedis();
const raw = await redis.get(PermissionCacheService.buildKey(userId));
if (!raw) return null;
if (!raw) {
cacheMetrics.recordMiss();
return null;
}
try {
const parsed = JSON.parse(raw) as unknown;
if (Array.isArray(parsed) && parsed.every((p) => typeof p === "string")) {
cacheMetrics.recordHit();
return parsed as string[];
}
cacheMetrics.recordMiss();
return null;
} catch {
cacheMetrics.recordMiss();
return null;
}
}
@@ -44,8 +51,9 @@ export class PermissionCacheService {
);
}
async invalidate(userId: string): Promise<void> {
async invalidate(userId: string, reason = "manual"): Promise<void> {
const redis = getRedis();
await redis.del(PermissionCacheService.buildKey(userId));
cacheMetrics.recordInvalidation(reason);
}
}

View File

@@ -24,4 +24,56 @@ registry.registerMetric(
// 这些指标无需业务代码埋点prom-client 自动采集
promClient.collectDefaultMetrics({ register: registry });
// Redis 权限缓存指标I3 裁决DB 驱动 + Redis 缓存可观测性)
registry.registerMetric(
new promClient.Counter({
name: "iam_permission_cache_hits_total",
help: "Total number of permission cache hits (Redis)",
}),
);
registry.registerMetric(
new promClient.Counter({
name: "iam_permission_cache_misses_total",
help: "Total number of permission cache misses (Redis)",
}),
);
registry.registerMetric(
new promClient.Counter({
name: "iam_permission_cache_invalidations_total",
help: "Total number of permission cache invalidations (Redis)",
labelNames: ["reason"],
}),
);
/**
* 缓存指标访问器:供 PermissionCacheService 使用.
* 避免在业务代码中直接操作 registry统一通过此门面.
*/
export const cacheMetrics = {
recordHit(): void {
const metric = registry.getSingleMetric("iam_permission_cache_hits_total");
if (metric && "inc" in metric) {
(metric as promClient.Counter).inc();
}
},
recordMiss(): void {
const metric = registry.getSingleMetric(
"iam_permission_cache_misses_total",
);
if (metric && "inc" in metric) {
(metric as promClient.Counter).inc();
}
},
recordInvalidation(reason: string): void {
const metric = registry.getSingleMetric(
"iam_permission_cache_invalidations_total",
);
if (metric && "inc" in metric) {
(metric as promClient.Counter).inc({ reason });
}
},
};
export { registry as metricsRegistry };