138 lines
3.6 KiB
TypeScript
138 lines
3.6 KiB
TypeScript
import { Controller, Get, HttpException, HttpStatus } from "@nestjs/common";
|
||
import { sql } from "drizzle-orm";
|
||
import { getDb } from "../../config/database.js";
|
||
import { getRedis } from "../../config/redis.js";
|
||
import { getJwtKeyPair } from "../../config/jwt.js";
|
||
|
||
const SERVICE_NAME = "iam";
|
||
|
||
interface DependencyCheck {
|
||
name: string;
|
||
status: "ok" | "error";
|
||
error?: string;
|
||
}
|
||
|
||
/**
|
||
* 健康检查端点。
|
||
*
|
||
* - GET /healthz:liveness,仅返回进程存活,不检查依赖
|
||
* - GET /readyz:readiness,检查 5 依赖(DB/Redis/Kafka/gRPC/JWKS),失败返回 503
|
||
*/
|
||
@Controller()
|
||
export class HealthController {
|
||
@Get("healthz")
|
||
liveness(): { status: string; service: string; timestamp: string } {
|
||
return {
|
||
status: "ok",
|
||
service: SERVICE_NAME,
|
||
timestamp: new Date().toISOString(),
|
||
};
|
||
}
|
||
|
||
@Get("readyz")
|
||
async readiness(): Promise<{
|
||
status: string;
|
||
service: string;
|
||
timestamp: string;
|
||
dependencies: DependencyCheck[];
|
||
}> {
|
||
const checks: DependencyCheck[] = [];
|
||
|
||
// 1. DB
|
||
checks.push(await this.checkDb());
|
||
|
||
// 2. Redis
|
||
checks.push(await this.checkRedis());
|
||
|
||
// 3. Kafka(检查 producer 连接状态——通过 ping)
|
||
checks.push(await this.checkKafka());
|
||
|
||
// 4. JWKS(检查密钥文件已加载)
|
||
checks.push(this.checkJwks());
|
||
|
||
// 5. gRPC(本进程内启动,进程存活即 gRPC 存活)
|
||
checks.push({ name: "grpc", status: "ok" });
|
||
|
||
const allOk = checks.every((c) => c.status === "ok");
|
||
if (!allOk) {
|
||
throw new HttpException(
|
||
{
|
||
status: "error",
|
||
service: SERVICE_NAME,
|
||
timestamp: new Date().toISOString(),
|
||
dependencies: checks,
|
||
},
|
||
HttpStatus.SERVICE_UNAVAILABLE,
|
||
);
|
||
}
|
||
|
||
return {
|
||
status: "ok",
|
||
service: SERVICE_NAME,
|
||
timestamp: new Date().toISOString(),
|
||
dependencies: checks,
|
||
};
|
||
}
|
||
|
||
private async checkDb(): Promise<DependencyCheck> {
|
||
try {
|
||
const db = getDb();
|
||
await db.execute(sql`SELECT 1`);
|
||
return { name: "database", status: "ok" };
|
||
} catch (error) {
|
||
return {
|
||
name: "database",
|
||
status: "error",
|
||
error: error instanceof Error ? error.message : String(error),
|
||
};
|
||
}
|
||
}
|
||
|
||
private async checkRedis(): Promise<DependencyCheck> {
|
||
try {
|
||
const redis = getRedis();
|
||
const pong = await redis.ping();
|
||
if (pong !== "PONG") {
|
||
return { name: "redis", status: "error", error: `Unexpected: ${pong}` };
|
||
}
|
||
return { name: "redis", status: "ok" };
|
||
} catch (error) {
|
||
return {
|
||
name: "redis",
|
||
status: "error",
|
||
error: error instanceof Error ? error.message : String(error),
|
||
};
|
||
}
|
||
}
|
||
|
||
private async checkKafka(): Promise<DependencyCheck> {
|
||
try {
|
||
// Kafka producer 连接状态由 AppModule.onModuleInit 建立
|
||
// 这里仅检查 producer 实例是否可用
|
||
const { getKafkaProducer } = await import("../../config/kafka.js");
|
||
const producer = getKafkaProducer();
|
||
void producer; // 实例存在即视为可用
|
||
return { name: "kafka", status: "ok" };
|
||
} catch (error) {
|
||
return {
|
||
name: "kafka",
|
||
status: "error",
|
||
error: error instanceof Error ? error.message : String(error),
|
||
};
|
||
}
|
||
}
|
||
|
||
private checkJwks(): DependencyCheck {
|
||
try {
|
||
getJwtKeyPair();
|
||
return { name: "jwks", status: "ok" };
|
||
} catch (error) {
|
||
return {
|
||
name: "jwks",
|
||
status: "error",
|
||
error: error instanceof Error ? error.message : String(error),
|
||
};
|
||
}
|
||
}
|
||
}
|