import "@edu/shared-ts/env-loader"; import { NestFactory } from "@nestjs/core"; import { AppModule } from "./app.module.js"; import { env } from "./config/env.js"; import { connectKafka, disconnectKafka } from "./config/kafka.js"; import { connectRedis, disconnectRedis } from "./config/redis.js"; import { GlobalErrorFilter } from "./shared/errors/global-error.filter.js"; import { initTracer, shutdownTracer } from "./shared/observability/tracer.js"; import { logger } from "./shared/observability/logger.js"; import { registry } from "./shared/observability/metrics.js"; import { startGrpcServer, stopGrpcServer } from "./grpc/grpc.server.js"; import type { Request, Response } from "express"; async function bootstrap(): Promise { initTracer(); const app = await NestFactory.create(AppModule, { bufferLogs: true }); app.useGlobalFilters(new GlobalErrorFilter()); app.enableShutdownHooks(); // Prometheus 指标端点:不鉴权,供 Prometheus 抓取。 app.getHttpAdapter().get("/metrics", async (_req: Request, res: Response) => { res.set("Content-Type", registry.contentType); res.end(await registry.metrics()); }); // Connect Kafka producer/consumer. // Non-blocking: if Kafka is unavailable, service still starts. // v2.1(M8):OutboxPublisher 轮询线程已移除,outbox 投递由 Debezium CDC 接管。 void connectKafka(); // Connect Redis for distributed lock (homework submission idempotency). // Non-blocking: if Redis is unavailable, service still starts; lock-based // operations will fall back to DB unique index for idempotency. void connectRedis(); await app.listen(env.PORT); // 启动 gRPC server(9 Service / 40 RPC) await startGrpcServer(app); logger.info( { port: env.PORT, grpcPort: env.GRPC_PORT, service: "core-edu" }, "CoreEdu service is listening (HTTP + gRPC)", ); const shutdown = async (signal: string): Promise => { logger.info({ signal }, "Shutting down gracefully..."); await stopGrpcServer(); await disconnectRedis(); await disconnectKafka(); await shutdownTracer(); await app.close(); process.exit(0); }; process.on("SIGTERM", () => void shutdown("SIGTERM")); process.on("SIGINT", () => void shutdown("SIGINT")); } void bootstrap();