feat(core-edu): 完整实现 core-edu 教学核心服务

包含 classes/exams/homework/grades/attendance/scheduling 域、outbox、iam-consumer、redis 配置等完整实现
This commit is contained in:
SpecialX
2026-07-10 19:08:56 +08:00
parent 06a646ea4e
commit 58c0ba1bd9
55 changed files with 4204 additions and 305 deletions

View File

@@ -2,6 +2,7 @@ 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 { outboxPublisher } from "./shared/outbox/outbox.publisher.js";
import { GlobalErrorFilter } from "./shared/errors/global-error.filter.js";
import { initTracer, shutdownTracer } from "./shared/observability/tracer.js";
@@ -27,33 +28,33 @@ async function bootstrap(): Promise<void> {
// publisher will retry sends and messages stay pending until Kafka recovers.
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();
// Start the transactional outbox publisher - polls pending messages
// and publishes them to Kafka topics defined in TOPIC_MAP.
await outboxPublisher.start();
await app.listen(env.PORT);
logger.info(
{ port: env.PORT, service: "core-edu" },
"CoreEdu service is listening",
{ port: env.PORT, grpcPort: env.GRPC_PORT, service: "core-edu" },
"CoreEdu service is listening (HTTP; gRPC port reserved for P3)",
);
process.on("SIGTERM", async () => {
logger.info("SIGTERM received, shutting down gracefully...");
const shutdown = async (signal: string): Promise<void> => {
logger.info({ signal }, "Shutting down gracefully...");
await outboxPublisher.stop();
await disconnectRedis();
await disconnectKafka();
await shutdownTracer();
await app.close();
process.exit(0);
});
};
process.on("SIGINT", async () => {
logger.info("SIGINT received, shutting down gracefully...");
await outboxPublisher.stop();
await disconnectKafka();
await shutdownTracer();
await app.close();
process.exit(0);
});
process.on("SIGTERM", () => void shutdown("SIGTERM"));
process.on("SIGINT", () => void shutdown("SIGINT"));
}
void bootstrap();