docs: 同步 P6 工作日志、runbook 与服务 README

- known-issues.md: 追加 9 条 P6 工作经验日志,更新 arch-scan 经验
- post-p6-followup.md: 新增 P6 后续工作手册 runbook
- iam/core-edu/content/msg README: 补充健康检查端点说明
This commit is contained in:
SpecialX
2026-07-08 12:53:05 +08:00
parent 20b1afd9ab
commit beedbaf686
6 changed files with 1321 additions and 247 deletions

View File

@@ -1,7 +1,7 @@
# 已知问题速查
> 索引式速查手册:场景 → 技术/规则映射。不写代码示例。
> 架构规则见 [../architecture/004_architecture_impact_map.md](../architecture/004_architecture_impact_map.md) 与 [../../project_rules.md](../../project_rules.md)
> 架构规则见 [../architecture/004_architecture_impact_map.md](../architecture/004_architecture_impact_map.md) 与 [../../.trae/rules/project_rules.md](../../.trae/rules/project_rules.md)
> 工作经验日志按时间倒序追加50 条上限AI 发现更好方案时可更新本节。
---
@@ -10,140 +10,140 @@
### 1.1 多语言 monorepo 配置
| 场景 | 技术/规则 |
| ----------------------------- | ------------------------------------------------------------------------------------------ |
| 多语言 workspace | pnpm workspaceTS+ go.workGo+ pyproject.toml/uv workspacePython三套并存 |
| 根 package.json scripts | 封装多语言命令入口:`pnpm dev` / `pnpm lint` / `pnpm test` / `pnpm build` |
| pnpm-workspace.yaml | 仅声明 TS 包路径packages/*、services/classes、bff/*、apps/*、scripts/*Go/Python 不入 |
| go.work | 列出所有 Go 服务模块services/api-gateway、services/push-gateway |
| pyproject.toml | uv workspace members 列 Python 服务services/data-ana、services/ai-gateway |
| 跨语言共享类型 | protobuf 生成三端代码TS/Go/Python单一契约源 |
| tsx 执行 TS 脚本 | arch-scan 等工具脚本用 `tsx` 直接运行,无需编译 |
| husky + commitlint | pre-commit 跑 eslint+prettiercommit-msg 校验 Conventional Commits |
| .editorconfig 多语言缩进 | Go 用 tabPython 用 4 空格TS/默认用 2 空格 |
| 场景 | 技术/规则 |
| ------------------------ | ------------------------------------------------------------------------------------------ |
| 多语言 workspace | pnpm workspaceTS+ go.workGo+ pyproject.toml/uv workspacePython三套并存 |
| 根 package.json scripts | 封装多语言命令入口:`pnpm dev` / `pnpm lint` / `pnpm test` / `pnpm build` |
| pnpm-workspace.yaml | 仅声明 TS 包路径packages/_、services/classes、bff/_、apps/_、scripts/_Go/Python 不入 |
| go.work | 列出所有 Go 服务模块services/api-gateway、services/push-gateway |
| pyproject.toml | uv workspace members 列 Python 服务services/data-ana、services/ai-gateway |
| 跨语言共享类型 | protobuf 生成三端代码TS/Go/Python单一契约源 |
| tsx 执行 TS 脚本 | arch-scan 等工具脚本用 `tsx` 直接运行,无需编译 |
| husky + commitlint | pre-commit 跑 eslint+prettiercommit-msg 校验 Conventional Commits |
| .editorconfig 多语言缩进 | Go 用 tabPython 用 4 空格TS/默认用 2 空格 |
### 1.2 Docker Compose 基础设施
| 场景 | 技术/规则 |
| ---------------------------- | ---------------------------------------------------------------------------------- |
| 日常开发启动 | 用 `docker-compose.minimal.yml` 仅起 MySQL+Redis |
| 全量启动内存不足 | 按 `profiles` 分阶段启用full/kafka/cdc/analytics/graph/search/config/observability |
| 每服务 mem_limit | 避免单服务吃满内存MySQL 512m、Redis 128m、Kafka 512m、ClickHouse 1g |
| 按阶段启用容器 | P1 仅 MySQL+RedisP3 加 Kafka+ZookeeperP4 加 Debezium+CH+Neo4jP5 加 ESP6 加 Consul+Istio |
| MySQL 初始化 | `init-sql/01-init.sql` 挂载到 `/docker-entrypoint-initdb.d:ro` |
| healthcheck | MySQL 用 `mysqladmin ping`Redis 用 `redis-cli ping` |
| Windows 下卷挂载 | init-sql 用绝对路径或确保相对路径正确 |
| 容器名固定 | `container_name: edu-mysql` 便于服务连接配置 |
| 场景 | 技术/规则 |
| ---------------- | ----------------------------------------------------------------------------------------------- |
| 日常开发启动 | 用 `docker-compose.minimal.yml` 仅起 MySQL+Redis |
| 全量启动内存不足 | 按 `profiles` 分阶段启用full/kafka/cdc/analytics/graph/search/config/observability |
| 每服务 mem_limit | 避免单服务吃满内存MySQL 512m、Redis 128m、Kafka 512m、ClickHouse 1g |
| 按阶段启用容器 | P1 仅 MySQL+RedisP3 加 Kafka+ZookeeperP4 加 Debezium+CH+Neo4jP5 加 ESP6 加 Consul+Istio |
| MySQL 初始化 | `init-sql/01-init.sql` 挂载到 `/docker-entrypoint-initdb.d:ro` |
| healthcheck | MySQL 用 `mysqladmin ping`Redis 用 `redis-cli ping` |
| Windows 下卷挂载 | init-sql 用绝对路径或确保相对路径正确 |
| 容器名固定 | `container_name: edu-mysql` 便于服务连接配置 |
### 1.3 protobuf + buf 契约
| 场景 | 技术/规则 |
| ----------------------- | ------------------------------------------------------------------------------------------ |
| 契约唯一源 | `packages/shared-proto/proto/*.proto`,禁止 REST/gRPC 混用 |
| breaking change 检测 | `buf breaking --against '.git#branch=main'` CI 强制,必须升版本号 |
| proto 版本化 | 包名带版本 `xxx.v1``xxx.v2`,新旧版本共存 |
| buf lint | `buf lint` 使用 STANDARD 规则集,可按需 except如 PACKAGE_VERSION_SUFFIX |
| 三端代码生成 | `buf.gen.yaml` 配置 TS/Go/Python 插件,`pnpm proto:gen` 统一生成 |
| 契约先行硬约束 | 任何服务间通信必须先改 protoCI 检测 breaking change 阻止违规合并 |
| 事件 schema 版本化 | Kafka 事件带 `schema_version` 字段,消费端按版本处理 |
| 错误码定义在 proto | 错误码三端共享,格式 `{服务前缀}_{错误类型}_{资源}`(如 `IAM_PERM_USER` |
| 场景 | 技术/规则 |
| -------------------- | --------------------------------------------------------------------------- |
| 契约唯一源 | `packages/shared-proto/proto/*.proto`,禁止 REST/gRPC 混用 |
| breaking change 检测 | `buf breaking --against '.git#branch=main'` CI 强制,必须升版本号 |
| proto 版本化 | 包名带版本 `xxx.v1``xxx.v2`,新旧版本共存 |
| buf lint | `buf lint` 使用 STANDARD 规则集,可按需 except如 PACKAGE_VERSION_SUFFIX |
| 三端代码生成 | `buf.gen.yaml` 配置 TS/Go/Python 插件,`pnpm proto:gen` 统一生成 |
| 契约先行硬约束 | 任何服务间通信必须先改 protoCI 检测 breaking change 阻止违规合并 |
| 事件 schema 版本化 | Kafka 事件带 `schema_version` 字段,消费端按版本处理 |
| 错误码定义在 proto | 错误码三端共享,格式 `{服务前缀}_{错误类型}_{资源}`(如 `IAM_PERM_USER` |
### 1.4 NestJS 服务开发TS
| 场景 | 技术/规则 |
| ------------------------- | ----------------------------------------------------------------------------------------------- |
| 服务标准结构 | `src/{main.ts,app.module.ts,config,middleware,<domain>,shared,generated}` 黄金模板复制 |
| 三层配置 | env环境变量< yaml业务参数< 配置中心P6 Consul每服务启动时合并 |
| 环境变量校验 | Zod schema 校验 process.env失败立即抛错终止启动 |
| ApplicationError 基类 | 统一错误体系ValidationError(400)/NotFoundError(404)/PermissionDeniedError(403)/ConflictError(409) 等 |
| 响应信封 | `{ success: boolean, data?: T, error?: { code, message, details, traceId } }` 统一跨服务 |
| 权限校验 | `requirePermission(perm)` 装饰器/中间件admin 角色拥有全部权限 |
| DataScope 过滤 | 6 级all/grade_managed/class_taught/class_members/children/ownedrepository 下推 WHERE 条件 |
| 结构化日志 | pino + `traceId`/`spanId`/`userId`/`service` 字段,禁止 `console.*` |
| Metrics endpoint | prom-client 暴露 `/metrics`QPS/延迟/错误率) |
| OpenTelemetry trace | OTel SDK 初始化W3C TraceContext header 跨服务传播 |
| 健康检查 | `/health` endpoint 返回 `{ status, service, version }` |
| 优雅关闭 | SIGTERM → app.close() → shutdownTracing() → process.exit(0) |
| i18n 错误码映射 | 错误码 → i18n key 映射表,后端返回 key + 参数,前端翻译 |
| Drizzle ORM | 参数化查询,禁止字符串拼接 SQL |
| Zod 输入校验 | controller 层 `schema.safeParse(body)`,失败抛 ValidationError |
| 场景 | 技术/规则 |
| --------------------- | ------------------------------------------------------------------------------------------------------ |
| 服务标准结构 | `src/{main.ts,app.module.ts,config,middleware,<domain>,shared,generated}` 黄金模板复制 |
| 三层配置 | env环境变量< yaml业务参数< 配置中心P6 Consul每服务启动时合并 |
| 环境变量校验 | Zod schema 校验 process.env失败立即抛错终止启动 |
| ApplicationError 基类 | 统一错误体系ValidationError(400)/NotFoundError(404)/PermissionDeniedError(403)/ConflictError(409) 等 |
| 响应信封 | `{ success: boolean, data?: T, error?: { code, message, details, traceId } }` 统一跨服务 |
| 权限校验 | `requirePermission(perm)` 装饰器/中间件admin 角色拥有全部权限 |
| DataScope 过滤 | 6 级all/grade_managed/class_taught/class_members/children/ownedrepository 下推 WHERE 条件 |
| 结构化日志 | pino + `traceId`/`spanId`/`userId`/`service` 字段,禁止 `console.*` |
| Metrics endpoint | prom-client 暴露 `/metrics`QPS/延迟/错误率) |
| OpenTelemetry trace | OTel SDK 初始化W3C TraceContext header 跨服务传播 |
| 健康检查 | `/health` endpoint 返回 `{ status, service, version }` |
| 优雅关闭 | SIGTERM → app.close() → shutdownTracing() → process.exit(0) |
| i18n 错误码映射 | 错误码 → i18n key 映射表,后端返回 key + 参数,前端翻译 |
| Drizzle ORM | 参数化查询,禁止字符串拼接 SQL |
| Zod 输入校验 | controller 层 `schema.safeParse(body)`,失败抛 ValidationError |
### 1.5 Go Gateway 开发
| 场景 | 技术/规则 |
| ------------------- | ------------------------------------------------------------------------------------------ |
| 框架 | Gin + httputil.ReverseProxy 路由转发 |
| P1 鉴权 | Gateway 内置 HS256 JWT 校验测试密钥P2 改 RS256IAM 签发,公钥校验) |
| JWT claims | `{ user_id, roles[], registered_claims }`,校验后注入 `x-user-id`/`x-user-roles` 头转发 |
| 请求 ID 注入 | Gateway 生成或透传 `X-Request-ID`,全链路传递 |
| 健康检查 | `GET /health` 无需鉴权,返回 `{ status, timestamp }` |
| 包结构 | `internal/{config,middleware,proxy}`P6 扩展 handler/service/repository |
| error 处理 | 显式处理,禁止 `_` 忽略;`gin.AbortWithStatusJSON` 统一错误响应 |
| 配置加载 | 环境变量 + 默认值(`getEnv(key, fallback)`P6 引入 viper |
| Dockerfile 多阶段 | golang:1.22-alpine 构建 → alpine:3.20 运行CGO_ENABLED=0 |
| 场景 | 技术/规则 |
| ----------------- | --------------------------------------------------------------------------------------- |
| 框架 | Gin + httputil.ReverseProxy 路由转发 |
| P1 鉴权 | Gateway 内置 HS256 JWT 校验测试密钥P2 改 RS256IAM 签发,公钥校验) |
| JWT claims | `{ user_id, roles[], registered_claims }`,校验后注入 `x-user-id`/`x-user-roles` 头转发 |
| 请求 ID 注入 | Gateway 生成或透传 `X-Request-ID`,全链路传递 |
| 健康检查 | `GET /health` 无需鉴权,返回 `{ status, timestamp }` |
| 包结构 | `internal/{config,middleware,proxy}`P6 扩展 handler/service/repository |
| error 处理 | 显式处理,禁止 `_` 忽略;`gin.AbortWithStatusJSON` 统一错误响应 |
| 配置加载 | 环境变量 + 默认值(`getEnv(key, fallback)`P6 引入 viper |
| Dockerfile 多阶段 | golang:1.22-alpine 构建 → alpine:3.20 运行CGO_ENABLED=0 |
### 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` |
### 1.7 微前端 Module Federation
| 场景 | 技术/规则 |
| --------------------- | -------------------------------------------------------------------------------------------- |
| 4 个稳定 portal | teacher-portal / student-portal / parent-app / admin-console按场景域划分 |
| P1 测试页 | teacher-portal 仅一个测试页验证 classes CRUD 链路P2 起配 Module Federation |
| 视口驱动渲染 | 侧边栏由 `viewports.L1` 驱动渲染,路由由 `viewports.L2` 控制 |
| 共享翻译资源包 | next-intl 微前端共享BFF/服务返回 i18n key 不返回翻译文本 |
| Module Federation 配置 | P2 起在 next.config.js 配置P1 用单一 Next.js 应用 |
| 场景 | 技术/规则 |
| ---------------------- | ---------------------------------------------------------------------------- |
| 4 个稳定 portal | teacher-portal / student-portal / parent-app / admin-console按场景域划分 |
| P1 测试页 | teacher-portal 仅一个测试页验证 classes CRUD 链路P2 起配 Module Federation |
| 视口驱动渲染 | 侧边栏由 `viewports.L1` 驱动渲染,路由由 `viewports.L2` 控制 |
| 共享翻译资源包 | next-intl 微前端共享BFF/服务返回 i18n key 不返回翻译文本 |
| Module Federation 配置 | P2 起在 next.config.js 配置P1 用单一 Next.js 应用 |
### 1.8 从旧项目迁移的通用经验
| 场景 | 技术/规则 |
| ----------------------------- | ----------------------------------------------------------------------------------------------- |
| React 19 乐观更新 | `useOptimistic` 替代手动 isPending配合 `useTransition` 自动管理回滚 |
| Zustand 细粒度选择器 | 单字段 selector 优于 `useShallow` 多字段包装 |
| Tiptap SSR | 必须 `immediatelyRender: false` 避免 hydration mismatch |
| 请求级去重 | React `cache()` 包装优于跨请求缓存(权限数据易变,不用 `unstable_cache` |
| 批量 SQL | INSERT batch `db.insert().values([...])` + UPDATE `CASE WHEN` 单次执行,禁止循环单条 |
| 动态导入重 UI 库 | `xxx-inner.tsx` + `xxx.tsx` lazy wrappertiptap / @xyflow/react / AI SDK |
| ReactFlowProvider | 留在外层同步渲染,保证 lazy 子组件 hook 可用 |
| 递归删除 | 批量收集后代 ID + `inArray` 单次删除,禁止递归逐个 |
| 统计走 SQL 聚合 | `COUNT(*)`/`SUM(CASE WHEN ...)` 替代拉全表内存循环 |
| 高频子组件 React.memo | `AssignmentCard` / `StatusBadge` / `EmptyState` |
| 虚拟化大表 | `@tanstack/react-virtual` |
| 破坏性操作独立权限 | `AUDIT_LOG_PURGE``AUDIT_LOG_READ`,沿用旧项目规则 |
| i18n key 命名 | 禁止包含 `.`,动态 key 必须包含完整嵌套路径(`t(\`type.${type}\`)` |
| i18n 翻译文件对称性 | zh-CN 与 en 必须同步新增/删除 key |
| 类型守卫优先 | JSON.parse 结果经 `unknown` 中转 + 守卫;禁止 `as` 断言(除非从 `unknown` 转换) |
| 函数返回值显式标注 | 特别是 `Promise<T>` |
| 仅类型导入 | `import type` |
| 场景 | 技术/规则 |
| --------------------- | ------------------------------------------------------------------------------------ |
| React 19 乐观更新 | `useOptimistic` 替代手动 isPending配合 `useTransition` 自动管理回滚 |
| Zustand 细粒度选择器 | 单字段 selector 优于 `useShallow` 多字段包装 |
| Tiptap SSR | 必须 `immediatelyRender: false` 避免 hydration mismatch |
| 请求级去重 | React `cache()` 包装优于跨请求缓存(权限数据易变,不用 `unstable_cache` |
| 批量 SQL | INSERT batch `db.insert().values([...])` + UPDATE `CASE WHEN` 单次执行,禁止循环单条 |
| 动态导入重 UI 库 | `xxx-inner.tsx` + `xxx.tsx` lazy wrappertiptap / @xyflow/react / AI SDK |
| ReactFlowProvider | 留在外层同步渲染,保证 lazy 子组件 hook 可用 |
| 递归删除 | 批量收集后代 ID + `inArray` 单次删除,禁止递归逐个 |
| 统计走 SQL 聚合 | `COUNT(*)`/`SUM(CASE WHEN ...)` 替代拉全表内存循环 |
| 高频子组件 React.memo | `AssignmentCard` / `StatusBadge` / `EmptyState` 等 |
| 虚拟化大表 | `@tanstack/react-virtual` |
| 破坏性操作独立权限 | `AUDIT_LOG_PURGE``AUDIT_LOG_READ`,沿用旧项目规则 |
| i18n key 命名 | 禁止包含 `.`,动态 key 必须包含完整嵌套路径(`t(\`type.${type}\`)` |
| i18n 翻译文件对称性 | zh-CN 与 en 必须同步新增/删除 key |
| 类型守卫优先 | JSON.parse 结果经 `unknown` 中转 + 守卫;禁止 `as` 断言(除非从 `unknown` 转换) |
| 函数返回值显式标注 | 特别是 `Promise<T>` |
| 仅类型导入 | `import type` |
### 1.9 架构工具与验证命令
| 场景 | 命令/规则 |
| --------------------- | ---------------------------------------------------------------------- |
| arch.db 扫描 | `npm run arch:scan`多语言TS+Go+Python |
| arch.db 查询 | `npm run arch:query -- <command>` |
| 查服务依赖 | `npm run arch:query -- service <service>` |
| 查 proto 契约 | `npm run arch:query -- contracts` |
| 查 Kafka 事件 | `npm run arch:query -- events` |
| 查架构违规 | `npm run arch:query -- violations`输出 0 为合格) |
| proto lint | `cd packages/shared-proto && pnpm exec buf lint` |
| proto breaking 检测 | `pnpm exec buf breaking --against '.git#branch=main'` |
| proto 代码生成 | `pnpm proto:gen` |
| Go 编译验证 | `cd services/api-gateway && go build ./... && go vet ./...` |
| TS 类型检查 | `pnpm -r exec tsc --noEmit` |
| 全量 lint | `pnpm lint` |
| 全量测试 | `pnpm test` |
| 启动最小基础设施 | `docker compose -f infra/docker-compose.minimal.yml up -d` |
| 场景 | 命令/规则 |
| ------------------- | ----------------------------------------------------------- |
| arch.db 扫描 | `pnpm run arch:scan`多语言TS+Go+Python+Proto |
| arch.db 查询 | `pnpm run arch:query -- <command>` |
| 查服务依赖 | `pnpm run arch:query -- deps <module>` |
| 查 proto 契约 | `pnpm run arch:query -- sql "SELECT * FROM contracts"` |
| 查 Kafka 事件 | `pnpm run arch:query -- sql "SELECT * FROM events"` |
| 查架构违规 | `pnpm run arch:query -- violations`骨架P1 后期补全) |
| proto lint | `cd packages/shared-proto && pnpm exec buf lint` |
| proto breaking 检测 | `pnpm exec buf breaking --against '.git#branch=main'` |
| proto 代码生成 | `pnpm proto:gen` |
| Go 编译验证 | `cd services/api-gateway && go build ./... && go vet ./...` |
| TS 类型检查 | `pnpm -r exec tsc --noEmit` |
| 全量 lint | `pnpm lint` |
| 全量测试 | `pnpm test` |
| 启动最小基础设施 | `docker compose -f infra/docker-compose.minimal.yml up -d` |
---
@@ -151,138 +151,138 @@
### 2.1 api-gatewayGo
| 场景 | 技术/规则 |
| ------------------- | ------------------------------------------------------------------------------------------ |
| P1 鉴权 | Gateway 内置 HS256 JWT`jwt.ParseWithClaims` + `SigningMethodHMAC` 校验 |
| P2 鉴权升级 | 改 RS256IAM 私钥签发Gateway 公钥校验,无需调 IAM |
| 路由转发 | `gin.Group("/api/v1")` + `httputil.NewSingleHostReverseProxy` |
| 路径重写 | 去掉 `/api/v1` 前缀后转发到下游服务 |
| 用户上下文注入 | `c.Request.Header.Set("x-user-id", claims.UserID)` 传递给下游 |
| 请求 ID | Gateway 生成或透传 `X-Request-ID``c.Set("request_id", ...)` + `c.Header(...)` |
| P1 不做 | 限流/熔断/灰度P6 硬化阶段实现) |
| 健康检查 | `GET /health` 无需鉴权 |
| 场景 | 技术/规则 |
| -------------- | ------------------------------------------------------------------------------- |
| P1 鉴权 | Gateway 内置 HS256 JWT`jwt.ParseWithClaims` + `SigningMethodHMAC` 校验 |
| P2 鉴权升级 | 改 RS256IAM 私钥签发Gateway 公钥校验,无需调 IAM |
| 路由转发 | `gin.Group("/api/v1")` + `httputil.NewSingleHostReverseProxy` |
| 路径重写 | 去掉 `/api/v1` 前缀后转发到下游服务 |
| 用户上下文注入 | `c.Request.Header.Set("x-user-id", claims.UserID)` 传递给下游 |
| 请求 ID | Gateway 生成或透传 `X-Request-ID``c.Set("request_id", ...)` + `c.Header(...)` |
| P1 不做 | 限流/熔断/灰度P6 硬化阶段实现) |
| 健康检查 | `GET /health` 无需鉴权 |
### 2.2 classesTS/NestJSP1 黄金模板)
| 场景 | 技术/规则 |
| ------------------------- | ----------------------------------------------------------------------------------------------- |
| 黄金模板定位 | 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+ E2EPlaywright |
| 覆盖率门槛 | 领域逻辑 ≥ 80%Handler ≥ 60%,整体 ≥ 60% |
| Drizzle schema | `mysqlTable` + `varchar`/`timestamp` + `index` |
| ID 生成 | `@paralleldrive/cuid2` 的 `createId()` |
| 响应转换 | repository 返回 Dateservice 转换为 `createdAt: number`(时间戳) |
| 阶段特有模式回写 | OutboxP3/ CDCP4/ 长连接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+ E2EPlaywright |
| 覆盖率门槛 | 领域逻辑 ≥ 80%Handler ≥ 60%,整体 ≥ 60% |
| Drizzle schema | `mysqlTable` + `varchar`/`timestamp` + `index` |
| ID 生成 | `@paralleldrive/cuid2` 的 `createId()` |
| 响应转换 | repository 返回 Dateservice 转换为 `createdAt: number`(时间戳) |
| 阶段特有模式回写 | OutboxP3/ CDCP4/ 长连接P5实现后回写黄金模板 README |
### 2.3 iamTS/NestJSP2
| 场景 | 技术/规则 |
| --------------------- | ------------------------------------------------------------------------------------------ |
| 认证 | 登录/登出/JWT/2FARS256 非对称签名 |
| RBAC | 角色/权限/角色-权限 CRUD + `getEffectivePermissions(userId)` API |
| 视口配置 | 4 层模型(导航/路由/组件/数据),`role_viewports` 表 |
| DataScope 解析 | 6 级数据范围,注入 JWT payload |
| JWT payload | `{ userId, roles, permissions(bitmap), dataScope, exp }` |
| Token TTL | access 15min / refresh 7dayrefresh 用 Redis 黑名单失效 |
| 权限缓存 | `getEffectivePermissions` 结果 Redis 缓存 TTL 5 分钟,角色变更主动失效 |
| schema 表 | users / roles / permissions / role_permissions / role_viewports / parent_student_relations / class_subject_teachers |
| 场景 | 技术/规则 |
| -------------- | ------------------------------------------------------------------------------------------------------------------- |
| 认证 | 登录/登出/JWT/2FARS256 非对称签名 |
| RBAC | 角色/权限/角色-权限 CRUD + `getEffectivePermissions(userId)` API |
| 视口配置 | 4 层模型(导航/路由/组件/数据),`role_viewports` 表 |
| DataScope 解析 | 6 级数据范围,注入 JWT payload |
| JWT payload | `{ userId, roles, permissions(bitmap), dataScope, exp }` |
| Token TTL | access 15min / refresh 7dayrefresh 用 Redis 黑名单失效 |
| 权限缓存 | `getEffectivePermissions` 结果 Redis 缓存 TTL 5 分钟,角色变更主动失效 |
| schema 表 | users / roles / permissions / role_permissions / role_viewports / parent_student_relations / class_subject_teachers |
### 2.4 core-eduTS/NestJSP3
| 场景 | 技术/规则 |
| --------------------- | ------------------------------------------------------------------------------------------ |
| 考试全生命周期 | 教师创建 → 发布 → 学生作答 → 教师批改 → 成绩统计 |
| Outbox 模式 | 业务事务同写 `outbox_events` 表,后台 relay worker 投递 Kafka |
| Outbox relay | Go 写独立服务 `services/outbox-relay/`,轻量高吞吐 |
| Kafka topic | `exam.published` / `homework.graded` / `grade.recorded` |
| 不引入 Saga | 跨服务一致性用 Outbox + 最终一致性 |
| 批改后联动 | 批改完成 → 发 `homework.graded` 事件 → 下游消费DataAna/Msg |
| Temporal 试点 | 仅 1 个工作流(考试发布编排:创建作业→通知) |
| schema 表 | exams / exam_questions / homework_assignments / homework_submissions / homework_answers / grade_records / outbox_events |
| 场景 | 技术/规则 |
| -------------- | ----------------------------------------------------------------------------------------------------------------------- |
| 考试全生命周期 | 教师创建 → 发布 → 学生作答 → 教师批改 → 成绩统计 |
| Outbox 模式 | 业务事务同写 `outbox_events` 表,后台 relay worker 投递 Kafka |
| Outbox relay | Go 写独立服务 `services/outbox-relay/`,轻量高吞吐 |
| Kafka topic | `exam.published` / `homework.graded` / `grade.recorded` |
| 不引入 Saga | 跨服务一致性用 Outbox + 最终一致性 |
| 批改后联动 | 批改完成 → 发 `homework.graded` 事件 → 下游消费DataAna/Msg |
| Temporal 试点 | 仅 1 个工作流(考试发布编排:创建作业→通知) |
| schema 表 | exams / exam_questions / homework_assignments / homework_submissions / homework_answers / grade_records / outbox_events |
### 2.5 contentTS/NestJSP4
| 场景 | 技术/规则 |
| --------------------- | ------------------------------------------------------------------------------------------ |
| 知识图谱 | Neo4j 查询前置依赖图(秒级返回) |
| 题库 CRUD | P4 仅 CRUDP5 引入 ES 实现检索,避免 MySQL FULLTEXT → ES 迁移成本 |
| 双写避免 | Neo4j/ES 不直接双写,由消费 Kafka 事件同步,天然最终一致 |
| Neo4j 写入 | Content 服务写 MySQL 同时发事件,独立 worker 消费事件同步 Neo4j |
| 场景 | 技术/规则 |
| ---------- | ------------------------------------------------------------------ |
| 知识图谱 | Neo4j 查询前置依赖图(秒级返回) |
| 题库 CRUD | P4 仅 CRUDP5 引入 ES 实现检索,避免 MySQL FULLTEXT → ES 迁移成本 |
| 双写避免 | Neo4j/ES 不直接双写,由消费 Kafka 事件同步,天然最终一致 |
| Neo4j 写入 | Content 服务写 MySQL 同时发事件,独立 worker 消费事件同步 Neo4j |
### 2.6 data-anaPython/FastAPIP4
| 场景 | 技术/规则 |
| --------------------- | ------------------------------------------------------------------------------------------ |
| 学情诊断 | ClickHouse 宽表查询5s 内返回 |
| CDC 链路 | Debezium 监听 MySQL binlog → Kafka`mysql.cdc.*`)→ DataAna 消费写 ClickHouse |
| CDC 延迟监控 | Debezium 暴露 lag metrics超阈值告警 |
| 双轨读策略 | 实时查 MySQL 主库(刚提交的成绩),聚合查 CH 宽表(延迟 1-5s 可接受) |
| 幂等消费 | 所有事件消费者必须幂等(基于 event_id 去重) |
| 场景 | 技术/规则 |
| ------------ | ------------------------------------------------------------------------------ |
| 学情诊断 | ClickHouse 宽表查询5s 内返回 |
| CDC 链路 | Debezium 监听 MySQL binlog → Kafka`mysql.cdc.*`)→ DataAna 消费写 ClickHouse |
| CDC 延迟监控 | Debezium 暴露 lag metrics超阈值告警 |
| 双轨读策略 | 实时查 MySQL 主库(刚提交的成绩),聚合查 CH 宽表(延迟 1-5s 可接受) |
| 幂等消费 | 所有事件消费者必须幂等(基于 event_id 去重) |
### 2.7 messagingTS/NestJSP5
| 场景 | 技术/规则 |
| --------------------- | ------------------------------------------------------------------------------------------ |
| 消息 CRUD | 会话/消息 + 调 Push Gateway 推送 + 通知偏好 |
| 通知批量化 | `createNotifications(items)` 单次 INSERT沿用旧项目 dispatcher 模式 |
| 多渠道 | 站内/SMS/邮件/微信in_app 批量 + 其他渠道并行 |
| fan-out 分页 | `getAllUserIds(limit=1000, offset)` 分页遍历 |
| 撤回不乐观更新 | 需服务端返回判断 2 分钟窗口 |
| 场景 | 技术/规则 |
| -------------- | -------------------------------------------------------------------- |
| 消息 CRUD | 会话/消息 + 调 Push Gateway 推送 + 通知偏好 |
| 通知批量化 | `createNotifications(items)` 单次 INSERT沿用旧项目 dispatcher 模式 |
| 多渠道 | 站内/SMS/邮件/微信in_app 批量 + 其他渠道并行 |
| fan-out 分页 | `getAllUserIds(limit=1000, offset)` 分页遍历 |
| 撤回不乐观更新 | 需服务端返回判断 2 分钟窗口 |
### 2.8 push-gatewayGoP5
| 场景 | 技术/规则 |
| --------------------- | ------------------------------------------------------------------------------------------ |
| WebSocket 长连接 | 单节点支撑 10w+ 连接,业务服务只需发 Kafka 消息 |
| 跨实例同步 | Redis PubSub |
| 离线消息 | 仅推在线用户,离线消息存 MySQL上线时拉取 |
| 场景 | 技术/规则 |
| ---------------- | ----------------------------------------------- |
| WebSocket 长连接 | 单节点支撑 10w+ 连接,业务服务只需发 Kafka 消息 |
| 跨实例同步 | Redis PubSub |
| 离线消息 | 仅推在线用户,离线消息存 MySQL上线时拉取 |
### 2.9 ai-gatewayPython/FastAPIP5
| 场景 | 技术/规则 |
| --------------------- | ------------------------------------------------------------------------------------------ |
| LLM Provider 适配 | OpenAI/Anthropiclangchain/litellm 生态 |
| Prompt 模板管理 | 版本管理友好 |
| 流式 SSE | AI 网关 → BFF → 前端三层透传BFF 不缓冲 |
| 用量计费 | 按 token 计费 |
| AI 模块纯服务端 | Zod 验证 + 失败降级返回空(沿用旧项目模式) |
| 场景 | 技术/规则 |
| ----------------- | ------------------------------------------- |
| LLM Provider 适配 | OpenAI/Anthropiclangchain/litellm 生态 |
| Prompt 模板管理 | 版本管理友好 |
| 流式 SSE | AI 网关 → BFF → 前端三层透传BFF 不缓冲 |
| 用量计费 | 按 token 计费 |
| AI 模块纯服务端 | Zod 验证 + 失败降级返回空(沿用旧项目模式) |
### 2.10 shared-proto契约包
| 场景 | 技术/规则 |
| --------------------- | ------------------------------------------------------------------------------------------ |
| 目录结构 | `proto/*.proto` + `buf.yaml` + `buf.gen.yaml` |
| P1 契约 | 仅 `classes.proto``iam.proto`/`core_edu.proto` 占位 |
| 代码生成输出 | TS → `shared-ts/generated`Go → `shared-go/`Python → `services/*/generated/` |
| 场景 | 技术/规则 |
| ------------ | ------------------------------------------------------------------------------- |
| 目录结构 | `proto/*.proto` + `buf.yaml` + `buf.gen.yaml` |
| P1 契约 | 仅 `classes.proto``iam.proto`/`core_edu.proto` 占位 |
| 代码生成输出 | TS → `shared-ts/generated`Go → `shared-go/`Python → `services/*/generated/` |
### 2.11 arch-scan多语言扫描器
| 场景 | 技术/规则 |
| --------------------- | ------------------------------------------------------------------------------------------ |
| TS 扫描 | ts-morph 解析 AST提取导出/函数/类/import |
| Go 扫描 | P1 用正则提取(函数/类型/importP2 起替换为 tree-sitter-go AST |
| Python 扫描 | P1 用正则提取P4 起替换为 tree-sitter-python AST |
| arch.db schema | modules/symbols/dependencies/contracts/events/violations 六表 |
| 全量扫描 | 先清空旧数据再扫描,避免残留 |
| 并行扫描风险 | 并行子代理执行 arch:scan 可能因竞争报 FOREIGN KEY 错误,必须串行执行 |
| 扫描后验证 | `npm run arch:query -- violations` 输出 0 为合格 |
| Windows 路径 | 自定义 ESLint 规则加载用 `path.join`,不用 `path.posix.join` |
| 场景 | 技术/规则 |
| -------------- | ----------------------------------------------------------------------------------------------- |
| TS 扫描 | regex 提取function/class/interface/UPPER_CASE const避免 ts-morph 对未安装依赖文件解析失败 |
| Go 扫描 | 正则提取(行首锚定 `^func`/`^type`P2 起替换为 tree-sitter-go AST |
| Python 扫描 | 正则提取(行首锚定 `^def`/`^class`P4 起替换为 tree-sitter-python AST |
| arch.db schema | modules/symbols/dependencies/contracts/events/violations 六表 |
| 全量扫描 | 先清空旧数据再扫描,避免残留 |
| 并行扫描风险 | 并行子代理执行 arch:scan 可能因竞争报 FOREIGN KEY 错误,必须串行执行 |
| 扫描后验证 | `npm run arch:query -- violations` 输出 0 为合格 |
| Windows 路径 | 自定义 ESLint 规则加载用 `path.join`,不用 `path.posix.join` |
### 2.12 teacher-portal微前端宿主P1 测试页)
| 场景 | 技术/规则 |
| --------------------- | ------------------------------------------------------------------------------------------ |
| P1 测试页 | 单一 Next.js 应用,验证 classes CRUD 端到端链路 |
| API 调用 | `fetch(${API_BASE}/api/v1/classes)` + `Authorization: Bearer ${TEST_JWT}` |
| P1 测试 JWT | 开发工具生成 HS256 tokenP2 起由 IAM 签发 RS256 |
| P2 Module Federation | next.config.js 配置,按场景域分 4 个稳定 portal |
| 场景 | 技术/规则 |
| -------------------- | ------------------------------------------------------------------------- |
| P1 测试页 | 单一 Next.js 应用,验证 classes CRUD 端到端链路 |
| API 调用 | `fetch(${API_BASE}/api/v1/classes)` + `Authorization: Bearer ${TEST_JWT}` |
| P1 测试 JWT | 开发工具生成 HS256 tokenP2 起由 IAM 签发 RS256 |
| P2 Module Federation | next.config.js 配置,按场景域分 4 个稳定 portal |
---
@@ -290,6 +290,15 @@
> 按时间倒序50 条上限。AI 发现更好方案时可更新本节。
| 日期 | 时间 | 模块 | 做了什么 + 学到什么 |
| ---------- | ----- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 2026-07-07 | 全天 | 全局 | 文档体系初始化从旧项目e:\Desktop\CICDNext.js 单体)迁移 spec + plan + known-issues 模板到新仓库e:\Desktop\Edu微服务架构。known-issues 重组为微服务分区:多语言 monorepo / Docker Compose / protobuf+buf / NestJS / Go Gateway / 可观测性 / 微前端。从旧项目提炼可迁移经验React 19 useOptimistic / Zustand 细粒度选择器 / Tiptap SSR / 请求级去重 / 批量 SQL / 动态导入模式 / arch:scan 串行执行。新增微服务特有经验:契约先行 / Outbox / CDC / 双轨读 / DataScope / 黄金模板复制流程。路线图按 6 阶段组织P1 地基 → P2 身份 → P3 核心教学 → P4 内容分析 → P5 沟通AI → P6 硬化。 |
| 日期 | 时间 | 模块 | 做了什么 + 学到什么 |
| ---------- | ---- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 2026-07-08 | 全天 | 全局 | **P6 后续工作手册执行**:完整执行 post-p6-followup.md 12 节任务。环境准备pnpm 925 包 + go mod tidy 双服务 + uv sync 双服务 + buf 安装)→ 代码质量校验Go vet/build 0 错误Python ruff 8 错误自动修复)→ arch.db 同步(实现 4 个扫描器骨架,输出 12 模块/233 符号/138 契约)→ project_rules.md P0 修复(迁移到 .trae/rules/17881 字节)→ 004 架构图修复1.1a/1.1b 双图 + 1.2 业务领域列 + 5.4 视口四层)→ P6 集成测试10 Go + 17 bash = 27 用例全通过)→ Helm Chart 演化8 chart lint 通过)。**学到**:多语言 monorepo 工具链配置需统一镜像源npmmirror/goproxy.cn/tunago.work BOM 字符会导致 `unexpected input character` 错误必须重写文件。 |
| 2026-07-08 | 上午 | 全局 | pnpm install 网络失败ECONNRESET→ 配置 `npm config set registry https://registry.npmmirror.com` + `pnpm config set registry https://registry.npmmirror.com` 重试成功。**学到**Windows 下 pnpm 还需配置 `PNPM_HOME` 和 `TMP` 环境变量避免 `_tmp_` 文件 ENOENT 错误。 |
| 2026-07-08 | 上午 | 全局 | project_rules.md 损坏72 字节乱码,从 P1 提交 2ba4250 就损坏git 历史无完整版本)→ 从 CICD 项目完整版迁移到 `e:\Desktop\Edu\.trae\rules\project_rules.md`(按用户要求放 .trae/rules/),按 MIGRATION_GUIDE 4.1 策略矩阵调整为微服务版13 章 17881 字节),删除根目录损坏文件,更新 7 处引用README/MIGRATION_GUIDE/004/known-issues/git-workflow/coding-standards。**学到**:迁移文件后必须 `Get-Item | Select Length` 验证完整性 + 全文搜索引用更新git commit 前运行 cat 检查内容。 |
| 2026-07-08 | 上午 | api-gateway | go.work BOM 字符 + 版本不匹配:`unexpected input character '\ufeff'` 和 `module requires go >= 1.22.0, but go.work lists go 1.22`。**修复**:重写 go.work 去除 BOM版本改为 `go 1.26.0`,移除不存在的 `./packages/shared-go`。**学到**PowerShell `Out-File` 默认加 BOM写 go.work 这类敏感文件应用 `Write` 工具或 `[System.IO.File]::WriteAllText` 指定 UTF8 无 BOM。 |
| 2026-07-08 | 上午 | arch-scan | arch:scan 返回 0 模块 0 符号 → 4 个扫描器ts/go/py/proto都是骨架实现。**修复**:完整实现 4 个扫描器TS 用 regex 提取(避免 ts-morph 对未安装依赖文件解析失败Go/Python 用行首锚定正则Proto 扫描 service/message/rpc。结果12 模块≥10 ✓、233 符号≥100 ✓、138 契约。**学到**ts-morph Project 对未 `pnpm install` 的 workspace 文件会报模块解析失败,改用 regex 更鲁棒scanner.ts main() 开头需 `DELETE FROM` 清空旧数据避免重跑重复。 |
| 2026-07-08 | 下午 | 004 | 架构图视角讨论(技术分层 vs 业务领域)→ 双图并存方案1.1a 技术分层视角(部署/流量/网络边界Users 层标注"场景域用户"BFF 层标注"按场景域分"+ 1.1b 业务领域视角6 DDD 限界上下文 subgraphD1 身份/D2 教学组织/D3 教学核心/D4 内容/D5 沟通/D6 智能洞察。1.2 服务清单新增"业务领域"列。**学到**双图互补1.1a 服务运维/SRE 视角1.1b 服务产品/架构视角同一服务可横跨多领域core-edu 同时承载 D2+D3。 |
| 2026-07-08 | 下午 | 004 | 视口四层模型补充5.4 章节L1 导航navigation_config 表)/ L2 路由route_permission + Gateway 校验)/ L3 组件usePermission().hasPermission/ L4 数据DataScope 枚举)。场景域 BFF 复用策略:按使用场景域分 BFF 而非按角色分,教导主任复用 Teacher BFF + 额外管理视口。iam 服务职责:认证 + RBAC + 视口配置 + DataScope + 权限解析 API。**学到**视口既可独立配置RoleViewport 表)也可由权限推导,新角色只需配权限集,视口自动推导。 |
| 2026-07-08 | 下午 | api-gateway | P6 集成测试补充circuit-breaker_test.go5 用例ClosedToOpen/OpenToHalfOpen/HalfOpenToClosed/HalfOpenToOpen/4xxNotCounted+ ratelimit_test.go5 用例AllowUnderBurst/RejectOverBurst/RefillTokens/PerIPIsolation/CleanupExpiredBuckets+ test-backup-mysql.sh8 用例 17 断言)。**学到**gobreaker v2 ReadyToTrip 在 1 次失败后就触发(`TotalFailures*2 > Requests` 当 Requests=1 时 1*2>1=trueHALF_OPEN 状态只在探测执行期间可见,探测完成后立即转 CLOSED 或回 OPEN测试需通过行为503 vs 500而非状态字段验证rateLimiter cleanup 测试需用短周期参数50ms/500ms加速且新鲜桶要在旧桶清理后再创建避免被一起清掉。 |
| 2026-07-08 | 下午 | infra/k8s | Helm Chart 演化:安装 Helm v4.2.2,创建 edu-platform 平台级 chartnamespace/configmap/secret/ingress/hpa + 4 环境 values 文件)+ api-gateway 服务级 chart完整迁移自原 deployment.yaml参数化所有字段+ 6 业务服务 chart 桩iam/core-edu/content/msg/data-ana/ai。删除原 api-gateway-deployment.yaml保留 namespace.yaml。**学到**Helm `{{- with ... -}}` 双向修剪会导致标签连在一行(`managed-by: Helmpart-of: edu-platform`),应改为 `{{- with ... }}` 只修剪左侧;`helm lint` 全部通过但 `helm template` 才能发现 YAML 渲染错误,验证时两个都要跑。 |
| 2026-07-07 | 全天 | 全局 | 文档体系初始化从旧项目e:\Desktop\CICDNext.js 单体)迁移 spec + plan + known-issues 模板到新仓库e:\Desktop\Edu微服务架构。known-issues 重组为微服务分区:多语言 monorepo / Docker Compose / protobuf+buf / NestJS / Go Gateway / 可观测性 / 微前端。从旧项目提炼可迁移经验React 19 useOptimistic / Zustand 细粒度选择器 / Tiptap SSR / 请求级去重 / 批量 SQL / 动态导入模式 / arch:scan 串行执行。新增微服务特有经验:契约先行 / Outbox / CDC / 双轨读 / DataScope / 黄金模板复制流程。路线图按 6 阶段组织P1 地基 → P2 身份 → P3 核心教学 → P4 内容分析 → P5 沟通AI → P6 硬化。 |