feat(ai): 完善AI网关服务并添加LLM降级模式

config.py 加openai_api_key/base_url/dev_mode

新建llm_client.py httpx异步调OpenAI REST API

main.py 业务路由加/ai前缀+降级模式+readyz端点

Gateway添加/notifications和/ai路由

docs: known-issues记录P5三服务经验
This commit is contained in:
SpecialX
2026-07-09 09:09:27 +08:00
parent dfb6d2bfc1
commit a70a74207e
6 changed files with 377 additions and 71 deletions

View File

@@ -254,31 +254,36 @@
### 2.7 messagingTS/NestJSP5
| 场景 | 技术/规则 |
| -------------- | -------------------------------------------------------------------- |
| 消息 CRUD | 会话/消息 + 调 Push Gateway 推送 + 通知偏好 |
| 通知批量化 | `createNotifications(items)` 单次 INSERT沿用旧项目 dispatcher 模式 |
| 多渠道 | 站内/SMS/邮件/微信in_app 批量 + 其他渠道并行 |
| fan-out 分页 | `getAllUserIds(limit=1000, offset)` 分页遍历 |
| 撤回不乐观更新 | 需服务端返回判断 2 分钟窗口 |
| 场景 | 技术/规则 |
| ------------- | ------------------------------------------------------------------ |
| 消息 CRUD | 会话/消息 + 调 Push Gateway 推送 + 通知偏好 |
| 通知批量化 | `createBatch(items)` 单次 INSERT沿用旧项目 dispatcher 模式 |
| 多渠道 | 站内/SMS/邮件/微信in_app 批量 + 其他渠道并行 |
| fan-out 分页 | `listByUserWithPagination(userId, page, pageSize)` 分页查询 |
| ES 降级 | ES_URL 未设置时 esClient=nullsafeIndex/safeSearch 跳过返回空结果 |
| Push 推送降级 | PUSH_GATEWAY_URL 未设置或连接失败时 try/catch 跳过,不影响 DB 写入 |
| db 常量导出 | database.ts 导出 `db` 常量替代 `getDb()` 函数 |
### 2.8 push-gatewayGoP5
| 场景 | 技术/规则 |
| ---------------- | ----------------------------------------------- |
| WebSocket 长连接 | 单节点支撑 10w+ 连接,业务服务只需发 Kafka 消息 |
| 跨实例同步 | Redis PubSub |
| 离线消息 | 仅推在线用户,离线消息存 MySQL上线时拉取 |
| 场景 | 技术/规则 |
| ---------------- | ---------------------------------------------------------------- |
| WebSocket 长连接 | 单节点支撑 10w+ 连接,业务服务只需调 /internal/push |
| 跨实例同步 | Redis PubSubRedisURL 配置,预留 P6 实现) |
| 离线消息 | 仅推在线用户,离线消息存 MySQL上线时拉取 |
| 并发写修复 | send chan + 单写协程模式,避免 gorilla/websocket 并发写竞争 |
| DEV_MODE 鉴权 | DEV_MODE=true 时接受 dev-token生产环境必须 JWT 校验 |
| 广播端点 | POST /internal/broadcastbody {event, data},调用 hub.Broadcast |
### 2.9 ai-gatewayPython/FastAPIP5
| 场景 | 技术/规则 |
| ----------------- | ------------------------------------------- |
| LLM Provider 适配 | OpenAI/Anthropiclangchain/litellm 生态 |
| Prompt 模板管理 | 版本管理友好 |
| 流式 SSE | AI 网关 → BFF → 前端三层透传BFF 不缓冲 |
| 用量计费 | 按 token 计费 |
| AI 模块纯服务端 | Zod 验证 + 失败降级返回空(沿用旧项目模式) |
| 场景 | 技术/规则 |
| ----------------- | --------------------------------------------------------------- |
| LLM Provider 适配 | OpenAI 兼容 REST APIhttpx 异步),不引入 openai SDK |
| 降级模式 | API key 为空或调用失败时返回骨架响应,标记 degraded: true |
| 流式 SSE | AI 网关 → BFF → 前端三层透传BFF 不缓冲 |
| 路由前缀 | 业务路由加 /ai 前缀APIRouter prefix="/ai"Gateway 代理 /ai |
| dev_mode tracer | dev_mode=true 时跳过 OTel exporter 初始化 |
### 2.10 shared-proto契约包
@@ -321,21 +326,22 @@
> 按时间倒序50 条上限。AI 发现更好方案时可更新本节。
| 日期 | 时间 | 模块 | 做了什么 + 学到什么 |
| ---------- | ---- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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 数据始终先落库。 |
| 2026-07-09 | 凌晨 | core-edu/api-gateway | **P3 核心教学服务端到端打通**(1) core-edu 服务系统性修复 13 项database.ts 导出 db 常量替代 getDb()env.ts JWT_SECRET 改 optional 加 DEV_MODEkafka.ts connectKafka 加 try/catch 不阻塞启动main.ts 去全局 /api 前缀 + connectKafka 改 void 非阻塞app.module 移除未用 AuthMiddleware/ClassesesModule 加 HealthModule3 个 controller 路由去前缀去 UseGuards 从 x-user-id 读身份exams/homework service datetime 列 ISO 字符串转 Date 修复 drizzle toISOString 错误;修正 10 处相对 import 路径health/lifecycle 改用 Drizzle 原生查询;新增 core-edu-init.sql 4 张表。(2) Gateway 路由扩展:发现 internal/routing/routing.go 是死代码(未被 main 引用),真正路由在 main.go在 main.go 添加 exams/homework/grades 三组路由(无尾斜杠+通配符);删除 routing.goconfig.go 加 CoreEduServiceURL。(3) DEV_MODE 环境变量问题Go 不自动加载 .env必须在启动前 export DEV_MODE=true 否则 dev-token 被拒 401。(4) E2E 验证POST /exams 201 → GET /exams/:id 200 → GET /exams/class/:id 200 → POST /homework 201 → POST /grades 201 → Outbox 3 条事件正确写入exam.failed 因 Kafka 未启动homework/grade pending。**学到**drizzle datetime 列需 Date 对象不是 ISO 字符串mapToDriverValue 调 toISOStringGo 项目 .env 不会自动加载需显式 export 或 godotenv 库NestJS controller 路由前缀与 Gateway 代理路径要协调Gateway 去掉 /api/v1 后转发controller 用裸路径如 'exams'Outbox 模式业务事务同写验证通过Kafka 未启动时事件 status=failed 但业务数据已落库。 |
| 2026-07-09 | 上午 | iam/teacher-bff/teacher-portal | **P2 身份阶段完整实现**(1) Gateway 公开路径白名单register/login/refresh解决无 token 死锁。(2) IAM schema 扩展users 加 dataScope新增 role_viewports 表。(3) RBAC 端点 4 个 GET。(4) 视口按 requiredPermission 过滤 + sortOrder 排序getEffectivePermissions 用 Set 去重。(5) JWT payload 含 dataScoperegister 自动分配 teacher 角色。(6) 种子数据 7 权限+12 映射+7 视口。(7) Teacher BFF 视口聚合。(8) 前端lib/auth.ts + login + AppShell + (app) 路由组 + dashboard + classes真实 JWT+ 根重定向。(9) E2E 全链路通过。**学到**Next.js 路由组 (app) 不影响 URL/login 与 /dashboard 共存只后者套壳fetch headers 函数返回 Record<string,string> 避免 TS2769ESLint 9 需 flat config 留 P6AppShell aside 用 flex flex-col + mt-auto 比 absolute 稳健。 |
| 2026-07-08 | 上 | iam/classes/api-gateway | **P1 端到端链路验证 + IAM 服务修复**:验证 register → JWT → Gateway /iam/me → Gateway /classes CRUD → teacher-portal 前端渲染全链路打通。(1) IAM 服务 14 个 TS 编译错误修复:移除 typeorm/ioredis/kafkajs 依赖IAM 用 Drizzlehealth.controller.ts 改用 `db.execute(sql\`SELECT 1\`)`lifecycle.service.ts 简化为只关闭 Drizzle 连接池Drizzle API 修正(`r.roles`→`r.iam_roles``.in()`→`inArray()`。(2) NestJS ESM DI 修复iam.module.ts 简化 providers 为 `[IamService, IamRepository]`iam.service.ts 构造器加 `@Inject(IamRepository)`(参考 classes 黄金模板),修复运行时 `Cannot read properties of undefined (reading 'findUserByEmail')`。(3) Gateway /iam/me 404 修复iam.controller.ts 直接读 `req.headers['x-user-id']`替代未注册的`AuthenticatedRequest`。(4) 创建 `scripts/iam-init.sql`建 6 张 IAM 表 + 种子数据。(5) E2E 验证iam:3002 注册/登录 → Gateway /iam/me 200 → Gateway GET /classes 200 → Gateway POST /classes合法 UUID gradeId201 → teacher-portal:3000 首页渲染 200 + 含"班级管理" → Next.js rewrites 透传 dev-token 到 Gateway 全链路通。**学到**NestJS ESM 模式下 DI 无法通过类型推断解析 token必须显式`@Inject(Token)`Drizzle select 返回字段名按 schema 定义而非表名classes.dto.ts 的 gradeId 要求 UUID 格式,测试数据不能用 "grade-12" 这类字符串PowerShell 控制台中文显示为 `?`是编码问题数据库实际存储正确DEV_MODE 下前端用`Bearer dev-token` 即可走通链路,无需真实 JWT。 |
| 2026-07-08 | 下午 | 全局 | **CI/CD 完整配置 + 多AI协作规范入规则**(1) project_rules.md 新增 §14 多 AI 协作规范(角色权限矩阵/分支命名/PR合并规则/跨模块变更顺序/冲突处理/AI 身份标注/敏感文件保护)+ §15 CI/CD 规范(流水线阶段/触发条件/镜像规范/部署策略/Secrets 管理/必需 CI 文件)。(2) 优化现有 4 个 ci-*.ymlci-ts.yml 加 arch-scan + docker-build jobci-go.yml 去掉 golangci-lintlint-staged 预存问题),加 docker-buildci-proto.yml 修复 buf breaking URL从 github.com 改为 .git 本地比较)。(3) 新增 `docker.yml`main/tag 触发,构建推送 3 服务镜像到 Gitea Container Registrygit.eazygame.cn/xiner/edu/<service>:latest + sha tag + version tag用 GITHUB_TOKEN 自动认证。(4) 新增 `deploy.yml`workflow_run 触发 + 手动 dispatchRunner 直接执行 docker compose pull && up -d10 次健康检查轮询,失败输出日志。(5) 新增 `infra/docker-compose.deploy.yml`(部署用,镜像来自 Gitea registry连接服务器已有 MySQL/Redis 通过 edu-shared 外部网络)+ `infra/deploy.env.example`(部署环境变量模板)。(6) 编写 `docs/standards/cicd-runbook.md`CI/CD 使用手册,含架构总览/一次性配置/日常使用/镜像管理/部署验证/回滚/常见问题/排查命令/安全注意事项)。**学到**Docker Compose 不支持 `restart_policy`(是 swarm 字段),用 `restart: unless-stopped` 替代Gitea Actions 兼容 GitHub Actions 语法但 `workflow_run` 触发可能不完整,备选手动 dispatch应用容器访问宿主机已有 MySQL/Redis 需通过共享外部网络(`docker network create edu-shared` + `docker network connect`)而非 `host.docker.internal`。 |
| 2026-07-08 | 下午 | api-gateway | **重定向循环修复 + 生产模式部署准备 + 多AI协作文档**(1) 修复 `ERR_TOO_MANY_REDIRECTS`Gin 默认 `RedirectTrailingSlash=true` 导致 `/api/v1/classes` → 301 → `/classes/`Next.js rewrites 代理时形成循环。**修复**`r.RedirectTrailingSlash=false` + 同时注册无尾斜杠路由(`/classes`)与通配符路由(`/classes/*path`)。(2) 新增 DEV_MODE 旁路:`config.go` 加 `DevMode` 字段,`auth.go` 在 `DEV_MODE=true` 时接受 `dev-token` 注入固定身份(生产必须 false。(3) 生产 Docker 化:新建 `apps/teacher-portal/Dockerfile`(多阶段 Next.js build+ `services/api-gateway/Dockerfile`(多阶段 Go 静态编译)+ `infra/docker-compose.prod.yml`三服务编排,强制 DEV_MODE=false)。(4) 编写 `docs/standards/local-dev-runbook.md`本地启动手册,含端口表/开发模式/生产模式/常见问题)+ `docs/standards/multi-ai-collaboration.md`多AI协作文档含模块分工矩阵/分支命名/PR流程/合并策略/冲突处理/权限矩阵)。**学到**Gin `RedirectTrailingSlash=false` 后需显式注册无尾斜杠路由(`Any("/classes")` + `Any("/classes/*path")`),否则 404Next.js rewrites 代理会透传 301 给浏览器形成循环,开发模式旁路应通过环境变量控制而非硬编码。 |
| 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 硬化。 |
| 日期 | 时间 | 模块 | 做了什么 + 学到什么 |
| ---------- | ---- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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 数据始终先落库。 |
| 2026-07-09 | 凌晨 | core-edu/api-gateway | **P3 核心教学服务端到端打通**(1) core-edu 服务系统性修复 13 项database.ts 导出 db 常量替代 getDb()env.ts JWT_SECRET 改 optional 加 DEV_MODEkafka.ts connectKafka 加 try/catch 不阻塞启动main.ts 去全局 /api 前缀 + connectKafka 改 void 非阻塞app.module 移除未用 AuthMiddleware/ClassesesModule 加 HealthModule3 个 controller 路由去前缀去 UseGuards 从 x-user-id 读身份exams/homework service datetime 列 ISO 字符串转 Date 修复 drizzle toISOString 错误;修正 10 处相对 import 路径health/lifecycle 改用 Drizzle 原生查询;新增 core-edu-init.sql 4 张表。(2) Gateway 路由扩展:发现 internal/routing/routing.go 是死代码(未被 main 引用),真正路由在 main.go在 main.go 添加 exams/homework/grades 三组路由(无尾斜杠+通配符);删除 routing.goconfig.go 加 CoreEduServiceURL。(3) DEV_MODE 环境变量问题Go 不自动加载 .env必须在启动前 export DEV_MODE=true 否则 dev-token 被拒 401。(4) E2E 验证POST /exams 201 → GET /exams/:id 200 → GET /exams/class/:id 200 → POST /homework 201 → POST /grades 201 → Outbox 3 条事件正确写入exam.failed 因 Kafka 未启动homework/grade pending。**学到**drizzle datetime 列需 Date 对象不是 ISO 字符串mapToDriverValue 调 toISOStringGo 项目 .env 不会自动加载需显式 export 或 godotenv 库NestJS controller 路由前缀与 Gateway 代理路径要协调Gateway 去掉 /api/v1 后转发controller 用裸路径如 'exams'Outbox 模式业务事务同写验证通过Kafka 未启动时事件 status=failed 但业务数据已落库。 |
| 2026-07-09 | 上 | iam/teacher-bff/teacher-portal | **P2 身份阶段完整实现**(1) Gateway 公开路径白名单register/login/refresh解决无 token 死锁。(2) IAM schema 扩展users 加 dataScope新增 role_viewports 表。(3) RBAC 端点 4 个 GET。(4) 视口按 requiredPermission 过滤 + sortOrder 排序getEffectivePermissions 用 Set 去重。(5) JWT payload 含 dataScoperegister 自动分配 teacher 角色。(6) 种子数据 7 权限+12 映射+7 视口。(7) Teacher BFF 视口聚合。(8) 前端lib/auth.ts + login + AppShell + (app) 路由组 + dashboard + classes真实 JWT+ 根重定向。(9) E2E 全链路通过。**学到**Next.js 路由组 (app) 不影响 URL/login 与 /dashboard 共存只后者套壳fetch headers 函数返回 Record<string,string> 避免 TS2769ESLint 9 需 flat config 留 P6AppShell aside 用 flex flex-col + mt-auto 比 absolute 稳健。 |
| 2026-07-08 | 晚上 | iam/classes/api-gateway | **P1 端到端链路验证 + IAM 服务修复**:验证 register → JWT → Gateway /iam/me → Gateway /classes CRUD → teacher-portal 前端渲染全链路打通。(1) IAM 服务 14 个 TS 编译错误修复:移除 typeorm/ioredis/kafkajs 依赖IAM 用 Drizzlehealth.controller.ts 改用 `db.execute(sql\`SELECT 1\`)`lifecycle.service.ts 简化为只关闭 Drizzle 连接池Drizzle API 修正(`r.roles`→`r.iam_roles``.in()`→`inArray()`)。(2) NestJS ESM DI 修复iam.module.ts 简化 providers 为 `[IamService, IamRepository]`iam.service.ts 构造器加 `@Inject(IamRepository)`(参考 classes 黄金模板),修复运行时 `Cannot read properties of undefined (reading 'findUserByEmail')`。(3) Gateway /iam/me 404 修复iam.controller.ts 直接读 `req.headers['x-user-id']`替代未注册的`AuthenticatedRequest`。(4) 创建 `scripts/iam-init.sql`建 6 张 IAM 表 + 种子数据。(5) E2E 验证iam:3002 注册/登录 → Gateway /iam/me 200 → Gateway GET /classes 200 → Gateway POST /classes合法 UUID gradeId201 → teacher-portal:3000 首页渲染 200 + 含"班级管理" → Next.js rewrites 透传 dev-token 到 Gateway 全链路通。**学到**NestJS ESM 模式下 DI 无法通过类型推断解析 token必须显式`@Inject(Token)`Drizzle select 返回字段名按 schema 定义而非表名classes.dto.ts 的 gradeId 要求 UUID 格式,测试数据不能用 "grade-12" 这类字符串PowerShell 控制台中文显示为 `?`是编码问题数据库实际存储正确DEV_MODE 下前端用`Bearer dev-token` 即可走通链路,无需真实 JWT。 |
| 2026-07-08 | 下午 | 全局 | **CI/CD 完整配置 + 多AI协作规范入规则**(1) project_rules.md 新增 §14 多 AI 协作规范(角色权限矩阵/分支命名/PR合并规则/跨模块变更顺序/冲突处理/AI 身份标注/敏感文件保护)+ §15 CI/CD 规范(流水线阶段/触发条件/镜像规范/部署策略/Secrets 管理/必需 CI 文件)。(2) 优化现有 4 个 ci-*.ymlci-ts.yml 加 arch-scan + docker-build jobci-go.yml 去掉 golangci-lintlint-staged 预存问题),加 docker-buildci-proto.yml 修复 buf breaking URL从 github.com 改为 .git 本地比较)。(3) 新增 `docker.yml`main/tag 触发,构建推送 3 服务镜像到 Gitea Container Registrygit.eazygame.cn/xiner/edu/<service>:latest + sha tag + version tag用 GITHUB_TOKEN 自动认证。(4) 新增 `deploy.yml`workflow_run 触发 + 手动 dispatchRunner 直接执行 docker compose pull && up -d10 次健康检查轮询,失败输出日志。(5) 新增 `infra/docker-compose.deploy.yml`部署用,镜像来自 Gitea registry连接服务器已有 MySQL/Redis 通过 edu-shared 外部网络)+ `infra/deploy.env.example`(部署环境变量模板)。(6) 编写 `docs/standards/cicd-runbook.md`CI/CD 使用手册,含架构总览/一次性配置/日常使用/镜像管理/部署验证/回滚/常见问题/排查命令/安全注意事项)。**学到**Docker Compose 不支持 `restart_policy`(是 swarm 字段),用 `restart: unless-stopped` 替代Gitea Actions 兼容 GitHub Actions 语法但 `workflow_run` 触发可能不完整,备选手动 dispatch应用容器访问宿主机已有 MySQL/Redis 需通过共享外部网络(`docker network create edu-shared` + `docker network connect`)而非 `host.docker.internal`。 |
| 2026-07-08 | 下午 | api-gateway | **重定向循环修复 + 生产模式部署准备 + 多AI协作文档**(1) 修复 `ERR_TOO_MANY_REDIRECTS`Gin 默认 `RedirectTrailingSlash=true` 导致 `/api/v1/classes` → 301 → `/classes/`Next.js rewrites 代理时形成循环。**修复**`r.RedirectTrailingSlash=false` + 同时注册无尾斜杠路由(`/classes`)与通配符路由(`/classes/*path`)。(2) 新增 DEV_MODE 旁路:`config.go` 加 `DevMode` 字段,`auth.go` 在 `DEV_MODE=true` 时接受 `dev-token` 注入固定身份(生产必须 false。(3) 生产 Docker 化:新建 `apps/teacher-portal/Dockerfile`(多阶段 Next.js build+ `services/api-gateway/Dockerfile`(多阶段 Go 静态编译)+ `infra/docker-compose.prod.yml`(三服务编排,强制 DEV_MODE=false。(4) 编写 `docs/standards/local-dev-runbook.md`(本地启动手册,含端口表/开发模式/生产模式/常见问题)+ `docs/standards/multi-ai-collaboration.md`多AI协作文档含模块分工矩阵/分支命名/PR流程/合并策略/冲突处理/权限矩阵)。**学到**Gin `RedirectTrailingSlash=false` 后需显式注册无尾斜杠路由(`Any("/classes")` + `Any("/classes/*path")`),否则 404Next.js rewrites 代理会透传 301 给浏览器形成循环,开发模式旁路应通过环境变量控制而非硬编码。 |
| 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 硬化。 |