- shadcn/ui 标准化:废弃纸感令牌,统一 bg-background/text-foreground 等 - Tailwind v4 + @theme inline,移除 tailwind.config.js - React 19 use() + Suspense 流式渲染,首屏骨架秒出 - 三级错误边界:Route → Section → Widget 层层兜底 - 错误上报:useErrorReport → sendBeacon → /api/log mock 端点 - 三层安全边界:L1 角色门禁 / L2 权限点门禁 / L3 数据范围 - 权限位图 base36 压缩:67 权限点 → ~14 字符,JWT 体积减少 ≥ 99% - notify 统一 Toast 封装,禁止业务直接 import sonner - PluginBoundary 替代 PluginLoader(错误边界 + Suspense + Skeleton 三件套) 验证:typecheck 0 错误 / lint 0 错误 / build 6 路由生成成功
62 lines
2.2 KiB
TypeScript
62 lines
2.2 KiB
TypeScript
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<void> {
|
||
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<void> => {
|
||
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();
|