Files
Edu/services/iam/src/main.ts
SpecialX 9cedf0c437 feat(portal-shell): v2.0 P0 shadcn standardization + security + streaming + error handling
- 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 路由生成成功
2026-07-17 16:10:05 +08:00

110 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import "@edu/shared-ts/env-loader";
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/iampnpm --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 serverenv.PORT3002供 gateway 透传 + admin-portal 直连
* - gRPC serverenv.GRPC_PORT50052供 BFF 聚合调用I1 裁决)
*
* 启动顺序:
* 1. initTracerOTel SDK
* 2. 创建 NestApplication
* 3. 注册 GlobalErrorFilter
* 4. 启动 gRPC microservicehybrid app
* 5. 启动 HTTP server
* 6. 预加载 JWT 密钥对(确保文件可读)
*/
async function bootstrap(): Promise<void> {
initTracer();
// 预加载 JWT 密钥对(启动时即校验文件可读,避免运行时才发现配置错误)
getJwtKeyPair();
const app = await NestFactory.create(AppModule, {
logger: ["log", "error", "warn"],
});
app.useGlobalFilters(new GlobalErrorFilter());
app.enableShutdownHooks();
// gRPC microservice端口 50052I1 裁决)
app.connectMicroservice<MicroserviceOptions>({
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 appHTTP + 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);
});