修复涵盖 6 大类问题: 1. api-gateway - 路径前缀剥离 /api 而非 /api/v1,保留下游 /v1/ controller 前缀 - JWKS URL 默认值修复 - publicPaths 白名单对齐 /v1/iam/* 2. iam - iam.module.ts exports 补充 PermissionCacheService 和 IamRepository - main.ts resolveProtoPath() 多路径探测 proto 文件 3. core-edu - app.module.ts AuthMiddleware 全局注册 4. BFF 层 GraphQL 端点(teacher-bff / parent-bff / student-bff) - teacher-bff: mock dataScope OWN→SELF 对齐 GraphQL enum;WHATWG Request header .get() 提取 - parent-bff: handleNodeRequestAndResponse 不存在 → 直接 yoga(req,res);WHATWG Request header .get() 提取 - student-bff: auth.resolver 移除 ActionState 信封返回扁平对象;WHATWG Request header .get() 提取 5. ai - Kafka 事务降级 + 10s 超时 - gRPC 拦截器降级 - dev mode 禁用事务模式 6. 前端 + 共享包 - teacher-portal: MF 插件条件实例化 + transpilePackages + extensionAlias - ui-components: error-boundary.tsx 添加 use client - ui-tokens: tailwind-theme.css 移除 @layer base - shared-ts: 导出从源码改为 dist 编译产物;OutboxModule global:true 7. infra - .gitignore 补充 keys/ *.pem *.key secrets/ 排除规则 - infra/init-sql/02-all-services-schema.sql 36 张表 DDL 验证结果: - TS typecheck: 19 个 workspace 项目全部通过 - Go vet + Ruff: 通过 - 15 服务全部启动成功 - 3 个 BFF GraphQL 端点 + 4 个前端页面全部 200 - Gateway → iam → core-edu 端到端链路验证通过 AI identity: trae-main(集成测试修复会话)
109 lines
3.4 KiB
TypeScript
109 lines
3.4 KiB
TypeScript
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<void> {
|
||
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<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 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);
|
||
});
|