feat(infra): p6 hardening - observability and deploy compose

- 5 NestJS services add /metrics endpoint via app.getHttpAdapter()
- prometheus.yml scales to 8 services with rule_files and alertmanager
- monitoring compose replaces blackbox with Loki+Promtail
- Grafana datasource adds Loki
- docker-compose.deploy.yml scales to 11 services
- deploy.env.example completes Neo4j/ES/ClickHouse/LLM vars
- teacher-bff adds health.controller
- CI removes continue-on-error on lint step
- teacher-portal lint script changed to eslint src
This commit is contained in:
SpecialX
2026-07-09 10:21:06 +08:00
parent 3ca654619f
commit 566060fade
21 changed files with 554 additions and 59 deletions

View File

@@ -86,15 +86,20 @@
### 1.6 可观测性OTel + Prometheus + Loki
| 场景 | 技术/规则 |
| --------------- | -------------------------------------------------------------------------------- |
| P1 最小可观测集 | 每服务结构化日志 + `/metrics` + OTel SDK 初始化(不引入完整后端) |
| 三支柱 | Logspino/winston/zap+ Metricsprom-client+ TracesOTel SDK |
| traceId 注入 | Gateway 注入 → 服务读取 header → 日志/响应携带 |
| P6 完整后端 | Loki日志+ Grafana仪表盘+ Jaegertrace+ Prometheusmetrics |
| Prometheus 指标 | `http_request_duration_seconds`Histogram+ `http_requests_total`Counter |
| 采样策略 | P1 全量 traceP6 引入采样率降低开销 |
| 日志参数顺序 | `log.error({ err: error, userId, traceId }, "操作描述")`,错误对象字段名用 `err` |
| 场景 | 技术/规则 |
| ----------------- | ------------------------------------------------------------------------------------------------------------------- |
| P1 最小可观测集 | 每服务结构化日志 + `/metrics` + OTel SDK 初始化(不引入完整后端) |
| 三支柱 | Logspino/winston/zap+ Metricsprom-client+ TracesOTel SDK |
| traceId 注入 | Gateway 注入 → 服务读取 header → 日志/响应携带 |
| P6 完整后端 | Loki日志+ Grafana仪表盘+ Jaegertrace+ Prometheusmetrics |
| Prometheus 指标 | `http_request_duration_seconds`Histogram+ `http_requests_total`Counter |
| 采样策略 | P1 全量 traceP6 引入采样率降低开销 |
| 日志参数顺序 | `log.error({ err: error, userId, traceId }, "操作描述")`,错误对象字段名用 `err` |
| P6 /metrics 端点 | NestJS 用 `app.getHttpAdapter().get('/metrics', handler)`,不能用 `app.get()`(会被解析为 DI 容器 get |
| P6 prometheus.yml | 8 个应用服务 + MySQL/Redis + node-exporter + prometheus 自身rule_files 引用 rules.ymlalerting 关联 alertmanager |
| P6 Grafana 数据源 | provisioning/datasources 同时声明 Prometheus默认和 Lokiuid=loki |
| P6 Promtail 采集 | docker_sd_configs + relabel_configs 仅采集 `edu-*` 前缀容器日志,避免无关日志 |
| P6 service label | static_configs.labels.service 给所有抓取目标打服务标签,告警规则按 service 聚合 |
### 1.7 微前端 Module Federation
@@ -328,6 +333,7 @@
| 日期 | 时间 | 模块 | 做了什么 + 学到什么 |
| ---------- | ---- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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-exporterGrafana 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-errorESLint 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 | 下午 | 全局 | **P6 硬化ESLint 9 flat config 配置**(1) 根目录创建 `eslint.config.js`ESLint 9 flat config 格式):用 `typescript-eslint` recommended 规则集 + `@eslint/js` recommended + `eslint-config-prettier` 禁用冲突规则;自定义规则:`no-explicit-any` warn + `no-unused-vars` 允许下划线前缀 + 测试文件放宽。(2) 6 个 TS 服务 package.json lint 脚本从 `eslint src --ext .ts` 改为 `eslint src`flat config 不需要 --ext。(3) `lint-staged.config.js` 恢复 `eslint --fix`。(4) 验证classes/content/msg/core-edu 四服务 lint 全部零错误零警告通过。**学到**ESLint 9 flat config 用 `tseslint.config()` 工厂函数组装配置数组;`--ext` 参数在 flat config 模式下被移除ESLint 自动根据 `eslint.config.js` 中的 `files` 匹配;`@typescript-eslint/consistent-type-assertions` 规则选项格式在 v8 中变化(`objectLiteralType` → `objectLiteralTypeAssertions`),配置时需查最新文档。 |
| 2026-07-09 | 中午 | msg/push-gateway/ai/api-gateway | **P5 沟通与 AI 阶段三服务完善**(1) msg 服务修复database.ts 导出 db 常量env.ts JWT_SECRET/ES_URL 改 optional 加 DEV_MODE/PUSH_GATEWAY_URLelasticsearch.ts ES 降级esClient=null 时 safeIndex/safeSearch 跳过notifications.service.ts 加 createBatch + listByUserWithPagination + Push Gateway 推送调用try/catch 降级);新建 msg-init.sql 2 张表。(2) push-gateway 完善hub.go 重写用 send chan + 单写协程模式修复 gorilla/websocket 并发写竞争handler.go 加 DEV_MODE dev-token 支持 + broadcast 端点config.go 加 DevMode/RedisURL。(3) ai 服务完善config.py 加 openai_api_key/base_url/dev_mode新建 llm_client.pyhttpx 异步调 OpenAI REST APImain.py 加 /ai 前缀 + 降级模式(无 key 返回骨架 + degraded: true+ /readyz 端点。(4) Gateway 路由扩展:/notifications → msg/ai → ai 服务。**学到**gorilla/websocket 不支持并发写,必须用 send chan 串行化所有写入FastAPI APIRouter prefix 与 Gateway 代理路径要协调ai 服务加 /ai 前缀Gateway 代理 /ai/*pathLLM 降级策略统一返回 degraded 标记,调用方据此判断是否路由流量。 |
| 2026-07-09 | 上午 | content/api-gateway | **P4 内容分析服务端到端打通**(1) content 服务系统性修复database.ts 导出 db 常量env.ts JWT_SECRET/ES_URL/NEO4J_URL/NEO4J_PASSWORD 改 optional 加 DEV_MODEneo4j.ts driver 惰性创建+try/catch+connectionTimeout:3000health/lifecycle 改用 Drizzleglobal-error.filter 移除 @types/express 依赖textbooks.schema 修复 integer→int + 导出 NewTextbook/NewChapter 类型textbooks.controller 移除 body as any + 加 PUT/DELETE。(2) 新建 3 模块chaptersCRUD + 按 textbook 查询、knowledge-pointsCRUD + Neo4j 前置依赖图非阻塞查询、questionsCRUD + 4 种题型校验)。(3) Gateway 路由扩展textbooks/chapters/knowledge-points/questions 四组路由。(4) 数据库content-init.sql 4 张表。(5) E2E 验证POST /textbooks 201 → POST /chapters 201字段用 order 非 orderNum→ POST /knowledge-points 201Neo4j 不可用 MySQL 正常写入)→ POST /questions 201 → GET 各列表 200。**学到**Drizzle schema TS 字段名与 DB 列名解耦order→order_numAPI 请求体用 TS 字段名Neo4j 不可用时必须 driver=null不设 NEO4J_URL否则每次请求尝试连接拖慢响应neo4j-driver safeCreateNode 用 try/catch 非阻塞MySQL 数据始终先落库。 |