fix(infra): resolve NestJS dist build and Prometheus target issues
NestJS: disable incremental in 6 services tsconfig.json to fix dist not emitted when nest-cli deleteOutDir conflicts with tsc tsbuildinfo. classes/iam: import HealthModule in AppModule to fix /healthz 404. classes: rewrite HealthController to Drizzle getDb from TypeORM DI. teacher-bff: add /metrics endpoint for Prometheus scraping. infra: add node/mysql/redis exporters to observability profile. mysql-exporter v0.15.1 uses command-line flags not DATA_SOURCE_NAME. prometheus: enable web.enable-lifecycle for hot reload.
This commit is contained in:
@@ -187,22 +187,25 @@
|
||||
|
||||
### 2.2 classes(TS/NestJS,P1 黄金模板)
|
||||
|
||||
| 场景 | 技术/规则 |
|
||||
| ---------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| 黄金模板定位 | P1 完整实现所有横切关注点,后续 8 个服务复制此模板 |
|
||||
| 黄金模板复制流程 | `cp -r services/classes services/xxx` → 改错误码前缀 → 改 proto → 改业务逻辑 → 改 README → 改 CI |
|
||||
| 横切关注点清单 | 错误处理 / 可观测 / 安全 / 契约 / 测试 / 文档 / 配置 / i18n / CI / Dockerfile |
|
||||
| 错误处理 | `ApplicationError` 基类 + 子类,错误码 `CLASSES_*` 前缀 |
|
||||
| 可观测 | pino logger + prom-client metrics + OTel tracer |
|
||||
| 安全 | auth.middleware(信任 Gateway 头)+ permission.guard + data-scope.interceptor |
|
||||
| 配置 | 三层配置 + Zod 校验 env |
|
||||
| i18n | `ERROR_CODES` 映射表(错误码 → i18n key) |
|
||||
| 测试四类 | 单元(vitest)+ 集成(Testcontainers)+ 契约(Pact)+ E2E(Playwright) |
|
||||
| 覆盖率门槛 | 领域逻辑 ≥ 80%,Handler ≥ 60%,整体 ≥ 60% |
|
||||
| Drizzle schema | `mysqlTable` + `varchar`/`timestamp` + `index` |
|
||||
| ID 生成 | `@paralleldrive/cuid2` 的 `createId()` |
|
||||
| 响应转换 | repository 返回 Date,service 转换为 `createdAt: number`(时间戳) |
|
||||
| 阶段特有模式回写 | Outbox(P3)/ CDC(P4)/ 长连接(P5)实现后回写黄金模板 README |
|
||||
| 场景 | 技术/规则 |
|
||||
| ---------------- | -------------------------------------------------------------------------------------------------- |
|
||||
| 黄金模板定位 | P1 完整实现所有横切关注点,后续 8 个服务复制此模板 |
|
||||
| 黄金模板复制流程 | `cp -r services/classes services/xxx` → 改错误码前缀 → 改 proto → 改业务逻辑 → 改 README → 改 CI |
|
||||
| 横切关注点清单 | 错误处理 / 可观测 / 安全 / 契约 / 测试 / 文档 / 配置 / i18n / CI / Dockerfile |
|
||||
| 错误处理 | `ApplicationError` 基类 + 子类,错误码 `CLASSES_*` 前缀 |
|
||||
| 可观测 | pino logger + prom-client metrics + OTel tracer |
|
||||
| 安全 | auth.middleware(信任 Gateway 头)+ permission.guard + data-scope.interceptor |
|
||||
| 配置 | 三层配置 + Zod 校验 env |
|
||||
| i18n | `ERROR_CODES` 映射表(错误码 → i18n key) |
|
||||
| 测试四类 | 单元(vitest)+ 集成(Testcontainers)+ 契约(Pact)+ E2E(Playwright) |
|
||||
| 覆盖率门槛 | 领域逻辑 ≥ 80%,Handler ≥ 60%,整体 ≥ 60% |
|
||||
| Drizzle schema | `mysqlTable` + `varchar`/`timestamp` + `index` |
|
||||
| ID 生成 | `@paralleldrive/cuid2` 的 `createId()` |
|
||||
| 响应转换 | repository 返回 Date,service 转换为 `createdAt: number`(时间戳) |
|
||||
| 阶段特有模式回写 | Outbox(P3)/ CDC(P4)/ 长连接(P5)实现后回写黄金模板 README |
|
||||
| 健康检查依赖 | `readyz` 用 Drizzle `getDb().execute(sql\`SELECT 1\`)` 校验,不要依赖 typeorm DataSource DI |
|
||||
| AppModule 注册 | HealthModule 必须在 `app.module.ts` imports 数组显式声明,否则 NestFactory 不扫描 HealthController |
|
||||
| 增量编译陷阱 | `tsconfig.json` 显式 `"incremental": false` 覆盖 base,避免 .tsbuildinfo 导致 nest watch 不 emit |
|
||||
|
||||
### 2.3 iam(TS/NestJS,P2)
|
||||
|
||||
@@ -350,6 +353,7 @@
|
||||
|
||||
| 日期 | 时间 | 模块 | 做了什么 + 学到什么 |
|
||||
| ---------- | ---- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 2026-07-09 | 下午 | classes/全局 | **一键启动脚本 NestJS dist/ 不生成根因定位 + classes 健康检查修复**:(1) 根因定位:`tsconfig.base.json` 的 `incremental: true` + `nest-cli.json` 的 `deleteOutDir: true` 冲突。`nest start --watch` 启动时先删除 dist/,tsc 读残留 .tsbuildinfo 认为无变化跳过 emit,dist/ 不生成 → `Cannot find module dist/main`。(2) 修复:6 个 NestJS 服务(classes/iam/teacher-bff/core-edu/content/msg)tsconfig.json 显式加 `"incremental": false` 覆盖 base 配置,删除所有残留 .tsbuildinfo 文件。(3) classes AppModule 缺 HealthModule 导入导致 /healthz 404,iam 同样问题,修复 app.module.ts 加 `imports: [..., HealthModule]`。(4) classes HealthController 误用 TypeORM `DataSource` DI(与 iam 不一致),运行时报 `Nest can't resolve dependencies of the HealthController (DataSource)`。修复:改为 Drizzle `getDb()` 函数式调用,与 iam 一致。(5) 一键启动验证:11/11 应用 + 11/11 基础设施 + 5/5 可观测性端点全绿。**学到**:NestJS + TypeScript incremental 编译是陷阱组合——nest-cli deleteOutDir 删 dist 但 tsc 读 tsbuildinfo 认为无变化,必须在服务级 tsconfig 显式 `incremental: false`;HealthModule 必须在 AppModule imports 中显式声明才能被 NestFactory 扫描到;5 个 NestJS 服务的 HealthController 应统一用 Drizzle `getDb()` 函数式调用而非 TypeORM DataSource DI(项目已弃 TypeORM 改 Drizzle)。 |
|
||||
| 2026-07-09 | 下午 | 全局 | **OTel auto-instrumentations 全服务补全**:(1) NestJS 6 服务(iam/classes/core-edu/content/msg/teacher-bff)tracer.ts 补 `getNodeAutoInstrumentations()`,NodeSDK 传 instrumentations 参数自动埋点 HTTP/Express/DB。(2) Python 2 服务(data-ana/ai)main.py 补 `FastAPIInstrumentor.instrument_app(app)`;ai 补缺失的 `opentelemetry-exporter-otlp` 依赖。(3) teacher-bff 从零补完整 OTel:env.ts 加 OTEL_EXPORTER_OTLP_ENDPOINT 字段 + 新建 shared/observability/tracer.ts + main.ts 调用 initTracer/shutdownTracer + package.json 加 sdk-node/exporter/auto-instrumentations 依赖。(4) Go 2 服务(api-gateway/push-gateway)新建 internal/observability/tracer.go(OTLP HTTP exporter + resource + TracerProvider + W3C propagator)+ main.go 调用 InitTracer + otelgin.Middleware 注册 Gin 中间件;push-gateway config.go 补 OTLPEndpoint 字段。(5) 质量校验全通过:TS typecheck 9 服务 + ESLint 6 服务 + ruff 2 服务 + go vet/build 2 服务零错误。**学到**:`getNodeAutoInstrumentations()` 一次注册所有 Node.js 自动埋点(http/express/dns/fs/net/grpc 等),比手动逐个注册 HttpInstrumentation 更简洁;Go OTel 用 `otlptracehttp.WithEndpoint(host)` + `WithInsecure()` 需从 "http://host:port" URL 解析出 host;otelgin.Middleware 必须在 Recovery 之后其他中间件之前注册,确保所有后续 handler 都被 trace;Python FastAPIInstrumentor.instrument_app(app) 在 app 创建后立即调用,lifespan 不受影响。 |
|
||||
| 2026-07-09 | 下午 | 全局 | **P6 硬化:可观测性 + 部署 + CI 硬化**:(1) 可观测性栈完善:5 个 NestJS 服务 main.ts 添加 `/metrics` Prometheus 端点(用 `app.getHttpAdapter().get('/metrics', ...)` 绕过 DI 容器 get 方法);prometheus.yml 从 2 个目标扩展到 8 个应用服务 + MySQL/Redis + node-exporter + prometheus 自身 + rule_files + alertmanager 关联;monitoring compose 用 Loki + Promtail 替换未配置的 blackbox-exporter;Grafana datasource 新增 Loki;新建 promtail/config.yml 用 docker_sd_configs 仅采集 `edu-*` 容器日志。(2) 部署 compose 扩展:docker-compose.deploy.yml 从 3 服务扩展到 11 服务(+ iam/teacher-bff/core-edu/content/msg/ai/data-ana/push-gateway),每个服务带 healthcheck + depends_on 条件 + edu-net/edu-shared 双网络;deploy.env.example 补全 Neo4j/ES/ClickHouse/LLM/Kafka 可选依赖配置。(3) teacher-bff 补 health.controller.ts(原缺失 /healthz 导致 deploy depends_on service_healthy 失败)。(4) CI 硬化:移除 lint 步骤的 continue-on-error(ESLint 9 flat config 已配置完成),test 保留 continue-on-error(部分服务无 test 脚本)。**学到**:NestJS `app.get('/metrics')` 会被解析为 DI 容器 `get(typeOrToken)`,必须用 `app.getHttpAdapter().get()` 才能注册 Express 路由;Promtail docker_sd_configs 通过 relabel_configs 的 `regex: '/(edu-.*).*'` 过滤容器名前缀;docker-compose.depends_on.condition: service_healthy 要求被依赖服务必须有 healthcheck 配置,否则启动失败。 |
|
||||
| 2026-07-09 | 下午 | data-ana/infra | **CDC 完整链路实现**:MySQL binlog → Debezium Connect → Kafka → data-ana 消费者 → ClickHouse 宽表。(1) MySQL binlog 配置:log_bin=ON, binlog_format=ROW, binlog_row_image=FULL, server_id=1;用 root 创建 `debezium` 用户授予 REPLICATION SLAVE + REPLICATION CLIENT。(2) Debezium Connect 容器:daocloud 禁用 debezium 镜像改用 `quay.io/debezium/connect:2.7`;MySQL 容器在 edu-minimal_default 网络,需 `docker network connect edu-full_default edu-mysql` 让 Debezium 同时可达;Kafka 必须配置双 listener(INSIDE:kafka:29092 + OUTSIDE:localhost:9092),否则 Debezium 拿到 advertised.listeners 中的 localhost metadata 后切换失败;Debezium 2.x 容器环境变量名用 BOOTSTRAP_SERVERS(不带 KAFKA_ 前缀),通过 envsubst 替换到 connect-distributed.properties。(3) 注册 connector:POST :8083/connectors,配置 topic.prefix=edu-cdc, database.include.list=next_edu_cloud, snapshot.mode=initial,4 张表(core_edu_grades/exams/classes/iam_users)成功产生快照事件。(4) data-ana 消费者实现:新建 cdc_consumer.py 用 aiokafka AIOKafkaConsumer,lifespan 中 asyncio.create_task 后台运行;按 source.table 路由(exams→内存缓存 exam_id→class_id 映射,grades→查缓存填 class_id 后 upsert ClickHouse);readyz 端点附加 cdc_consumer 状态。(5) ClickHouse 远程访问:默认 default-user.xml 限制 127.0.0.1/::1 无密码,挂载 `clickhouse/users.d/custom-users.xml` 覆盖密码+任意 IP。(6) structlog 24.x API:`make_filtering_bound_logger(level)` 替代废弃的 `make_filtering_logger`。(7) E2E 验证:MySQL INSERT 成绩 → Debezium op=c 事件 → Kafka → 消费者写 ClickHouse 宽表(class_id 通过 exam 缓存正确填充)→ /readyz cdc_consumer=running → /analytics/student/student-002/weakness 返回实时 92 分数据。**学到**:Debezium 2.x 容器 bootstrap.servers 默认值是 0.0.0.0:9092 必须显式覆盖;Kafka 单 listener 配置 localhost 会让容器间通信的客户端拿到 metadata 后切换失败,必须用双 listener;ClickHouse users_xml 存储是 readonly 不能用 ALTER USER 修改密码,必须挂载 users.d 配置文件覆盖;消费者 offset 重置必须先停消费者让 group 处于 Empty 状态才能执行 --reset-offsets。 |
|
||||
|
||||
Reference in New Issue
Block a user