import "reflect-metadata"; import { NestFactory } from "@nestjs/core"; import { Transport, MicroserviceOptions } from "@nestjs/microservices"; import { join } from "node:path"; import { existsSync } from "node:fs"; import { AppModule } from "./app.module.js"; import { GlobalErrorFilter } from "./shared/errors/global-error.filter.js"; import { initTracer, shutdownTracer } from "./shared/observability/tracer.js"; import { env } from "./config/env.js"; import { logger } from "./shared/observability/logger.js"; import { metricsRegistry } from "./shared/observability/metrics.js"; import { getJwtKeyPair } from "./config/jwt.js"; import type { Request, Response } from "express"; /** * 解析 proto 文件路径。 * 开发环境:从 monorepo 根目录的 packages/shared-proto/proto/ 加载 * - 当 cwd 为 monorepo 根(如 CI)→ packages/shared-proto/proto/iam.proto * - 当 cwd 为 services/iam(pnpm --filter run dev)→ ../../packages/shared-proto/proto/iam.proto * 生产环境(Docker):从服务本地的 ./proto/ 加载(Dockerfile COPY) */ function resolveProtoPath(): string { const monorepoRootPath = join( process.cwd(), "packages", "shared-proto", "proto", "iam.proto", ); const monorepoParentPath = join( process.cwd(), "..", "..", "packages", "shared-proto", "proto", "iam.proto", ); const localPath = join(process.cwd(), "proto", "iam.proto"); if (existsSync(monorepoRootPath)) return monorepoRootPath; if (existsSync(monorepoParentPath)) return monorepoParentPath; if (existsSync(localPath)) return localPath; return monorepoParentPath; } /** * IAM 服务启动入口。 * * 双入口(president §2.16): * - HTTP server:env.PORT(3002),供 gateway 透传 + admin-portal 直连 * - gRPC server:env.GRPC_PORT(50052),供 BFF 聚合调用(I1 裁决) * * 启动顺序: * 1. initTracer(OTel SDK) * 2. 创建 NestApplication * 3. 注册 GlobalErrorFilter * 4. 启动 gRPC microservice(hybrid app) * 5. 启动 HTTP server * 6. 预加载 JWT 密钥对(确保文件可读) */ async function bootstrap(): Promise { initTracer(); // 预加载 JWT 密钥对(启动时即校验文件可读,避免运行时才发现配置错误) getJwtKeyPair(); const app = await NestFactory.create(AppModule, { logger: ["log", "error", "warn"], }); app.useGlobalFilters(new GlobalErrorFilter()); app.enableShutdownHooks(); // gRPC microservice(端口 50052,I1 裁决) app.connectMicroservice({ transport: Transport.GRPC, options: { package: "next_edu_cloud.iam.v1", protoPath: resolveProtoPath(), url: `0.0.0.0:${env.GRPC_PORT}`, }, }); // Prometheus 指标端点 app.getHttpAdapter().get("/metrics", async (_req: Request, res: Response) => { res.set("Content-Type", metricsRegistry.contentType); res.end(await metricsRegistry.metrics()); }); // 启动 hybrid app(HTTP + gRPC) await app.startAllMicroservices(); await app.listen(env.PORT); logger.info( { httpPort: env.PORT, grpcPort: env.GRPC_PORT }, "IAM service started (HTTP + gRPC dual entry)", ); process.on("SIGTERM", async () => { await app.close(); await shutdownTracer(); }); } bootstrap().catch((err: unknown) => { logger.error({ err }, "Failed to start IAM service"); process.exit(1); });