fix: code compliance audit and fix across all services
NestJS (6 services): implement @RequirePermission decorator with SetMetadata+Reflector, register APP_GUARD globally, fix as assertions to type guards, add explicit return types, fix import type for express, fix /metrics implicit any, replace native Error with ApplicationError, remove typeorm remnants, register LifecycleService. teacher-bff: add logger, ApplicationError, GlobalErrorFilter, forward real userId to downstream, log downstream failures, migrate health controller to shared/health. Go (2 services): interface to any, doc comments, CORS dev whitelist, JWT secret fail-fast, push-gateway internal API auth, metrics and readyz endpoints, remove dead code. Python (2 services): lifespan return type, dev_mode to bool, data-ana APIRouter, ai POST body model, ClickHouse async wrapping.
This commit is contained in:
@@ -156,7 +156,7 @@ graph TB
|
||||
|
||||
subgraph D4["D4 内容资源领域"]
|
||||
CONTENT[content 服务]
|
||||
CONTENT_M[textbooks / knowledge-points<br/>questions / grading / search(ES)]
|
||||
CONTENT_M[textbooks / knowledge-points<br/>questions / grading / search]
|
||||
end
|
||||
|
||||
subgraph D5["D5 沟通通知领域"]
|
||||
@@ -167,19 +167,19 @@ graph TB
|
||||
subgraph D6["D6 智能洞察领域"]
|
||||
DATA[data-ana 服务]
|
||||
AI[ai 服务]
|
||||
DATA_M[analytics / dashboard / diagnostic(ClickHouse)]
|
||||
AI_M[ai 备课/出题/分析 / search]
|
||||
DATA_M[analytics / dashboard / diagnostic]
|
||||
AI_M[AI 备课 / 出题 / 分析 / 搜索]
|
||||
end
|
||||
|
||||
D1 --> D2
|
||||
D1 --> D3
|
||||
D1 --> D4
|
||||
D1 --> D5
|
||||
D2 --> D3
|
||||
D3 --> D4
|
||||
D3 --> D5
|
||||
D4 --> D6
|
||||
D3 --> D6
|
||||
IAM --> ORG
|
||||
IAM --> TEACH
|
||||
IAM --> CONTENT
|
||||
IAM --> MSG
|
||||
ORG --> TEACH
|
||||
TEACH --> CONTENT
|
||||
TEACH --> MSG
|
||||
CONTENT --> DATA
|
||||
TEACH --> DATA
|
||||
```
|
||||
|
||||
**双图并存说明**:
|
||||
@@ -517,24 +517,24 @@ graph LR
|
||||
Cmd[Command 命令] --> App[Application Service]
|
||||
App --> Domain[Domain 领域模型]
|
||||
Domain --> Repo[Repository 写模型]
|
||||
Repo -->[(MySQL 主库)]
|
||||
Repo --> mysql_w[(MySQL 主库)]
|
||||
App --> Outbox[(Outbox 表<br/>同事务)]
|
||||
end
|
||||
|
||||
subgraph Sync["同步链路"]
|
||||
Outbox --> Relay[Relay Worker]
|
||||
Relay --> Kafka[(Kafka)]
|
||||
Kafka --> Proj[Projection]
|
||||
Proj -->[(ClickHouse 宽表)]
|
||||
Proj -->[(Redis 缓存)]
|
||||
Proj -->[(ES 索引)]
|
||||
Relay --> kafka_sync[(Kafka)]
|
||||
kafka_sync --> Proj[Projection]
|
||||
Proj --> ch_sync[(ClickHouse 宽表)]
|
||||
Proj --> redis_sync[(Redis 缓存)]
|
||||
Proj --> es_sync[(ES 索引)]
|
||||
end
|
||||
|
||||
subgraph Read["读路径"]
|
||||
Query[Query 查询] --> ReadModel[Read Model]
|
||||
ReadModel -->[(ClickHouse 宽表)]
|
||||
ReadModel -->[(Redis 缓存)]
|
||||
ReadModel -->[(ES 索引)]
|
||||
ReadModel --> ch_read[(ClickHouse 宽表)]
|
||||
ReadModel --> redis_read[(Redis 缓存)]
|
||||
ReadModel --> es_read[(ES 索引)]
|
||||
end
|
||||
```
|
||||
|
||||
@@ -581,7 +581,7 @@ graph LR
|
||||
Outbox[(Outbox 表)]
|
||||
end
|
||||
|
||||
subgraph MySQL[("MySQL 主库")]
|
||||
subgraph MySQL["MySQL 主库"]
|
||||
BizTable[(业务表)]
|
||||
OutboxTable[(outbox 表)]
|
||||
end
|
||||
@@ -593,7 +593,7 @@ graph LR
|
||||
end
|
||||
|
||||
subgraph Bus["事件总线"]
|
||||
Kafka[(Kafka topic)]
|
||||
kafka_bus[(Kafka topic)]
|
||||
end
|
||||
|
||||
subgraph Consumers["消费者"]
|
||||
@@ -607,10 +607,10 @@ graph LR
|
||||
Repo --> OutboxTable
|
||||
OutboxTable --> Poll
|
||||
Poll --> Publish
|
||||
Publish --> Kafka
|
||||
Kafka --> Proj
|
||||
Kafka --> OtherSvc
|
||||
Proj -->[(ClickHouse/Redis/ES)]
|
||||
Publish --> kafka_bus
|
||||
kafka_bus --> Proj
|
||||
kafka_bus --> OtherSvc
|
||||
Proj --> read_stores[(ClickHouse / Redis / ES)]
|
||||
```
|
||||
|
||||
### 7.2 事件 Topic 分类
|
||||
|
||||
401
docs/architecture/ai-allocation.md
Normal file
401
docs/architecture/ai-allocation.md
Normal file
@@ -0,0 +1,401 @@
|
||||
# AI 分配方案与架构设计外包流程
|
||||
|
||||
> 版本:1.0
|
||||
> 日期:2026-07-09
|
||||
> 适用范围:Edu 微服务项目模块架构设计外包阶段
|
||||
> 关联文档:[多 AI 协作指南](../standards/multi-ai-collaboration.md)、[004 架构影响地图](./004_architecture_impact_map.md)、[待开发功能路线图](./roadmap/pending-features.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 外包总流程:三阶段
|
||||
|
||||
```
|
||||
阶段 1:全局理解 阶段 2:模块架构设计 阶段 3:按图实施
|
||||
(每个 AI 独立) (每个 AI 独立) (并行开发)
|
||||
│ │ │
|
||||
阅读全局架构文档 产出模块内部架构图 按自己画的图写代码
|
||||
理解边界与契约 定义内部模块/数据流 coord 定期巡检一致性
|
||||
理解与其他模块的接口 标注与其他模块的交互点 遇到偏差更新架构图
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
交付:理解确认书 交付:模块架构设计文档 交付:代码 + 更新图
|
||||
```
|
||||
|
||||
**阶段 1 目标**:每个 AI 读懂自己负责的模块在全局架构中的位置、边界、契约。
|
||||
**阶段 2 目标**:每个 AI 产出自己模块的内部架构设计,经过 coord 交叉审查后放行。
|
||||
**阶段 3 目标**:按设计文档写代码,coord 定期巡检一致性。
|
||||
|
||||
---
|
||||
|
||||
## 2. 完整服务清单
|
||||
|
||||
| 类别 | 服务名 | 语言/框架 | 限界上下文 | 阶段 | 状态 |
|
||||
| ---- | -------------- | ---------------- | ---------------------- | ------ | ----------------- |
|
||||
| 网关 | api-gateway | Go (Gin) | API 网关 | P1 | ✅ 已实现 |
|
||||
| 网关 | push-gateway | Go (Gin) | 推送网关 | P5 | 📐 需设计 |
|
||||
| BFF | teacher-bff | TS (NestJS) | 教学场景域聚合 | P2 | ✅ 已实现 |
|
||||
| BFF | student-bff | TS (NestJS) | 学习场景域聚合 | P3 | 📐 需设计 |
|
||||
| BFF | parent-bff | TS (NestJS) | 家长场景域聚合 | P4 | 📐 需设计 |
|
||||
| 业务 | iam | TS (NestJS) | 身份认证 | P2 | ✅ 已实现 |
|
||||
| 业务 | core-edu | TS (NestJS) | 教学核心(含 classes) | P3 | 📐 待合并 classes |
|
||||
| 业务 | content | TS (NestJS) | 内容资源 | P4 | 📐 需设计 |
|
||||
| 业务 | msg | TS (NestJS) | 消息通知 | P5 | 📐 需设计 |
|
||||
| 业务 | data-ana | Python (FastAPI) | 数据分析 | P4 | 📐 需设计 |
|
||||
| 业务 | ai | Python (FastAPI) | AI 网关 | P5 | 📐 需设计 |
|
||||
| 前端 | teacher-portal | TS (Next.js) | 教学场景域前端 | P2 | ✅ 已实现 |
|
||||
| 前端 | student-portal | TS (Next.js) | 学习场景域前端 | P3 | 📐 需设计 |
|
||||
| 前端 | parent-portal | TS (Next.js) | 家长场景域前端 | P4 | 📐 需设计 |
|
||||
| 前端 | admin-portal | TS (Next.js) | 管理场景域前端 | P6 | 📐 需设计 |
|
||||
| 共享 | shared-proto | protobuf | 契约 | 跨阶段 | ✅ 部分 |
|
||||
| 共享 | shared-ts | TS | TS 共享工具 | 跨阶段 | — |
|
||||
| 共享 | shared-go | Go | Go 共享工具 | 跨阶段 | — |
|
||||
| 共享 | shared-py | Python | Python 共享工具 | 跨阶段 | — |
|
||||
| 基础 | infra | — | K8s/Grafana/WAF | 跨阶段 | ✅ 部分 |
|
||||
|
||||
> 状态标记:✅ 已实现需审计 | 📐 需架构设计(本次外包核心产出)
|
||||
|
||||
---
|
||||
|
||||
## 3. AI 分配方案(7 AI + 1 coord)
|
||||
|
||||
### 3.1 分配原则
|
||||
|
||||
- **同语言内聚**:一个 AI 负责多个同语言服务,学习成本只付一次
|
||||
- **领域亲缘性**:同类业务放一起(BFF 归 BFF、Python 归 Python)
|
||||
- **工作负载均衡**:Neo4j+ES 的内容服务、ClickHouse 的分析服务复杂度高,不绑太多其他服务
|
||||
- **前端统一**:Module Federation 微前端由一人设计,保证 shell + remote 架构一致
|
||||
- **黄金模板对齐**:已实现的 services 负责 AI 需审计并对齐 classes 标准
|
||||
|
||||
### 3.2 分配矩阵
|
||||
|
||||
| AI 标识 | 语言 | 服务 | 数量 | 阶段归属 |
|
||||
| --------- | ------ | ----------------------------------------------------------- | ---- | -------- |
|
||||
| **ai01** | Go | api-gateway、push-gateway | 2 | P1 + P5 |
|
||||
| **ai02** | TS | iam | 1 | P2 |
|
||||
| **ai03** | TS | teacher-bff、core-edu | 2 | P2 + P3 |
|
||||
| **ai04** | TS | student-bff、parent-bff | 2 | P3 + P4 |
|
||||
| **ai05** | TS | content、msg | 2 | P4 + P5 |
|
||||
| **ai06** | Python | data-ana、ai | 2 | P4 + P5 |
|
||||
| **ai07** | TS | teacher-portal、student-portal、parent-portal、admin-portal | 4 | P2-P6 |
|
||||
| **coord** | — | shared-proto、shared-*、infra/、docs/、CI/CD | — | 跨阶段 |
|
||||
|
||||
### 3.3 为什么这样拆
|
||||
|
||||
| 决策 | 理由 |
|
||||
| ----------------------------- | ------------------------------------------------------------------------------- |
|
||||
| ai02 独立负责 iam | RBAC 三层角色 + DataScope 6 级 + 视口 4 层是整个系统的权限中枢,复杂度最高 |
|
||||
| ai03 teacher-bff + core-edu | 教学域全栈:BFF 聚合 + 核心业务。考试/作业/成绩状态机在一个人手里,不跨 AI 协调 |
|
||||
| ai04 student-bff + parent-bff | 两个 BFF 都是纯聚合层,技术同质(GraphQL + DataLoader),设计模式完全复用 |
|
||||
| ai05 content + msg | 都依赖 ES,content 建索引、msg 查索引,一人设计避免 ES 索引冲突 |
|
||||
| ai07 前端 4 端 | Module Federation shell + remote 架构需一人统一设计,4 端共享组件库和权限体系 |
|
||||
| coord 不写业务代码 | 专注契约管理 + 交叉审查,保证 7 份设计文档的接口一致性 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 各 AI 阶段 1 必读文档清单
|
||||
|
||||
以下为每个 AI 在阶段 1 必须按顺序阅读的文档(标注 ★ 为强制必读):
|
||||
|
||||
| 顺序 | 文档 | ai01 | ai02 | ai03 | ai04 | ai05 | ai06 | ai07 | coord |
|
||||
| ---- | ------------------------------------------------------------------- | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :---: |
|
||||
| 1 | [README.md](../../README.md) | ★ | ★ | ★ | ★ | ★ | ★ | ★ | ★ |
|
||||
| 2 | [MIGRATION_GUIDE.md](../../MIGRATION_GUIDE.md) | ★ | ★ | ★ | ★ | ★ | ★ | ★ | ★ |
|
||||
| 3 | [004 架构影响地图](./004_architecture_impact_map.md) | ★ | ★ | ★ | ★ | ★ | ★ | ★ | ★ |
|
||||
| 4 | [pending-features.md](./roadmap/pending-features.md) | ★ | ★ | ★ | ★ | ★ | ★ | ★ | ★ |
|
||||
| 5 | [project_rules.md](../../.trae/rules/project_rules.md) | ★ | ★ | ★ | ★ | ★ | ★ | ★ | ★ |
|
||||
| 6 | [coding-standards.md](../standards/coding-standards.md) | ★ | ★ | ★ | ★ | ★ | ★ | ★ | ★ |
|
||||
| 7 | [multi-ai-collaboration.md](../standards/multi-ai-collaboration.md) | ★ | ★ | ★ | ★ | ★ | ★ | ★ | ★ |
|
||||
| 8 | 黄金模板 `services/classes/src/` 全部源码 | ★ | ★ | ★ | ★ | — | — | — | ★ |
|
||||
| 9 | `packages/shared-proto/proto/` 全部 .proto | ★ | ★ | ★ | ★ | ★ | ★ | — | ★ |
|
||||
|
||||
**语言特定补充阅读**:
|
||||
|
||||
| AI | 语言 | 补充文档 |
|
||||
| ------- | -------- | --------------------------------------------------------------- |
|
||||
| ai01 | Go | `services/api-gateway/` 全部源码 |
|
||||
| ai02-05 | TS | `services/iam/`、`services/teacher-bff/` 源码(参考已实现模板) |
|
||||
| ai06 | Python | `services/data-ana/`、`services/ai/` 骨架源码 |
|
||||
| ai07 | TS/React | `apps/teacher-portal/` 全部源码、Module Federation 配置 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 各 AI 阶段 2 设计重点
|
||||
|
||||
### ai01 — Go 网关层
|
||||
|
||||
| 服务 | 设计重点 |
|
||||
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| api-gateway | 路由表矩阵(路径 → 下游服务 + 端口,需覆盖全部 6 个业务服务 + 3 个 BFF);限流策略表(每路由 QPS);熔断阈值配置(错误率/延迟阈值);JWT RS256 公钥校验流程;CORS 白名单;请求 ID 注入 |
|
||||
| push-gateway | WebSocket 连接生命周期(认证 → 心跳 → 断线重连);与 msg 的 gRPC 推送通道协议;用户 session 映射(在线用户 → WebSocket 连接);水平扩展方案(Redis Pub/Sub 跨实例广播) |
|
||||
|
||||
### ai02 — 身份认证
|
||||
|
||||
| 服务 | 设计重点 |
|
||||
| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| iam | RBAC 权限点枚举(全部模块的 CRUD 权限常量);三层角色模型(系统/组织/临时)权限合并规则;DataScope 6 级 SQL WHERE 注入规则(每级对应的过滤条件);视口 4 层配置表设计(导航/路由/组件/数据);JWT RS256 私钥签发 + 公钥暴露端点;refresh_token 轮换策略;权限解析 API(getEffectivePermissions → permissions + viewports + dataScope) |
|
||||
|
||||
### ai03 — 教学场景域
|
||||
|
||||
| 服务 | 设计重点 |
|
||||
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| teacher-bff | GraphQL schema(Query/Mutation,按场景域组织);DataLoader 批量去重策略;并行 gRPC 调用编排;聚合结果缓存 TTL 策略(5-30s 短缓存);教师角色差异化(教师 vs 教导主任 vs 教研组长 → 视口推导) |
|
||||
| core-edu | classes 模块黄金模板对齐;考试生命周期状态机(草稿 → 已发布 → 作答中 → 批改中 → 已出分 → 已归档);Outbox 事件定义(ExamPublished、HomeworkSubmitted、GradeRecorded);成绩计算公式与配置化;作业提交高并发优化(Redis 分布式锁 + 排队);排课/考勤数据模型 |
|
||||
|
||||
### ai04 — 学习 + 家长场景域 BFF
|
||||
|
||||
| 服务 | 设计重点 |
|
||||
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| student-bff | 学生端 GraphQL schema(与 teacher-bff 对比差异);DataLoader 复用 teacher-bff 模式;权限区分(学生只能看自己的数据,DataScope=SELF);考试/作业/成绩的学生视角 API |
|
||||
| parent-bff | 家长端 GraphQL schema;与 iam 的学生-家长关联查询;多子女账户切换设计;家长通知偏好配置 |
|
||||
|
||||
### ai05 — 内容 + 通知
|
||||
|
||||
| 服务 | 设计重点 |
|
||||
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| content | Neo4j 图模型(知识点 → 知识点前置依赖 → 教材关联);ES 索引 mapping 设计(题库全文检索 + 标签过滤);题库 CRUD 完整 API(含批量导入);与 ai 服务的 gRPC 接口(查询知识点/题库用于 AI 出题);教材/章节结构树 |
|
||||
| msg | 通知渠道抽象(站内信/邮件/短信,策略模式);ES 降级查询策略(DB 不可用时走 ES);Kafka 消费幂等设计(event_id 去重);与 push-gateway 的推送通道协议;通知模板管理;已读/未读状态管理 |
|
||||
|
||||
### ai06 — Python 数据 + AI
|
||||
|
||||
| 服务 | 设计重点 |
|
||||
| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| data-ana | ClickHouse 宽表设计(考试、作业、成绩、掌握度、出勤 5 张宽表);CDC 消费者架构(Debezium → Kafka → ClickHouse);学情分析 API(班级统计/个人趋势/预警阈值);掌握度计算算法(加权滑动平均);Dashboard 数据聚合 |
|
||||
| ai | LLM Provider 适配器模式(OpenAI/百川/本地模型);SSE 流式响应(题目逐字生成);出题 Prompt 模板管理;备课工作流(分析学情 → 推荐知识点 → 生成题目 → 教师审核 → 入库);用量计费/频率限制 |
|
||||
|
||||
### ai07 — 前端 4 端
|
||||
|
||||
| 服务 | 设计重点 |
|
||||
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| teacher-portal | 现有代码审计对齐黄金标准;Module Federation shell 暴露的共享组件 |
|
||||
| 全部 4 端 | Module Federation shell + remote 架构设计;路由骨架(4 端路由表对照);共享组件库(错误边界 ErrorBoundary、Loading 骨架屏、Empty 空态、权限控制组件);`usePermission().hasPermission()` 统一权限 Hook;API 请求层统一错误处理(toast 提示);4 端差异化对比表(导航菜单/路由/组件/数据 4 层差异) |
|
||||
|
||||
### coord — 协调 AI
|
||||
|
||||
| 职责 | 具体内容 |
|
||||
| ---------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| proto 契约维护 | 统一管理 `packages/shared-proto/`,跨 AI 的 proto 变更唯一入口 |
|
||||
| 设计文档交叉审查 | 审查 7 份模块架构设计文档的跨模块接口一致性(接口签名匹配、topic 不重复、端口不冲突、错误码前缀不重叠) |
|
||||
| 黄金模板维护 | 维护 classes 黄金模板标准,审查其他服务对齐情况 |
|
||||
| 架构文档同步 | 各 AI 产出设计文档后,同步更新 `004_architecture_impact_map.md` |
|
||||
| 共享包管理 | shared-ts / shared-go / shared-py 建立与维护 |
|
||||
| CI/CD | `.github/workflows/ci.yml` 覆盖全部 15 服务 |
|
||||
| 基础设施 | `infra/` K8s/Grafana/WAF/灾备(可由 SRE AI 协助) |
|
||||
|
||||
---
|
||||
|
||||
## 6. 阶段 1 交付物模板
|
||||
|
||||
每个 AI 阅读完 §4 的文档清单后,必须产出以下确认书:
|
||||
|
||||
```markdown
|
||||
## 模块理解确认书 — [模块名]
|
||||
|
||||
### 1. 我在架构中的位置
|
||||
|
||||
- 层级:Gateway / BFF / Service / Data / Frontend
|
||||
- 上游:谁调用我?
|
||||
- 下游:我调用谁?
|
||||
- 通信方式:HTTP / gRPC / Kafka / WebSocket / 直接 DB?
|
||||
|
||||
### 2. 我的限界上下文
|
||||
|
||||
- 我负责哪些聚合/实体?
|
||||
- 我的数据属于哪个业务领域(D1-D6 中的哪个)?
|
||||
- 我不负责什么(明确边界外的东西)?
|
||||
|
||||
### 3. 我与外部的契约
|
||||
|
||||
- 我消费哪些 proto message(从 shared-proto)?
|
||||
- 我暴露哪些 API 端点或 gRPC 方法或 Kafka 事件?
|
||||
- 错误码前缀是什么?
|
||||
|
||||
### 4. 我的技术栈
|
||||
|
||||
- 语言 / 框架 / ORM / 存储
|
||||
|
||||
### 5. 我的阶段归属
|
||||
|
||||
- 属于 P1-P6 哪个阶段?
|
||||
- 当前阶段目标是什么?
|
||||
- 依赖哪些上游阶段的产出?
|
||||
|
||||
### 6. 我需要对齐的黄金模板项(对照 classes 服务)
|
||||
|
||||
- [ ] 权限装饰器 @RequirePermission(全部 Controller 方法)
|
||||
- [ ] 错误码前缀统一
|
||||
- [ ] logger / metrics / tracer 三支柱
|
||||
- [ ] /healthz + /readyz 健康检查
|
||||
- [ ] 优雅关闭(SIGTERM)
|
||||
- [ ] 测试覆盖率 ≥ 80%
|
||||
- [ ] Dockerfile 多阶段构建
|
||||
- [ ] Zod 输入验证
|
||||
- [ ] GlobalErrorFilter 统一兜底
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 阶段 2 交付物模板
|
||||
|
||||
每个 AI 在阶段 1 确认书通过 coord 审核后,产出以下架构设计文档:
|
||||
|
||||
```markdown
|
||||
## 模块架构设计文档 — [模块名]
|
||||
|
||||
### 1. 模块内部分层图
|
||||
|
||||
[画图:Controller → Guard → Service → Repository → DB 调用链]
|
||||
[标注中间件、Guard、Filter 的拦截点]
|
||||
|
||||
### 2. 领域模型
|
||||
|
||||
- 聚合根:有哪些?
|
||||
- 实体/值对象:有哪些?
|
||||
- 聚合间如何通信?(同服务内直接调用 / 跨服务走事件)
|
||||
|
||||
### 3. 数据模型
|
||||
|
||||
- 有哪些表?(列出 schema,标注字段类型、约束)
|
||||
- 每张表的索引策略(主键、唯一索引、查询索引)
|
||||
- 读写分离策略(哪些走主库、哪些走读模型)
|
||||
|
||||
### 4. API 设计
|
||||
|
||||
| method | path | 权限 | 请求/响应结构 | 说明 |
|
||||
| ------ | ---- | ---------- | ------------- | ---- |
|
||||
| POST | /xxx | XXX_CREATE | { ... } | ... |
|
||||
|
||||
### 5. 事件设计(如适用)
|
||||
|
||||
- 我发布哪些领域事件?触发时机是什么?
|
||||
- 我消费哪些外部事件?消费后做什么?
|
||||
- 事件 Topic 名称(遵循 `edu.<domain>.<aggregate>.<action>` 格式)
|
||||
|
||||
### 6. 横切关注点对齐清单
|
||||
|
||||
- [ ] 权限装饰器(列出所有端点及对应权限常量)
|
||||
- [ ] 错误码清单(带前缀,每个错误码 → 触发条件 → HTTP 状态码)
|
||||
- [ ] Logger 初始化位置与配置(pino/zap/structlog)
|
||||
- [ ] Metrics 指标清单(指标名 / 类型 / 标签 / 描述)
|
||||
- [ ] Tracer 初始化位置(OTLP endpoint)
|
||||
- [ ] /healthz 检查逻辑
|
||||
- [ ] /readyz 检查逻辑(DB SELECT 1 / Redis PING / Kafka 连接)
|
||||
- [ ] 优雅关闭顺序(HTTP server → DB → Redis → Kafka)
|
||||
|
||||
### 7. 与其他模块的交互点(契约清单)
|
||||
|
||||
| 方向 | 对方服务 | 协议 | 接口/事件 | 用途 |
|
||||
| ------ | -------- | ----- | --------- | ---- |
|
||||
| 调用 | xxx | gRPC | XxxMethod | ... |
|
||||
| 被调用 | xxx | gRPC | YyyMethod | ... |
|
||||
| 发布 | — | Kafka | topic 名 | ... |
|
||||
| 消费 | — | Kafka | topic 名 | ... |
|
||||
|
||||
### 8. 风险与假设
|
||||
|
||||
- 我假设 [某服务] 提供了 [某接口],如果没提供我的 fallback 是什么?
|
||||
- 我的模块有哪些技术风险?(性能瓶颈、数据一致性、外部依赖)
|
||||
- 有哪些未决的设计决策需要协调 AI 仲裁?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 交叉审查规则
|
||||
|
||||
coord 收到全部 7 份设计文档后,执行以下审查:
|
||||
|
||||
### 8.1 接口一致性检查
|
||||
|
||||
```markdown
|
||||
| 服务 A 说 | 服务 B 说 | 是否匹配 |
|
||||
| --------------------------------------------------------- | ---------------------------------------------- | --------- |
|
||||
| ai02 iam: 暴露 getUserInfo(userId) | ai03 teacher-bff: 调用 iam.getUserInfo(userId) | ✅ |
|
||||
| ai03 core-edu: 调用 content.getKnowledgePoints(subjectId) | ai05 content: ??? | ⚠️ 待确认 |
|
||||
```
|
||||
|
||||
### 8.2 全局冲突检查
|
||||
|
||||
| 检查项 | 检查方式 |
|
||||
| -------------------- | ---------------------------------------------------------------------- |
|
||||
| 端口不冲突 | 对照 [full-stack-runbook](../standards/full-stack-runbook.md) 端口矩阵 |
|
||||
| Topic 不重复 | 汇总全部 AI 的 §5 事件设计,去重检查 |
|
||||
| 错误码前缀不重叠 | 汇总全部 AI 的 §6 错误码清单,前缀唯一性检查 |
|
||||
| Proto message 不遗漏 | 检查全部"跨模块交互点"是否在 proto 中有对应定义 |
|
||||
|
||||
### 8.3 黄金模板对齐检查
|
||||
|
||||
| 检查项 | 全部 TS NestJS 服务 |
|
||||
| ----------------------- | ---------------------- |
|
||||
| @RequirePermission 覆盖 | 每个 Controller 方法 |
|
||||
| 错误码前缀 | 用服务名大写前缀 |
|
||||
| /healthz + /readyz | 存在且逻辑正确 |
|
||||
| Zod 输入验证 | Controller 层解析 body |
|
||||
| GlobalErrorFilter | 注册到 AppModule |
|
||||
| Dockerfile 多阶段 | builder + runtime |
|
||||
|
||||
---
|
||||
|
||||
## 9. 协作规则
|
||||
|
||||
### 9.1 单仓库并行开发
|
||||
|
||||
当前阶段采用**单仓库直接 push main**模式,不经过 PR:
|
||||
|
||||
- 每个 AI 只能修改自己负责的目录(见 §3.2)
|
||||
- `packages/shared-proto/` 仅 coord 修改,其他 AI 只读
|
||||
- `docs/`、`.trae/`、`infra/`、`.github/` 仅 coord 修改
|
||||
|
||||
### 9.2 唯一冲突文件处理
|
||||
|
||||
`pnpm-lock.yaml` 是唯一可能多 AI 同时修改的文件,冲突时:
|
||||
|
||||
```bash
|
||||
git pull origin main --rebase
|
||||
# 冲突时:
|
||||
git checkout --theirs pnpm-lock.yaml # 取远程版本
|
||||
pnpm install # 重新生成
|
||||
git add pnpm-lock.yaml
|
||||
git rebase --continue
|
||||
```
|
||||
|
||||
### 9.3 提交规范
|
||||
|
||||
```bash
|
||||
# 每个 AI 在自己的服务目录内工作
|
||||
git add services/<service>/...
|
||||
git commit -m "docs(<service>): 模块架构设计文档"
|
||||
|
||||
# 或
|
||||
git commit -m "docs(<service>): 阶段1理解确认书"
|
||||
```
|
||||
|
||||
### 9.4 proto 变更流程
|
||||
|
||||
任何 AI 需要新增/修改 proto:
|
||||
|
||||
1. 在共享协调渠道声明需求(格式:`# proto-change: <描述>`)
|
||||
2. coord 统一修改 `packages/shared-proto/`
|
||||
3. coord 通知受影响 AI 更新设计文档
|
||||
|
||||
---
|
||||
|
||||
## 10. 审计模板(阶段 1 自检用)
|
||||
|
||||
每个 AI 审计自己负责的已实现服务时,填写下表:
|
||||
|
||||
```markdown
|
||||
## 服务审计表 — [AI标识]
|
||||
|
||||
| 服务 | 权限装饰器 | 错误码前缀 | logger | metrics | tracer | /healthz | /readyz | 优雅关闭 | 测试覆盖率 | Dockerfile |
|
||||
| ---- | ---------- | ---------- | -------- | -------- | -------- | -------- | -------- | -------- | ---------- | ---------- |
|
||||
| xxx | ✅/❌/⚠️ | ✅/❌/⚠️ | ✅/❌/⚠️ | ✅/❌/⚠️ | ✅/❌/⚠️ | ✅/❌/⚠️ | ✅/❌/⚠️ | ✅/❌/⚠️ | XX% | ✅/❌/⚠️ |
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 相关文档
|
||||
|
||||
- [多 AI 协作指南](../standards/multi-ai-collaboration.md) — 日常开发协作流程
|
||||
- [004 架构影响地图](./004_architecture_impact_map.md) — 全局架构与依赖
|
||||
- [项目规则](../../.trae/rules/project_rules.md) — 强制约束
|
||||
- [编码规范](../standards/coding-standards.md) — 多语言编码标准
|
||||
- [待开发功能路线图](./roadmap/pending-features.md) — 六阶段目标
|
||||
216
docs/architecture/ai03-phase1-understanding.md
Normal file
216
docs/architecture/ai03-phase1-understanding.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# ai03 阶段 1 交付物:模块理解确认书
|
||||
|
||||
> AI 标识:ai03
|
||||
> 负责模块:teacher-bff(P2)、core-edu(P3)
|
||||
> 阶段:架构设计外包 · 阶段 1(全局理解)
|
||||
> 日期:2026-07-09
|
||||
> 关联文档:[ai-allocation.md](./ai-allocation.md)、[004 架构影响地图](./004_architecture_impact_map.md)、[pending-features.md](./roadmap/pending-features.md)
|
||||
|
||||
---
|
||||
|
||||
## 模块理解确认书 — teacher-bff
|
||||
|
||||
### 1. 我在架构中的位置
|
||||
|
||||
- **层级**:BFF 聚合层(L4)
|
||||
- **上游**:api-gateway(Go/Gin)通过 HTTP 转发请求,注入 `x-user-id` / `x-user-roles` 头
|
||||
- **下游**:iam(3002)、classes(3001)、core-edu(3004);P3 后扩展 content、data-ana、ai
|
||||
- **通信方式**:
|
||||
- 当前:REST `fetch`(同步)
|
||||
- 目标态(004 §4.1 / pending-features P2):**gRPC** 调下游业务服务 + **GraphQL Yoga** 对前端暴露 + DataLoader 防 N+1
|
||||
- **端口**:3003(见 [teacher-bff env.ts](../../services/teacher-bff/src/config/env.ts))
|
||||
|
||||
### 2. 我的限界上下文
|
||||
|
||||
- **聚合职责**:教学场景域(教师 / 教导主任 / 教研组长 共用)的数据聚合、裁剪、协议转换
|
||||
- **业务领域**:跨 D2 教学组织 + D3 教学核心 + D1 身份认证(只读拉取视口/权限)
|
||||
- **我不负责**:
|
||||
- 不持有业务状态(无 DB 写入,无 Outbox)
|
||||
- 不做权限决策(依赖 Gateway JWT 校验 + 下游服务 `@RequirePermission`)
|
||||
- 不直接访问任何业务服务数据库
|
||||
- **复用策略**(004 §5.4):教导主任 / 教研组长复用 Teacher BFF,通过视口差异化(L1 导航扩展管理菜单,L4 DataScope 扩大到年级)
|
||||
|
||||
### 3. 我与外部的契约
|
||||
|
||||
- **消费的 proto message**:
|
||||
- `iam.v1.IamService`:GetUserInfo / getEffectivePermissions(视口 + DataScope)
|
||||
- `classes.v1.ClassService`:ListClasses / GetClass
|
||||
- `core_edu.v1.ExamService / HomeworkService / GradeService`:全部 RPC
|
||||
- **暴露的 API**(当前 REST,目标 GraphQL):
|
||||
- `GET /teacher/dashboard` — 聚合 IAM 用户信息 + classes 列表
|
||||
- `GET /teacher/viewports` — 拉取 IAM 视口配置(L1 导航)
|
||||
- `GET /teacher/classes/:classId/exams` — 聚合 core-edu 考试列表
|
||||
- `GET /teacher/classes/:classId/homework` — 聚合 core-edu 作业列表
|
||||
- `GET /teacher/exams/:examId/grades` — 聚合 core-edu 成绩列表
|
||||
- **错误码前缀**:BFF 自身错误用 `BAD_GATEWAY`(下游不可达);业务错误透传下游 `CORE_EDU_*` / `IAM_*` / `CLASSES_*`
|
||||
- **缓存**:聚合结果 Redis 短缓存 5-30s(004 §6.3,当前未实现)
|
||||
|
||||
### 4. 我的技术栈
|
||||
|
||||
- 语言:TypeScript 5.5+(ESM 模式,相对 import 带 `.js` 后缀)
|
||||
- 框架:NestJS 10
|
||||
- 下游通信:当前 `fetch`(REST)→ 目标 `@grpc/grpc-js` + `@bufbuild/protobuf`
|
||||
- 对前端:目标 GraphQL Yoga + DataLoader
|
||||
- 缓存:Redis(待引入)
|
||||
- 可观测:pino logger + prom-client metrics + OpenTelemetry tracer(已具备 [tracer.ts](../../services/teacher-bff/src/shared/observability/tracer.ts))
|
||||
|
||||
### 5. 我的阶段归属
|
||||
|
||||
- **P2 身份**:教师登录 → 获取 JWT → 访问 teacher-portal → 侧边栏按 viewports.L1 渲染 → 空白 Dashboard
|
||||
- **P3 扩展**:考试/作业/成绩的 GraphQL 查询与 mutation
|
||||
- **依赖上游**:P1 黄金模板 classes、P2 iam(getEffectivePermissions + 视口)
|
||||
|
||||
### 6. 我需要对齐的黄金模板项(对照 classes 服务)
|
||||
|
||||
- [ ] 权限装饰器 `@RequirePermission`(**BFF 不做权限决策**,当前无;目标态:BFF 不加 Guard,仅校验 `x-user-id` 存在)
|
||||
- [x] 错误处理:[GlobalErrorFilter](../../services/teacher-bff/src/shared/errors/global-error.filter.ts) + ApplicationError 层次
|
||||
- [x] logger / metrics / tracer 三支柱(已具备)
|
||||
- [x] `/healthz` 健康检查(HealthModule 已注册)
|
||||
- [ ] `/readyz`(当前 HealthController 仅 liveness,无下游就绪探针)
|
||||
- [x] 优雅关闭 SIGTERM(main.ts 已处理)
|
||||
- [ ] 测试覆盖率 ≥ 80%(**当前 0%**,无测试文件)
|
||||
- [ ] Dockerfile 多阶段构建(需核对)
|
||||
- [ ] Zod 输入验证(当前 Controller 直接透传 unknown,**未做 Zod 校验**)
|
||||
- [x] GlobalErrorFilter 统一兜底
|
||||
|
||||
---
|
||||
|
||||
## 模块理解确认书 — core-edu
|
||||
|
||||
### 1. 我在架构中的位置
|
||||
|
||||
- **层级**:业务微服务层(L5)
|
||||
- **上游**:teacher-bff(聚合层)、api-gateway(直接路由 `/api/v1/exams` 等)
|
||||
- **下游**:MySQL(独占库)、Kafka(事件发布)、Redis(待引入,高并发提交锁)
|
||||
- **通信方式**:
|
||||
- 入口:当前 REST(`/exams`、`/homework`、`/grades`)
|
||||
- 目标态(proto 注释):P3 起转 gRPC(`core_edu.v1.ExamService/HomeworkService/GradeService` 已定义)
|
||||
- 出口:Kafka 事件(Outbox 模式发布)
|
||||
- **端口**:3004(见 [core-edu env.ts](../../services/core-edu/src/config/env.ts))
|
||||
|
||||
### 2. 我的限界上下文
|
||||
|
||||
- **聚合职责**:跨 **D2 教学组织**(classes 模块,待合并)+ **D3 教学核心**(exams / homework / grades)
|
||||
- **聚合根**:Exam、Homework、Grade、Class(待合并)
|
||||
- **我不负责**:
|
||||
- 不负责题库内容(→ content 服务)
|
||||
- 不负责学情分析(→ data-ana 服务,消费 core-edu 事件)
|
||||
- 不负责通知投递(→ msg 服务,消费 core-edu 事件)
|
||||
- **数据自治**:独占 `core_edu` 数据库,表前缀 `core_edu_*`
|
||||
|
||||
### 3. 我与外部的契约
|
||||
|
||||
- **暴露的 gRPC 契约**([core_edu.proto](../../packages/shared-proto/proto/core_edu.proto),已定义待实现):
|
||||
- `ExamService`:CreateExam / GetExam / ListExamsByClass / UpdateExam / DeleteExam
|
||||
- `HomeworkService`:AssignHomework / GetHomework / ListHomeworkByClass / SubmitHomework
|
||||
- `GradeService`:RecordGrade / GetGrade / ListGradesByStudent/Exam/Homework
|
||||
- **发布的领域事件**([events.proto](../../packages/shared-proto/proto/events.proto) + [outbox.publisher.ts TOPIC_MAP](../../services/core-edu/src/shared/outbox/outbox.publisher.ts)):
|
||||
|
||||
| 事件 | Topic | 触发时机 | 消费者 |
|
||||
| ------------------ | ------------------- | --------------------- | ------------- |
|
||||
| exam.created | edu.exam.events | CreateExam 事务内 | msg、data-ana |
|
||||
| exam.updated | edu.exam.events | UpdateExam | msg |
|
||||
| exam.deleted | edu.exam.events | DeleteExam | data-ana |
|
||||
| homework.assigned | edu.homework.events | AssignHomework | msg、data-ana |
|
||||
| homework.submitted | edu.homework.events | SubmitHomework | data-ana、msg |
|
||||
| homework.graded | edu.homework.events | **未实现**(P3 待补) | msg、data-ana |
|
||||
| grade.recorded | edu.grade.events | RecordGrade | data-ana、msg |
|
||||
| grade.updated | edu.grade.events | **未实现**(P3 待补) | data-ana |
|
||||
| class.transferred | edu.class.events | classes 合并后 | data-ana |
|
||||
|
||||
- **消费的事件**:`edu.identity.user.created`(IAM,初始化教师默认班级关联,**当前未消费**,P3 待补)
|
||||
- **错误码前缀**:`CORE_EDU_*`(见 [application-error.ts CoreEduErrorCode](../../services/core-edu/src/shared/errors/application-error.ts))
|
||||
|
||||
### 4. 我的技术栈
|
||||
|
||||
- 语言:TypeScript 5.5+(ESM 模式)
|
||||
- 框架:NestJS 10
|
||||
- ORM:Drizzle ORM(mysql2 driver,直接 `db` 导出,**与 classes 的 `getDb()` 不一致**)
|
||||
- 存储:MySQL 8(独占库)、Redis(待引入)、Kafka(kafkajs,idempotent + transactionalId)
|
||||
- 可观测:pino + prom-client + OTel(已具备)
|
||||
|
||||
### 5. 我的阶段归属
|
||||
|
||||
- **P3 核心教学**:考试全生命周期 + Outbox + Kafka 事件落地
|
||||
- **退出标准**:教师创建考试 → 发布 → 学生作答 → 教师批改 → 事件到 Kafka → 成绩统计更新 → 全链路可观测
|
||||
- **依赖上游**:P1 classes 黄金模板、P2 iam(用户身份 + 权限)
|
||||
|
||||
### 6. 我需要对齐的黄金模板项(对照 classes 服务)
|
||||
|
||||
- [x] 权限装饰器 `@RequirePermission`(exams.controller 全覆盖;需核对 homework/grades controller)
|
||||
- [x] 错误码前缀 `CORE_EDU_*`(已用 [CoreEduErrorCode 枚举](../../services/core-edu/src/shared/errors/application-error.ts))
|
||||
- [x] logger / metrics / tracer 三支柱
|
||||
- [x] `/healthz` 健康检查
|
||||
- [ ] `/readyz`(需补 DB SELECT 1 / Kafka 连接探针)
|
||||
- [x] 优雅关闭 SIGTERM(main.ts 已处理 outboxPublisher.stop + disconnectKafka)
|
||||
- [ ] 测试覆盖率 ≥ 80%(**当前 0%**,无测试文件)
|
||||
- [ ] Dockerfile 多阶段构建(需核对)
|
||||
- [ ] Zod 输入验证(**当前 Controller 直接接收 body,未 Zod 校验**;classes 用 zod schema)
|
||||
- [x] GlobalErrorFilter 统一兜底
|
||||
- [x] Outbox 模式(事务内写业务表 + outbox 表,独立 publisher 投递)
|
||||
|
||||
---
|
||||
|
||||
## §10 服务审计表 — ai03
|
||||
|
||||
> 对照 [黄金模板 classes 服务](../../services/classes/src/),审计已实现的两服务。状态:✅ 达标 / ⚠️ 部分 / ❌ 缺失
|
||||
|
||||
| 服务 | 权限装饰器 | 错误码前缀 | logger | metrics | tracer | /healthz | /readyz | 优雅关闭 | 测试覆盖率 | Dockerfile |
|
||||
| ----------- | --------------------------------------- | --------------------------------- | ------- | -------------- | ------- | -------- | ------- | -------- | ---------- | ---------- |
|
||||
| teacher-bff | ⚠️ 无(BFF 不做权限决策,依赖 Gateway) | ⚠️ 用 `BAD_GATEWAY`(无自有前缀) | ✅ pino | ✅ prom-client | ✅ OTel | ✅ | ❌ | ✅ | 0% ❌ | 待核对 |
|
||||
| core-edu | ✅ `@RequirePermission(EXAM_*)` 全覆盖 | ✅ `CORE_EDU_*` | ✅ pino | ✅ prom-client | ✅ OTel | ✅ | ❌ | ✅ | 0% ❌ | 待核对 |
|
||||
|
||||
### 审计发现的关键差距(P3 阶段 2 设计需解决)
|
||||
|
||||
**teacher-bff**:
|
||||
|
||||
1. ❌ 通信方式:当前 REST `fetch`,目标 gRPC + GraphQL(P2 退出标准要求 GraphQL Yoga + DataLoader)
|
||||
2. ❌ 无 Redis 聚合缓存(004 §6.3 要求 5-30s 短缓存)
|
||||
3. ❌ 无 DataLoader(防 N+1,pending-features P2 明确要求)
|
||||
4. ❌ 无 `/readyz` 下游就绪探针
|
||||
5. ⚠️ env 配置用 `IamServiceUrl`/`ClassesServiceUrl`(REST URL),转 gRPC 后需改为 gRPC target
|
||||
6. ⚠️ 无 Zod 输入验证
|
||||
7. ⚠️ 无测试
|
||||
|
||||
**core-edu**:
|
||||
|
||||
1. ❌ 考试生命周期状态机缺失(当前仅 `draft` 初值,无 `published → in_progress → grading → graded → archived` 转换与校验)
|
||||
2. ❌ 作业状态机不完整(仅 `assigned → submitted`,缺 `graded`;pending-features 要求 `HomeworkGraded` 事件)
|
||||
3. ❌ 成绩录入无业务校验(不校验 exam/homework 是否存在、score 是否在 totalScore 范围内、是否重复录入)
|
||||
4. ❌ 作业提交高并发优化缺失(004 §9.2 要求 Redis 分布式锁 + 排队)
|
||||
5. ❌ 无 `grade.updated` / `homework.graded` 事件触发点(proto 已定义,service 未实现)
|
||||
6. ❌ 未消费 IAM `user.created` 事件(初始化教师默认关联)
|
||||
7. ⚠️ Drizzle `db` 直接导出 vs classes 的 `getDb()` 函数式 — **不一致**,建议统一为 `getDb()`
|
||||
8. ⚠️ [kafka.ts](../../services/core-edu/src/config/kafka.ts) 用 `console.log`/`console.warn`,应改用结构化 logger
|
||||
9. ⚠️ classes 模块在 core-edu 仅有 `classes.module.ts` 占位,**P3 待合并**(classes 服务代码迁入 + 删除独立 services/classes)
|
||||
10. ⚠️ 入口仍为 REST,proto gRPC 契约已定义但未接入 `@grpc/grpc-js` + buf generate 代码
|
||||
11. ❌ 无 Zod 输入验证(Controller 直接接收 `body: CreateExamInput`,未走 zod schema)
|
||||
12. ❌ 无测试
|
||||
|
||||
### 跨模块契约对齐待确认项(提请 coord 交叉审查)
|
||||
|
||||
| 待确认项 | 我方期望 | 对方模块 | 状态 |
|
||||
| ---------------------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| iam `getEffectivePermissions(userId)` 返回结构 | `{permissions, viewports, dataScope}` | iam(ai02) | ⚠️ 当前 teacher-bff 调 `/iam/viewports` 与 `/iam/me`,未定义此聚合 API 的 proto |
|
||||
| iam `user.created` 事件 topic | `edu.identity.user.created` | iam(ai02) | ⚠️ 004 §7.2 定义,但 core-edu 未消费,需确认 iam 是否发布 |
|
||||
| core-edu 端口 3004 | 不冲突 | 全局端口矩阵 | 待 coord 核对 |
|
||||
| Kafka topic 命名 | `edu.exam.events` / `edu.homework.events` / `edu.grade.events` / `edu.class.events` | 004 §7.2 用 `edu.teaching.*` 前缀 | ⚠️ **不一致**:004 文档用 `edu.teaching.exam.published`,代码用 `edu.exam.events`,需 coord 仲裁统一 |
|
||||
| data-ana 消费 core-edu 事件 | 消费 `exam.created` / `homework.submitted` / `grade.recorded` | data-ana(ai06) | 待 ai06 确认消费契约 |
|
||||
| msg 消费 core-edu 事件 | 消费 `exam.created` / `homework.assigned` / `grade.recorded` 触发通知 | msg(ai05) | 待 ai05 确认消费契约 |
|
||||
|
||||
---
|
||||
|
||||
## 下一步(阶段 2 入口)
|
||||
|
||||
待 coord 审核本确认书通过后,ai03 进入阶段 2,按 [ai-allocation.md §5 ai03 设计重点](./ai-allocation.md#ai03--教学场景域) 产出两份模块架构设计文档:
|
||||
|
||||
1. **teacher-bff 模块架构设计**:GraphQL schema(Query/Mutation 按场景域组织)、DataLoader 批量去重、并行 gRPC 编排、聚合缓存 TTL、教师角色差异化视口推导
|
||||
2. **core-edu 模块架构设计**:classes 黄金模板对齐、考试生命周期状态机、Outbox 事件定义、成绩计算配置化、作业提交高并发(Redis 锁 + 排队)、排课/考勤数据模型
|
||||
|
||||
阶段 2 设计需先解决上述 12 项差距与 6 项跨模块契约对齐。
|
||||
|
||||
---
|
||||
|
||||
**AI Agent**: ai03 (teacher-bff + core-edu)
|
||||
**Coordinator**: coord-ai
|
||||
**Branch**: 单仓库并行模式(直接 push main)
|
||||
269
docs/architecture/ai05-phase1-understanding.md
Normal file
269
docs/architecture/ai05-phase1-understanding.md
Normal file
@@ -0,0 +1,269 @@
|
||||
# ai05 阶段 1 交付物:模块理解确认书
|
||||
|
||||
> AI 标识:ai05
|
||||
> 负责模块:content(P4)、msg(P5)
|
||||
> 阶段:架构设计外包 · 阶段 1(全局理解)
|
||||
> 日期:2026-07-09
|
||||
> 关联文档:[ai-allocation.md](./ai-allocation.md)、[004 架构影响地图](./004_architecture_impact_map.md)、[pending-features.md](./roadmap/pending-features.md)、[known-issues.md](../troubleshooting/known-issues.md)
|
||||
|
||||
---
|
||||
|
||||
## 模块理解确认书 — content
|
||||
|
||||
### 1. 我在架构中的位置
|
||||
|
||||
- **层级**:业务微服务层(L5),DDD 限界上下文
|
||||
- **业务领域**:D4 内容资源领域(004 §1.1b)
|
||||
- **上游**(谁调用我):
|
||||
- teacher-bff(3003):教学场景聚合,教师查教材/知识点/题库
|
||||
- student-bff(学习场景,P3 起):学生查学习路径、知识点前置
|
||||
- ai(Python,P5):gRPC 查询知识点/题库用于 AI 出题(004 §4.1 `AI -.gRPC.-> Content`,§9.3 AI 辅助出题流程)
|
||||
- **下游**(我调用谁):
|
||||
- MySQL(写模型主库,独占)
|
||||
- Neo4j(知识图谱,前置依赖图)
|
||||
- Elasticsearch(题库全文检索,P4 后续补充,当前未实现)
|
||||
- **通信方式**:
|
||||
- 当前:HTTP REST(Controller,无 gRPC controller)
|
||||
- 目标态(004 §4.1 / pending-features P4):gRPC 暴露 `TextbookService` + `KnowledgeGraphService`
|
||||
- Kafka:消费 core-edu 教学内容变更通知(004 §4.1 `CoreEdu -.事件.-> Content`);发布 `edu.content.question.published`(004 §7.2)
|
||||
- **端口**:3005(见 [content env.ts](../../services/content/src/config/env.ts))
|
||||
|
||||
### 2. 我的限界上下文
|
||||
|
||||
- **聚合职责**:管理 Textbook(教材)、Chapter(章节)、KnowledgePoint(知识点)、Question(题库)四个聚合
|
||||
- **我的数据属于**:D4 内容资源领域
|
||||
- **我不负责**:
|
||||
- 不负责学情分析(D6,由 data-ana 承载)
|
||||
- 不负责考试/作业/成绩(D3,由 core-edu 承载)
|
||||
- 不负责通知分发(D5,由 msg 承载)
|
||||
- 不直接访问 core-edu / iam / msg 的数据库
|
||||
- **现有骨架领域模块**(4 个,见 [content/src](../../services/content/src)):
|
||||
- `textbooks/`:教材 CRUD(5 端点)
|
||||
- `chapters/`:章节 CRUD(5 端点,按 textbook 查询)
|
||||
- `knowledge-points/`:知识点 CRUD + Neo4j 知识图谱(7 端点,含前置链路查询/添加)
|
||||
- `questions/`:题库 CRUD(5 端点,4 种题型校验)
|
||||
|
||||
### 3. 我与外部的契约
|
||||
|
||||
- **消费的 proto message**(从 shared-proto):
|
||||
- 无直接消费其他服务 proto;通过 Kafka 事件接收 core-edu 教学内容变更(事件契约见 `events.proto`)
|
||||
- **暴露的契约**(见 [content.proto](../../packages/shared-proto/proto/content.proto),包名 `next_edu_cloud.content.v1`):
|
||||
- `TextbookService`:CreateTextbook / GetTextbook / ListTextbooks
|
||||
- `KnowledgeGraphService`:GetPrerequisites / GetLearningPath
|
||||
- **当前实现均为 REST,gRPC controller 未实现**(proto 已定义待迁移)
|
||||
- **事件契约**:
|
||||
- 发布:`edu.content.question.published`(题库新增/发布,消费者 AI、ES,见 004 §7.2)
|
||||
- 消费:core-edu 教学内容变更事件(004 §4.1,具体 topic 待 core-edu ai03 设计确认)
|
||||
- **错误码前缀**:`CONTENT_*`(CONTENT_VALIDATION_ERROR / NOT_FOUND / PERMISSION_DENIED / CONFLICT / BUSINESS_ERROR / DATABASE_ERROR / INTERNAL_ERROR,见 [application-error.ts](../../services/content/src/shared/errors/application-error.ts))
|
||||
- **权限点**(16 个,见 [permission.guard.ts](../../services/content/src/middleware/permission.guard.ts)):
|
||||
- `CONTENT_TEXTBOOK_{CREATE,READ,UPDATE,DELETE}`
|
||||
- `CONTENT_CHAPTER_{CREATE,READ,UPDATE,DELETE}`
|
||||
- `CONTENT_QUESTION_{CREATE,READ,UPDATE,DELETE}`
|
||||
- `CONTENT_KNOWLEDGE_POINT_{CREATE,READ,UPDATE,DELETE}`
|
||||
|
||||
### 4. 我的技术栈
|
||||
|
||||
- 语言:TypeScript 5.6(ESM 模式,相对 import 带 `.js` 后缀)
|
||||
- 框架:NestJS 10
|
||||
- ORM:Drizzle ORM 0.31 + mysql2 3.11
|
||||
- 知识图谱:neo4j-driver 5.23(PREREQUISITE_OF 关系,Cypher 查询深度 1..5)
|
||||
- 全文检索:Elasticsearch(**待引入**,env.ts 预留 ES_URL 但 package.json 未装 @elastic/elasticsearch)
|
||||
- 可观测:pino logger + prom-client metrics + OpenTelemetry tracer(三支柱已具备)
|
||||
- 消息总线:Kafka(**待引入**,pending-features P4 未明确要求 content 发事件,但 004 §7.2 列了 `edu.content.question.published`)
|
||||
|
||||
### 5. 我的阶段归属
|
||||
|
||||
- **P4 内容分析阶段**(pending-features §P4):
|
||||
- 教材/章节/知识点 CRUD(仅 CRUD,不实现检索)+ 知识图谱查询(Neo4j)+ 题库 CRUD(不实现检索)
|
||||
- MySQL schema:textbooks / chapters / knowledge_points / questions
|
||||
- Neo4j 数据:知识点前置依赖图(从 MySQL 同步)
|
||||
- CDC 链路:Debezium 监听 MySQL binlog → Kafka → data-ana 消费写 ClickHouse
|
||||
- **退出标准**:教师查看知识图谱前置依赖(Neo4j 秒级返回)→ 学生查看学情诊断(ClickHouse 宽表 5s 内返回)→ CDC 链路延迟 < 5s
|
||||
- **依赖上游**:P1 黄金模板 classes(横切关注点对齐)、P3 core-edu(教学内容变更事件,待 ai03 设计确认 topic)
|
||||
|
||||
### 6. 我需要对齐的黄金模板项(对照 classes 服务)
|
||||
|
||||
- [x] 权限装饰器 `@RequirePermission`(16 个 CONTENT_* 权限点,全部 Controller 方法已覆盖)
|
||||
- [x] 错误码前缀统一(`CONTENT_*`)
|
||||
- [x] logger / metrics / tracer 三支柱(已具备)
|
||||
- [x] `/healthz` 健康检查(HealthModule 已注册)
|
||||
- [⚠️] `/readyz`(**仅检查 DB `SELECT 1`,未检查 Neo4j 连通性**,Neo4j 故障时仍返回 ok)
|
||||
- [x] 优雅关闭 SIGTERM(main.ts 已处理:closeNeo4j → closeDb → shutdownTracer)
|
||||
- [ ] 测试覆盖率 ≥ 80%(**当前 0%**,无测试文件)
|
||||
- [x] Dockerfile 多阶段构建(已具备)
|
||||
- [⚠️] Zod 输入验证(questions 用 service 层手动 if 校验抛 ValidationError,**非 Controller 层 schema.parse**)
|
||||
- [x] GlobalErrorFilter 统一兜底
|
||||
|
||||
### 7. 现有骨架差距与待决策(提请 coord 仲裁)
|
||||
|
||||
| # | 差距 | 影响 | 提请决策 |
|
||||
| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| C1 | **ES 完全未实现**:env.ts 预留 ES_URL 但无 @elastic/elasticsearch 依赖、无 config/elasticsearch.ts、无 service 调用 | P4 退出标准要求题库检索,pending-features 注明"P4 仅 CRUD,P5 引入 ES" | 确认 content 的 ES 检索归属 P4 还是 P5(004 §2.2 列 ES 使用者为 Content、AI) |
|
||||
| C2 | **无 Outbox / Kafka**:骨架无 shared/outbox/,无 Kafka producer/consumer | 004 §7.2 列 `edu.content.question.published` 事件需发布 | 确认 content 是否需 Outbox(iam 无 Outbox,core-edu 有) |
|
||||
| C3 | **README 与实现脱节**:README 声称 `TextbooksService.createKnowledgeGraph` 和 `getPrerequisites`,源码中 createKnowledgeGraph 不存在,getPrerequisites 在 KnowledgePointsService | 文档误导 | 阶段 2 设计需同步修正 README |
|
||||
| C4 | **gRPC 未实现**:proto 定义了 TextbookService/KnowledgeGraphService,但无 gRPC controller | pending-features P4 未强制 gRPC,004 §4.1 目标态 gRPC | 确认 P4 是否启用 gRPC(ai03 提请统一决策) |
|
||||
| C5 | **schema 定义分散**:textbooks.schema.ts 定义 3 张表(textbooks/chapters/knowledgePoints),chapters/knowledge-points schema 仅 re-export;questions.schema.ts 独立 | 维护成本 | 阶段 2 设计统一 schema 归属 |
|
||||
| C6 | **DB 连接模式与 iam 不一致**:content 用模块级 `const db`,iam 用 `getDb()` 函数式懒加载 | 测试 mock 困难 | coord 已在 known-issues 记录"db 常量导出对齐黄金模板",需确认统一方向 |
|
||||
| C7 | **表时间戳不统一**:textbooks/questions 有 created_at+updated_at,chapters/knowledge_points 无时间戳 | 审计追踪缺失 | 阶段 2 设计补齐 |
|
||||
| C8 | **无外键约束**:questions.knowledge_point_id → knowledge_points.id 等无 Drizzle 外键 | 引用完整性靠应用层 | 阶段 2 设计评估是否加外键 |
|
||||
| C9 | **addPrerequisite 降级策略不一致**:safeCreateNode 非阻塞(Neo4j 失败仅 warn),addPrerequisite 在 Neo4j 不可用时抛 InternalError | 行为不一致 | 阶段 2 设计统一降级策略 |
|
||||
| C10 | **proto 包名**:实际 `next_edu_cloud.content.v1`,project_rules §5 规定 `edu.content.v1` | 命名规范不一致 | **coord 仲裁**(ai03 已提请) |
|
||||
|
||||
---
|
||||
|
||||
## 模块理解确认书 — msg
|
||||
|
||||
### 1. 我在架构中的位置
|
||||
|
||||
- **层级**:业务微服务层(L5),DDD 限界上下文
|
||||
- **业务领域**:D5 沟通通知领域(004 §1.1b)
|
||||
- **上游**(谁调用我):
|
||||
- teacher-bff / student-bff / parent-bff:聚合层调 msg 查询用户通知列表、发送通知
|
||||
- api-gateway:REST 转发通知请求
|
||||
- Kafka:消费 core-edu / iam 事件触发通知(004 §4.1 `CoreEdu -.事件.-> Msg`、`IAM -.事件.-> Msg`)
|
||||
- **下游**(我调用谁):
|
||||
- MySQL(写模型主库,独占)
|
||||
- Elasticsearch(全文检索,已实现 safeSearch)
|
||||
- push-gateway(gRPC 推送通道,004 §4.1 `PushGW → Msg`,当前用 fetch POST /internal/push 降级)
|
||||
- **通信方式**:
|
||||
- 当前:HTTP REST(Controller,无 gRPC controller)
|
||||
- 目标态(004 §4.1 / pending-features P5):gRPC 暴露 `NotificationService`
|
||||
- Kafka:消费 core-edu(ExamPublished/HomeworkGraded/GradeRecorded)、iam(UserRegistered)事件(004 §7.3)
|
||||
- **端口**:3007(见 [msg env.ts](../../services/msg/src/config/env.ts))
|
||||
|
||||
### 2. 我的限界上下文
|
||||
|
||||
- **聚合职责**:管理 Notification(通知)聚合 + NotificationPreference(用户通知偏好)
|
||||
- **我的数据属于**:D5 沟通通知领域
|
||||
- **我不负责**:
|
||||
- 不负责 WebSocket 长连接管理(由 push-gateway 承载)
|
||||
- 不负责业务数据变更(仅消费事件触发通知)
|
||||
- 不直接访问 core-edu / iam 的数据库
|
||||
- **现有骨架领域模块**(1 个,见 [msg/src](../../services/msg/src)):
|
||||
- `notifications/`:通知 CRUD + ES 全文检索 + Push Gateway 推送 + 用户偏好(6 端点)
|
||||
|
||||
### 3. 我与外部的契约
|
||||
|
||||
- **消费的 proto message**(从 shared-proto):
|
||||
- 消费 `events.proto` 的事件 message(ClassEvent/ExamEvent/HomeworkEvent/GradeEvent)
|
||||
- **events.proto 当前无 NotificationEvent**,若 msg 发事件需补充
|
||||
- **暴露的契约**(见 [msg.proto](../../packages/shared-proto/proto/msg.proto),包名 `next_edu_cloud.msg.v1`):
|
||||
- `NotificationService`:SendNotification / ListNotifications / MarkAsRead / SearchNotifications
|
||||
- **当前实现均为 REST,gRPC controller 未实现**
|
||||
- **事件契约**:
|
||||
- 消费(004 §7.2 / §7.3):
|
||||
- `edu.identity.user.created` / `edu.identity.user.updated`(IAM 发,msg 发欢迎通知)
|
||||
- `edu.teaching.exam.published`(core-edu 发,msg 推送考试通知给学生)
|
||||
- `edu.teaching.assignment.submitted`(core-edu 发,msg 通知教师)
|
||||
- `edu.teaching.grade.recorded`(core-edu 发,msg 通知学生)
|
||||
- `edu.insight.mastery.updated`(data-ana 发,msg 触发预警)
|
||||
- 发布:无明确(pending-features 未要求 msg 发事件)
|
||||
- **错误码前缀**:`MSG_*`(MSG_VALIDATION_ERROR / NOT_FOUND / PERMISSION_DENIED / CONFLICT / BUSINESS_ERROR / DATABASE_ERROR / INTERNAL_ERROR,见 [application-error.ts](../../services/msg/src/shared/errors/application-error.ts))
|
||||
- **权限点**(3 个,见 [permission.guard.ts](../../services/msg/src/middleware/permission.guard.ts)):
|
||||
- `MSG_NOTIFICATION_SEND`、`MSG_NOTIFICATION_READ`、`MSG_NOTIFICATION_MANAGE`
|
||||
|
||||
### 4. 我的技术栈
|
||||
|
||||
- 语言:TypeScript 5.6(ESM 模式)
|
||||
- 框架:NestJS 10
|
||||
- ORM:Drizzle ORM 0.31 + mysql2 3.11
|
||||
- 全文检索:@elastic/elasticsearch 8.15(已实现 safeIndex/safeSearch,**无 mapping 定义**,依赖动态 mapping)
|
||||
- 消息总线:kafkajs 2.2(**已装依赖但无 consumer/producer 代码**,env.ts 有 KAFKA_BROKERS 默认值)
|
||||
- 推送:fetch POST 到 push-gateway `/internal/push`(降级模式,PUSH_GATEWAY_URL 未配置或失败时跳过)
|
||||
- 幂等去重:Redis(**env.ts 预留 REDIS_URL 但无 redis 客户端依赖**,pending-features P5 要求 event_id 去重)
|
||||
- 可观测:pino + prom-client + OpenTelemetry(三支柱已具备)
|
||||
|
||||
### 5. 我的阶段归属
|
||||
|
||||
- **P5 沟通与 AI 阶段**(pending-features §P5):
|
||||
- 会话/消息 CRUD + 调 Push Gateway 推送 + 通知偏好
|
||||
- 多渠道(站内/SMS/邮件/微信),沿用旧项目 dispatcher 模式
|
||||
- Elasticsearch 题库全文检索(从 MySQL 同步)
|
||||
- **退出标准**:教师发广播通知 → 全在线学生实时收到(Push Gateway)→ AI 辅助出题流式返回 → 题库全文检索 < 200ms
|
||||
- **依赖上游**:P1 黄金模板 classes、P3 core-edu(事件来源)、P5 push-gateway(推送通道,ai01 设计)、P5 ai(无直接依赖)
|
||||
|
||||
### 6. 我需要对齐的黄金模板项(对照 classes 服务)
|
||||
|
||||
- [x] 权限装饰器 `@RequirePermission`(3 个 MSG_* 权限点,全部 Controller 方法已覆盖)
|
||||
- [x] 错误码前缀统一(`MSG_*`)
|
||||
- [x] logger / metrics / tracer 三支柱(已具备)
|
||||
- [x] `/healthz` 健康检查(HealthModule 已注册)
|
||||
- [⚠️] `/readyz`(**仅检查 DB `SELECT 1`,未检查 ES 连通性**,ES 故障时仍返回 ok)
|
||||
- [x] 优雅关闭 SIGTERM(main.ts 已处理:app.close → closeEs → closeDb → shutdownTracer)
|
||||
- [ ] 测试覆盖率 ≥ 80%(**当前 0%**,无测试文件)
|
||||
- [x] Dockerfile 多阶段构建(已具备)
|
||||
- [⚠️] Zod 输入验证(Controller 用 `schema.parse(body)`,**但 ZodError 未在 GlobalErrorFilter 特殊处理,走默认 500 而非 400**)
|
||||
- [x] GlobalErrorFilter 统一兜底
|
||||
|
||||
### 7. 现有骨架差距与待决策(提请 coord 仲裁)
|
||||
|
||||
| # | 差距 | 影响 | 提请决策 |
|
||||
| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| M1 | **Kafka 消费未实现**:装了 kafkajs 但无 consumer 代码,无 shared/kafka/ 目录 | 004 §7.3 列 msg 消费 5 类事件(ExamPublished/HomeworkGraded/GradeRecorded/UserRegistered/MasteryUpdated) | 阶段 2 设计需补 Kafka consumer + 幂等去重 |
|
||||
| M2 | **无 Outbox**:骨架无 shared/outbox/ | msg 作为通知中心,发送通知后可能需发事件(如 NotificationSent) | 确认 msg 是否需 Outbox(iam 无,core-edu 有) |
|
||||
| M3 | **ES 无 mapping 定义**:依赖动态 mapping,索引名 "notifications" 硬编码在 service | 检索质量不稳定,索引管理缺失 | 阶段 2 设计补 mapping + ensureIndex |
|
||||
| M4 | **无独立 repository**:notifications.service.ts 直接用 db,无 repository 抽象 | 与黄金模板分层不一致(iam 有 repository) | 阶段 2 设计补 repository 层 |
|
||||
| M5 | **Redis 未引入**:env.ts 预留 REDIS_URL 但无 redis 客户端依赖 | pending-features P5 要求 event_id 去重(Redis SETNX 或 DB 唯一索引) | 阶段 2 设计决策:Redis SETNX vs DB 唯一索引 |
|
||||
| M6 | **createBatch 无事务/无批量优化**:for 循环串行调 send,无批量 INSERT | 性能瓶颈(广播场景) | 阶段 2 设计改为批量 INSERT |
|
||||
| M7 | **NotificationsModule 缺 exports**:notifications.module.ts 无 `exports: [NotificationsService]` | 未来 BFF 注入受阻 | 阶段 2 设计补 exports |
|
||||
| M8 | **ZodError 未特殊处理**:GlobalErrorFilter 未识别 ZodError,走默认 500 | 输入校验错误返回码错误(500 而非 400) | 阶段 2 设计 GlobalErrorFilter 补 ZodError 分支(iam 已有可参考) |
|
||||
| M9 | **gRPC 未实现**:proto 定义了 NotificationService,但无 gRPC controller | pending-features P5 未强制 gRPC | 确认 P5 是否启用 gRPC(ai03 提请统一决策) |
|
||||
| M10 | **README 与实现脱节**:README API 表标 `POST /notifications/:id/read`,实际是 PUT;漏 batch/user/:userId/user/:userId/page 端点;声称"消费 Kafka 事件"但无代码 | 文档误导 | 阶段 2 设计同步修正 README |
|
||||
| M11 | **main.ts 与 LifecycleService 重复关闭资源**:两者都调 closeDb/closeEs | 重复关闭可能报错(虽有 try-catch) | 阶段 2 设计统一关闭逻辑到 LifecycleService |
|
||||
| M12 | **proto 包名**:实际 `next_edu_cloud.msg.v1`,project_rules §5 规定 `edu.msg.v1` | 命名规范不一致 | **coord 仲裁**(ai03 已提请) |
|
||||
|
||||
---
|
||||
|
||||
## 三、服务审计表 — ai05
|
||||
|
||||
> 审计标准对照 [project_rules §3](../../.trae/rules/project_rules.md) 与 [known-issues §2.2 classes 黄金模板](../troubleshooting/known-issues.md)
|
||||
|
||||
| 服务 | 权限装饰器 | 错误码前缀 | logger | metrics | tracer | /healthz | /readyz | 优雅关闭 | 测试覆盖率 | Dockerfile |
|
||||
| ------- | ---------------- | -------------- | ------- | -------------- | ------------------------------- | -------- | ------------------- | --------------------- | ---------- | ---------- |
|
||||
| content | ✅ 16 端点全覆盖 | ✅ `CONTENT_*` | ✅ pino | ✅ prom-client | ✅ OTel + auto-instrumentations | ✅ | ⚠️ 仅 DB 不查 Neo4j | ✅ closeNeo4j→closeDb | 0% | ✅ |
|
||||
| msg | ✅ 6 端点全覆盖 | ✅ `MSG_*` | ✅ pino | ✅ prom-client | ✅ OTel + auto-instrumentations | ✅ | ⚠️ 仅 DB 不查 ES | ✅ closeEs→closeDb | 0% | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 四、跨模块契约对齐提请(coord 交叉审查)
|
||||
|
||||
### 4.1 接口一致性检查
|
||||
|
||||
| 本服务声明 | 对方服务声明 | 是否匹配 | 备注 |
|
||||
| --------------------------------------------------------- | ------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------ |
|
||||
| ai05 content: 暴露 KnowledgeGraphService.GetPrerequisites | ai06 ai: 调 content 查知识点(004 §9.3) | ⚠️ 待确认 | ai06 设计文档需确认调用签名 |
|
||||
| ai05 content: 暴露 TextbookService.ListTextbooks | ai03 teacher-bff: 聚合 content 查教材(004 §4.1) | ⚠️ 待确认 | ai03 设计文档需确认调用 |
|
||||
| ai05 content: 发布 `edu.content.question.published` | ai06 ai: 消费题库事件(004 §7.2 消费者 AI) | ⚠️ 待确认 | ai06 设计需确认是否消费 |
|
||||
| ai05 msg: 消费 `edu.teaching.exam.published` | ai03 core-edu: 发布考试事件 | ⚠️ 待确认 | ai03 提请 topic 命名统一(004 `edu.teaching.exam.published` vs core-edu 代码 `edu.exam.events`) |
|
||||
| ai05 msg: 消费 `edu.identity.user.created` | ai02 iam: 发布用户创建事件 | ⚠️ 待确认 | ai02 设计需确认 topic |
|
||||
| ai05 msg: 调 push-gateway `/internal/push` | ai01 push-gateway: 暴露推送端点 | ✅ 已实现 | msg 当前用 fetch POST,push-gateway 已有 /internal/push 端点 |
|
||||
|
||||
### 4.2 全局冲突检查
|
||||
|
||||
| 检查项 | 检查结果 | 备注 |
|
||||
| -------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 端口不冲突 | ✅ content 3005、msg 3007 | 与 iam(3002)/classes(3001)/teacher-bff(3003)/core-edu(3004)/data-ana(3006)/ai(3008) 不冲突 |
|
||||
| Topic 不重复 | ⚠️ 待汇总 | content 发布 `edu.content.question.published`;msg 仅消费不发布 |
|
||||
| 错误码前缀不重叠 | ✅ `CONTENT_*` / `MSG_*` 唯一 | 与 iam `IAM_*` / core-edu `CORE_EDU_*` 不重叠 |
|
||||
| Proto message 不遗漏 | ⚠️ 待确认 | events.proto 无 NotificationEvent,若 msg 发事件需补充;content.proto 缺 Update/Delete/分页(对比 classes.proto 有 page_token) |
|
||||
| proto 包名规范 | ⚠️ 不一致 | 实际 `next_edu_cloud.<domain>.v1`,规则要求 `edu.<domain>.v1`,**提请 coord 仲裁**(ai03 已提请) |
|
||||
|
||||
### 4.3 待 coord 仲裁的决策项
|
||||
|
||||
1. **proto 包名统一**:`next_edu_cloud.*` vs `edu.*`(影响全部 proto,需 coord 决策)
|
||||
2. **gRPC 启用时机**:P4/P5 是否启用 gRPC controller,还是继续 REST(影响 content/msg/iam/core-edu 全部服务)
|
||||
3. **content ES 检索归属**:P4(pending-features 注明"P4 仅 CRUD,P5 引入 ES")vs 004 §2.2(列 ES 使用者 Content、AI)—— 需明确 content 何时引入 ES
|
||||
4. **content Outbox**:是否需要(004 §7.2 列 content 发事件,但 pending-features P4 未要求)
|
||||
5. **msg Outbox**:是否需要(msg 仅消费事件触发通知,是否需发 NotificationSent 事件)
|
||||
6. **msg Redis 引入**:env.ts 预留 REDIS_URL 但无依赖,幂等去重用 Redis SETNX 还是 DB 唯一索引
|
||||
7. **events.proto 补充**:若 msg 发事件需追加 NotificationEvent message
|
||||
|
||||
---
|
||||
|
||||
## 五、下一步
|
||||
|
||||
1. **等待 coord 审核本确认书**(§4 跨模块契约对齐 + §4.3 仲裁项)
|
||||
2. coord 放行后进入**阶段 2:模块架构设计文档**,按 ai-allocation.md §7 模板产出:
|
||||
- content 模块架构设计文档(含 Neo4j 图模型、ES 索引 mapping、题库 CRUD API、与 ai 的 gRPC 接口、教材/章节结构树)
|
||||
- msg 模块架构设计文档(含通知渠道抽象策略模式、ES 降级查询、Kafka 消费幂等、push-gateway 推送协议、通知模板、已读/未读状态管理)
|
||||
3. 阶段 2 设计完成后同步更新 README(修正 §C3/M10 文档脱节问题)
|
||||
333
docs/architecture/ai06-phase1-understanding.md
Normal file
333
docs/architecture/ai06-phase1-understanding.md
Normal file
@@ -0,0 +1,333 @@
|
||||
# ai06 阶段 1 交付物:模块理解确认书
|
||||
|
||||
> AI 标识:ai06
|
||||
> 负责模块:data-ana(P4)、ai(P5)
|
||||
> 语言:Python 3.12+ / FastAPI 0.115+
|
||||
> 阶段:架构设计外包 · 阶段 1(全局理解)
|
||||
> 日期:2026-07-09
|
||||
> 关联文档:[ai-allocation.md](./ai-allocation.md)、[004 架构影响地图](./004_architecture_impact_map.md)、[pending-features.md](./roadmap/pending-features.md)、[known-issues.md](../troubleshooting/known-issues.md)
|
||||
|
||||
---
|
||||
|
||||
## 阶段 1 必读清单完成确认
|
||||
|
||||
按 ai-allocation.md §4,ai06 必读 7 份全局文档 + 全部 `.proto` + `services/data-ana/`、`services/ai/` 骨架源码:
|
||||
|
||||
| # | 文档 | 状态 |
|
||||
| --- | ------------------------------------------------------------------- | ------------------------------ |
|
||||
| 1 | [README.md](../../README.md) | ✅ 已读 |
|
||||
| 2 | [MIGRATION_GUIDE.md](../../MIGRATION_GUIDE.md) | ✅ 已读 |
|
||||
| 3 | [004 架构影响地图](./004_architecture_impact_map.md) | ✅ 已读 |
|
||||
| 4 | [pending-features.md](./roadmap/pending-features.md) | ✅ 已读 |
|
||||
| 5 | [project_rules.md](../../.trae/rules/project_rules.md) | ✅ 已读(workspace 规则) |
|
||||
| 6 | [coding-standards.md](../standards/coding-standards.md) | ✅ 已读(重点 §4 Python 规范) |
|
||||
| 7 | [multi-ai-collaboration.md](../standards/multi-ai-collaboration.md) | ✅ 已读 |
|
||||
| 8 | `packages/shared-proto/proto/*.proto`(8 份) | ✅ 已读 |
|
||||
| 9 | `services/data-ana/src/`、`services/ai/src/` 全部源码 | ✅ 已读 |
|
||||
|
||||
> ai06 不需要读 classes 黄金模板(ai-allocation.md §4 矩阵 ai06 列对黄金模板行为 "—"),但审计表横切关注点对齐仍参考 classes 标准。
|
||||
|
||||
---
|
||||
|
||||
## 模块理解确认书 — data-ana
|
||||
|
||||
### 1. 我在架构中的位置
|
||||
|
||||
- **层级**:业务微服务层(L5),属 **D6 智能洞察领域**(004 §1.1b)
|
||||
- **上游**:
|
||||
- api-gateway 直接 HTTP 代理 `/api/v1/analytics/*` → data-ana:3006(见 [api-gateway main.go:142-148](../../services/api-gateway/main.go))
|
||||
- teacher-bff / student-bff 聚合查询(004 §4.1:BFF → 业务服务 gRPC;当前为 REST)
|
||||
- **下游**:
|
||||
- ClickHouse(独占读模型,宽表 `student_dashboard_view` / `student_errors`)
|
||||
- Kafka(消费 Debezium CDC 事件 + 待发布 `edu.insight.mastery.updated` 领域事件)
|
||||
- **通信方式**:
|
||||
- 入口:当前 REST(`/analytics/class/{id}/performance`、`/analytics/student/{id}/weakness`、`/analytics/student/{id}/errorbook`);目标态(004 §4.1 + analytics.proto)转 **gRPC** 暴露 `AnalyticsService`
|
||||
- 出口:Kafka 消费(CDC + 领域事件订阅);Kafka 发布(`edu.insight.mastery.updated`,**未实现**)
|
||||
- **端口**:3006(见 [data-ana config.py:7](../../services/data-ana/src/data_ana/config.py) + [api-gateway config.go:55](../../services/api-gateway/internal/config/config.go))
|
||||
|
||||
### 2. 我的限界上下文
|
||||
|
||||
- **聚合职责**:学情分析读模型构建 + 查询服务,承载 **D6 智能洞察领域**的"分析/诊断"子域
|
||||
- **聚合根 / 实体**:
|
||||
- `StudentDashboard`(学生学情宽表视图,ClickHouse 物化)
|
||||
- `StudentErrorBook`(学生错题本,ClickHouse 物化)
|
||||
- `ClassPerformance`(班级成绩聚合,ClickHouse 即时聚合)
|
||||
- `MasterySnapshot`(知识点掌握度快照,**待实现**)
|
||||
- **业务领域**:D6 智能洞察(与 ai 服务共享领域,ai 偏"生成",data-ana 偏"分析")
|
||||
- **我不负责**:
|
||||
- 不负责写模型(成绩由 core-edu 写 MySQL,data-ana 只消费 CDC)
|
||||
- 不负责题库内容(→ content 服务)
|
||||
- 不负责通知投递(→ msg 服务消费 data-ana 发布的 `mastery.updated` 事件触发预警)
|
||||
- 不负责 AI 推理(→ ai 服务,ai 通过 gRPC 反向查询 data-ana 学情数据)
|
||||
- **数据自治**:独占 `edu_analytics` ClickHouse 数据库(不与 MySQL 写模型混用)
|
||||
|
||||
### 3. 我与外部的契约
|
||||
|
||||
#### 消费的 proto message
|
||||
|
||||
| 来源 | message | 用途 |
|
||||
| --------------- | ------------------------------- | ---------------------------------------------- |
|
||||
| events.proto | `GradeEvent` | 消费 core-edu 成绩写入事件 → 更新学情宽表 |
|
||||
| events.proto | `ExamEvent` | 消费考试事件 → 缓存 exam_id→class_id 映射 |
|
||||
| events.proto | `HomeworkEvent` | 消费作业提交/批改事件 → 更新学情(**待实现**) |
|
||||
| events.proto | `ClassEvent` | 消费班级变更事件 → 同步班级维度(**待实现**) |
|
||||
| analytics.proto | `GetClassPerformanceRequest` 等 | gRPC 暴露契约(**待实现 gRPC server**) |
|
||||
|
||||
> **CDC 直连 vs 领域事件双通道**:当前实现走 Debezium CDC(直接监听 MySQL binlog),不依赖 core-edu 的 Outbox。这是 ADR-008 的设计决策(CDC 解耦 Outbox Relay,减少业务侵入)。Outbox 领域事件(events.proto)作为业务语义更清晰的补充通道,待 P4 后期评估是否双消费。
|
||||
|
||||
#### 暴露的 API / 事件
|
||||
|
||||
**HTTP 端点**(当前实现,见 [main.py](../../services/data-ana/src/data_ana/main.py)):
|
||||
|
||||
| method | path | 说明 |
|
||||
| ------ | ------------------------------------------- | -------------------------------------- |
|
||||
| GET | `/healthz` | liveness |
|
||||
| GET | `/readyz` | readiness(含 ClickHouse + CDC 状态) |
|
||||
| GET | `/metrics` | Prometheus 指标 |
|
||||
| GET | `/analytics/class/{class_id}/performance` | 班级成绩分析(平均分/及格率/参考人数) |
|
||||
| GET | `/analytics/student/{student_id}/weakness` | 学生薄弱知识点(mastery < 0.6) |
|
||||
| GET | `/analytics/student/{student_id}/errorbook` | 学生错题本 |
|
||||
|
||||
**gRPC 契约**(analytics.proto,**待实现**):
|
||||
|
||||
- `GetClassPerformance(GetClassPerformanceRequest) → ClassPerformance`
|
||||
- `GetStudentWeakness(GetStudentWeaknessRequest) → StudentWeakness`
|
||||
- `GetLearningTrend(GetLearningTrendRequest) → LearningTrend`(**当前 HTTP 未暴露**)
|
||||
|
||||
**发布的领域事件**:
|
||||
|
||||
| 事件 | Topic(004 §7.2) | 触发时机 | 消费者(004 §7.3) |
|
||||
| ---------------- | ----------------------------- | -------------- | --------------------------------------- |
|
||||
| `MasteryUpdated` | `edu.insight.mastery.updated` | 掌握度计算完成 | core-edu(推荐个性化练习)、msg(预警) |
|
||||
|
||||
> **当前未实现发布**:data-ana 当前只消费不发布。掌握度计算完成后应通过 Kafka 发布 `MasteryUpdated`,下游 core-edu / msg 消费。阶段 2 设计需补全此发布链路(Python 无 Outbox 模式,需评估直接 producer 还是引入 Outbox 表)。
|
||||
|
||||
- **错误码前缀**:`DATA_ANA_*`(待定义清单,见阶段 2 §6)
|
||||
- **缓存**:当前无 Redis 缓存;004 §6.3 学情宽表走 ClickHouse 实时,CDC 同步延迟 < 5s
|
||||
|
||||
### 4. 我的技术栈
|
||||
|
||||
- 语言:Python 3.12+
|
||||
- 框架:FastAPI 0.115+ / uvicorn
|
||||
- ORM / 客户端:`clickhouse-connect`(HTTP 协议,非原生协议)
|
||||
- 消息:`aiokafka`(CDC 消费者,AIOKafkaConsumer)
|
||||
- 配置:`pydantic-settings` BaseSettings(env_prefix="",全大写环境变量)
|
||||
- 可观测:
|
||||
- 日志:`structlog` 24.x(`make_filtering_bound_logger`,**注意**:旧版 `make_filtering_logger` 已废弃)
|
||||
- 指标:`prometheus-client` + `make_asgi_app()` 挂载 `/metrics`
|
||||
- 链路:`opentelemetry-sdk` + `OTLPSpanExporter` + `FastAPIInstrumentor.instrument_app(app)`
|
||||
- 序列化:JSON(Debezium 事件 `schemas.enable=false`,直接 `json.loads`)
|
||||
- 测试:pytest + pytest-asyncio(**当前 0% 覆盖率**)
|
||||
|
||||
### 5. 我的阶段归属
|
||||
|
||||
- **P4 内容分析阶段(M11-M13)**:建 DataAna 服务 + CDC 链路落地
|
||||
- **退出标准**(pending-features P4):学生查看学情诊断 ClickHouse 宽表 5s 内返回 + CDC 链路延迟 < 5s
|
||||
- **依赖上游**:
|
||||
- P1 地基:api-gateway 路由 + arch.db 扫描器 Python 支持
|
||||
- P3 核心教学:core-edu 写成绩到 MySQL(Debezium 监听 binlog)
|
||||
- P4 同期:content 服务(提供知识点 ID 供掌握度计算)
|
||||
- **下游依赖我**:
|
||||
- P5 ai 服务通过 gRPC 查询学情数据(004 §4.1:AI → DataAna gRPC)
|
||||
- P5 msg 服务消费 `mastery.updated` 触发预警
|
||||
|
||||
### 6. 我需要对齐的黄金模板项(对照 classes 服务 + Python 规范)
|
||||
|
||||
> Python 服务无 NestJS 装饰器体系,权限校验等通过等价方式实现。
|
||||
|
||||
- [ ] 权限装饰器等价物:**当前 HTTP 端点全部裸露,无权限校验**。Gateway 层做 JWT 校验,但 data-ana 本身未校验 `x-user-id` / DataScope。**阶段 2 需设计 FastAPI Depends 权限依赖 + DataScope 过滤注入**
|
||||
- [ ] 错误码前缀统一:**当前无错误码体系**,降级时返回 `degraded: true` 标记但无业务错误码。**阶段 2 需定义 `DATA_ANA_*` 错误码清单**
|
||||
- [x] logger / metrics / tracer 三支柱(已具备,见 main.py + clickhouse_client.py)
|
||||
- [x] `/healthz` + `/readyz` 健康检查(已具备,readyz 含 ClickHouse ping + CDC 状态)
|
||||
- [ ] 优雅关闭 SIGTERM:当前 lifespan 仅关闭 CDC task + ClickHouse client,**未注册 SIGTERM 信号处理器**显式 drain
|
||||
- [ ] 测试覆盖率 ≥ 80%:**当前 0%**,无 tests/ 目录
|
||||
- [ ] Dockerfile 多阶段构建:**当前单阶段**(`FROM python:3.12-slim` → `uv sync` → `COPY src`),非多阶段
|
||||
- [ ] Pydantic 输入验证:**当前端点直接接收 path param,无 Pydantic 请求模型校验**(应补 `ClassPerformanceResponse` 等 response_model)
|
||||
- [x] 配置通过 pydantic-settings 管理(已具备)
|
||||
- [x] 异步优先(async def,aiokafka async consumer)
|
||||
- [ ] 类型注解强制:**部分函数缺返回值标注**(如 `init_logger` 返回 `BoundLogger` 但 `_logger` 全局变量标注 `None`,需统一)
|
||||
- [ ] ruff check 零错误:需阶段 2 验证
|
||||
|
||||
---
|
||||
|
||||
## 模块理解确认书 — ai
|
||||
|
||||
### 1. 我在架构中的位置
|
||||
|
||||
- **层级**:业务微服务层(L5),属 **D6 智能洞察领域**(004 §1.1b,与 data-ana 共享领域)
|
||||
- **上游**:
|
||||
- api-gateway 直接 HTTP 代理 `/api/v1/ai/*` → ai:3008(见 [api-gateway main.go:138-139](../../services/api-gateway/main.go))
|
||||
- teacher-bff 聚合 AI 出题/优化能力(004 §4.1:BFF → ai,当前 REST)
|
||||
- **下游**:
|
||||
- content 服务(gRPC 查询知识点 / 题库,004 §4.1:AI → Content gRPC)
|
||||
- data-ana 服务(gRPC 查询学情数据,004 §4.1:AI → DataAna gRPC)
|
||||
- LLM Provider(外部 HTTP,OpenAI 兼容 REST API)
|
||||
- **通信方式**:
|
||||
- 入口:当前 REST(`/ai/chat`、`/ai/chat/stream`、`/ai/generate/question`、`/ai/optimize/expression`);目标态(ai.proto)转 **gRPC** 暴露 `AiService`(含 `StreamChat` 流式 RPC)
|
||||
- 出口:gRPC 调 content / data-ana(**当前未实现,仅 LLM HTTP 调用**);SSE 流式对前端
|
||||
- **端口**:3008(见 [ai config.py:8](../../services/ai/src/ai/config.py) + [api-gateway config.go:57](../../services/api-gateway/internal/config/config.go))
|
||||
|
||||
### 2. 我的限界上下文
|
||||
|
||||
- **聚合职责**:LLM 调用网关 + 教学场景 AI 编排(备课 / 出题 / 表达优化),承载 **D6 智能洞察领域**的"生成"子域
|
||||
- **聚合根 / 实体**:
|
||||
- `ChatConversation`(聊天会话,**待实现**,当前无状态)
|
||||
- `GeneratedQuestion`(生成的题目,待审核入库)
|
||||
- `PromptTemplate`(Prompt 模板,**待实现**)
|
||||
- `UsageRecord`(用量计费记录,**待实现**)
|
||||
- **业务领域**:D6 智能洞察(与 data-ana 共享,data-ana 偏"分析",ai 偏"生成")
|
||||
- **我不负责**:
|
||||
- 不负责题库存储(→ content 服务,ai 生成后调 content.CreateQuestions 入库)
|
||||
- 不负责学情计算(→ data-ana 服务,ai 查询学情用于个性化出题)
|
||||
- 不负责通知投递(→ msg 服务)
|
||||
- 不持有业务状态(无 DB 写入,无 Outbox;用量计费记录可走 Kafka 事件给 data-ana 落 ClickHouse)
|
||||
- **数据自治**:**无独占数据库**(设计上无状态;用量计费通过 Kafka 事件外发)
|
||||
|
||||
### 3. 我与外部的契约
|
||||
|
||||
#### 消费的 proto message
|
||||
|
||||
| 来源 | message / service | 用途 |
|
||||
| --------------- | ---------------------------------------- | --------------------------------------- |
|
||||
| content.proto | `KnowledgeGraphService.GetPrerequisites` | 查询知识点前置依赖用于出题上下文 |
|
||||
| content.proto | `KnowledgeGraphService.GetLearningPath` | 查询学生学习路径用于个性化出题 |
|
||||
| analytics.proto | `AnalyticsService.GetStudentWeakness` | 查询学生薄弱知识点用于靶向出题 |
|
||||
| analytics.proto | `AnalyticsService.GetLearningTrend` | 查询学习趋势用于难度调节 |
|
||||
| ai.proto | `ChatRequest` 等 | gRPC 暴露契约(**待实现 gRPC server**) |
|
||||
|
||||
#### 暴露的 API / 事件
|
||||
|
||||
**HTTP 端点**(当前实现,见 [main.py](../../services/ai/src/ai/main.py)):
|
||||
|
||||
| method | path | 说明 |
|
||||
| ------ | ------------------------- | ---------------------------- |
|
||||
| GET | `/healthz` | liveness |
|
||||
| GET | `/readyz` | readiness(含 LLM 是否配置) |
|
||||
| GET | `/metrics` | Prometheus 指标 |
|
||||
| POST | `/ai/chat` | LLM 聊天(非流式) |
|
||||
| POST | `/ai/chat/stream` | LLM 流式聊天(SSE) |
|
||||
| POST | `/ai/generate/question` | 生成题目 |
|
||||
| POST | `/ai/optimize/expression` | 优化表达 |
|
||||
|
||||
**gRPC 契约**(ai.proto,**待实现**):
|
||||
|
||||
- `Chat(ChatRequest) → ChatResponse`
|
||||
- `StreamChat(ChatRequest) → stream ChatChunk`(流式 RPC)
|
||||
- `GenerateQuestion(GenerateQuestionRequest) → GeneratedQuestion`
|
||||
- `OptimizeExpression(OptimizeExpressionRequest) → OptimizedExpression`
|
||||
|
||||
**发布的领域事件**:**当前无发布**。设计上可发布 `AIUsageRecorded` 事件(用量计费),由 data-ana 消费落 ClickHouse。004 §7.2 未列出此 topic,**阶段 2 需与 coord 确认是否新增 `edu.insight.ai.usage` topic**。
|
||||
|
||||
- **错误码前缀**:`AI_*`(待定义清单,见阶段 2 §6)
|
||||
- **降级策略**:LLM API key 为空或调用失败时返回 `degraded: true` 骨架响应(见 [llm_client.py](../../services/ai/src/ai/llm_client.py))
|
||||
|
||||
### 4. 我的技术栈
|
||||
|
||||
- 语言:Python 3.12+
|
||||
- 框架:FastAPI 0.115+ / uvicorn
|
||||
- LLM 客户端:`httpx` 异步直接调 OpenAI 兼容 REST API(**不依赖 openai SDK**,见 [llm_client.py:1-7](../../services/ai/src/ai/llm_client.py))
|
||||
- 配置:`pydantic-settings` BaseSettings(env_prefix="")
|
||||
- 可观测:
|
||||
- 日志:`structlog`
|
||||
- 指标:`prometheus-client` + `make_asgi_app()`
|
||||
- 链路:`opentelemetry-sdk` + `OTLPSpanExporter` + `FastAPIInstrumentor`(dev_mode=true 时跳过 exporter 初始化避免本地无 collector 报错)
|
||||
- 流式响应:FastAPI `StreamingResponse` + `AsyncGenerator`,SSE 格式 `data: <chunk>\n\n`
|
||||
- 测试:pytest(**当前 0% 覆盖率**)
|
||||
|
||||
### 5. 我的阶段归属
|
||||
|
||||
- **P5 沟通与 AI 阶段(M14-M16)**:建 AI 网关 + LLM Provider 适配 + 流式 SSE
|
||||
- **退出标准**(pending-features P5):AI 辅助出题流式返回 + 题库全文检索 < 200ms(ES 部分由 ai05 负责)
|
||||
- **依赖上游**:
|
||||
- P1 地基:api-gateway 路由
|
||||
- P4 内容分析:content 服务(gRPC 查询知识点 / 题库)+ data-ana 服务(gRPC 查询学情)
|
||||
- 外部:LLM Provider API key(OpenAI / Anthropic / 百川 / 本地模型)
|
||||
- **下游依赖我**:
|
||||
- P5 teacher-bff 聚合 AI 出题 mutation(004 §9.3:教师用 AI 出题并发布到班级)
|
||||
- P5 teacher-portal SSE 流式 AI 对话(前端)
|
||||
|
||||
### 6. 我需要对齐的黄金模板项(对照 classes 服务 + Python 规范)
|
||||
|
||||
- [ ] 权限装饰器等价物:**当前 HTTP 端点全部裸露,无权限校验**。阶段 2 需设计 FastAPI Depends 权限依赖(AI 出题需 `AI_QUESTION_GENERATE` 权限,表达优化需 `AI_EXPRESSION_OPTIMIZE`)
|
||||
- [ ] 错误码前缀统一:**当前无错误码体系**,降级时返回 `degraded: true` 但无业务错误码。**阶段 2 需定义 `AI_*` 错误码清单**
|
||||
- [x] logger / metrics / tracer 三支柱(已具备)
|
||||
- [x] `/healthz` + `/readyz` 健康检查(已具备,readyz 含 LLM 配置状态)
|
||||
- [ ] 优雅关闭 SIGTERM:当前 lifespan 无显式 drain(LLM 流式请求需等待完成)
|
||||
- [ ] 测试覆盖率 ≥ 80%:**当前 0%**,无 tests/ 目录
|
||||
- [ ] Dockerfile 多阶段构建:**当前单阶段**(`FROM python:3.12-slim`)
|
||||
- [ ] Pydantic 输入验证:**当前仅 `ChatRequest` 是 BaseModel,`generate/question` 和 `optimize/expression` 直接接收 `prompt: str` / `text: str` query param,无请求体模型**。阶段 2 需补完整 Pydantic 请求/响应模型
|
||||
- [x] 配置通过 pydantic-settings 管理(已具备)
|
||||
- [x] 异步优先(httpx async + AsyncGenerator stream)
|
||||
- [x] 类型注解强制(已基本符合,main.py 函数返回值已标注)
|
||||
- [ ] LLM Provider 适配器模式:**当前仅 OpenAI 兼容 REST**,未抽象 Provider 接口。阶段 2 需设计 `LLMProvider` 抽象 + OpenAI/Anthropic/百川/本地 多适配器
|
||||
- [ ] Prompt 模板管理:**当前 Prompt 硬编码在 main.py**,阶段 2 需设计模板管理(DB 或文件)
|
||||
- [ ] 用量计费 / 频率限制:**当前无用量记录和限流**,阶段 2 需设计(Kafka 事件 + Redis 限流)
|
||||
- [ ] 备课工作流:**当前未实现**,pending-features P5 要求"分析学情 → 推荐知识点 → 生成题目 → 教师审核 → 入库"完整工作流
|
||||
- [ ] ruff check 零错误:需阶段 2 验证
|
||||
|
||||
---
|
||||
|
||||
## ai06 服务审计表
|
||||
|
||||
> 对照 ai-allocation.md §10 审计模板。data-ana 与 ai 均为 Python/FastAPI,黄金模板对齐按 Python 规范(coding-standards §4)评估。
|
||||
> 符号说明:✅ 已实现 | ❌ 缺失 | ⚠️ 部分实现
|
||||
|
||||
| 服务 | 权限校验 | 错误码前缀 | logger | metrics | tracer | /healthz | /readyz | 优雅关闭 | 测试覆盖率 | Dockerfile |
|
||||
| -------- | -------- | ---------- | ------------ | ------------- | ------------------- | -------- | -------------------- | ------------------- | ---------- | ---------- |
|
||||
| data-ana | ❌ | ❌ | ✅ structlog | ✅ prometheus | ✅ OTel | ✅ | ✅(含 CH+CDC 状态) | ⚠️ 仅 lifespan 关闭 | 0% | ❌ 单阶段 |
|
||||
| ai | ❌ | ❌ | ✅ structlog | ✅ prometheus | ✅ OTel(dev 跳过) | ✅ | ✅(含 LLM 状态) | ⚠️ 仅 lifespan 关闭 | 0% | ❌ 单阶段 |
|
||||
|
||||
### 审计发现的关键差距清单
|
||||
|
||||
#### data-ana 关键差距(按优先级)
|
||||
|
||||
1. **P0 权限校验缺失**:所有 `/analytics/*` 端点裸露,无 DataScope 过滤。学生 A 可查询学生 B 的错题本(越权风险)。阶段 2 必须设计 `Depends(require_permission)` + `Depends(inject_data_scope)` 依赖注入
|
||||
2. **P0 gRPC server 未实现**:analytics.proto 定义了 `AnalyticsService` 但 data-ana 当前仅 HTTP。004 §4.1 明确 BFF → 业务服务走 gRPC,阶段 2 需引入 `grpc.aio` + `betterproto` 实现 gRPC server
|
||||
3. **P1 事件发布缺失**:`edu.insight.mastery.updated` 事件未发布,下游 core-edu/msg 无法消费。需设计掌握度计算 + Kafka producer 发布链路
|
||||
4. **P1 ClickHouse schema 不规范**:当前 `student_dashboard_view` 实为 MergeTree 表(注释提到应为 ReplacingMergeTree(last_updated) 实现幂等去重),无 DDL 文件管理。阶段 2 需产出完整 ClickHouse DDL(5 张宽表:考试/作业/成绩/掌握度/出勤)
|
||||
5. **P2 测试覆盖率 0%**:无 tests/ 目录,pytest 未配置
|
||||
6. **P2 Dockerfile 单阶段**:未做多阶段构建(builder + runtime),镜像体积大
|
||||
7. **P2 掌握度计算算法缺失**:当前 `_handle_grades_event` 用 `score / 100.0` 简化,未实现 pending-features 要求的"加权滑动平均"算法
|
||||
8. **P2 无 Pydantic 响应模型**:端点返回 `dict` 而非 `BaseModel`,无 `response_model` 校验
|
||||
|
||||
#### ai 关键差距(按优先级)
|
||||
|
||||
1. **P0 权限校验缺失**:所有 `/ai/*` 端点裸露,AI 出题等敏感操作无权限校验
|
||||
2. **P0 gRPC server 未实现**:ai.proto 定义了 `AiService`(含 `StreamChat` 流式 RPC)但 ai 当前仅 HTTP。阶段 2 需引入 `grpc.aio` 实现 gRPC server + 流式 RPC
|
||||
3. **P0 gRPC client 未实现**:004 §4.1 明确 AI → Content / AI → DataAna 走 gRPC,当前未实现。阶段 2 需设计 gRPC client 调用 content / data-ana
|
||||
4. **P1 LLM Provider 适配器缺失**:当前 `llm_client.py` 仅 OpenAI 兼容 REST,未抽象 Provider 接口。pending-features P5 要求"LLM Provider 适配(OpenAI/Anthropic/百川/本地模型)"
|
||||
5. **P1 Prompt 模板管理缺失**:system prompt 硬编码在 main.py,无模板管理。阶段 2 需设计模板存储(文件 or DB)+ 模板渲染
|
||||
6. **P1 备课工作流缺失**:pending-features P5 要求"分析学情 → 推荐知识点 → 生成题目 → 教师审核 → 入库"完整工作流,当前仅"生成题目"单步
|
||||
7. **P1 用量计费 / 频率限制缺失**:无用量记录(token 消耗)、无频率限制(用户可无限调用 LLM)。阶段 2 需设计 Redis 限流 + Kafka 事件外发用量
|
||||
8. **P2 测试覆盖率 0%**:无 tests/ 目录
|
||||
9. **P2 Dockerfile 单阶段**:未做多阶段构建
|
||||
10. **P2 Pydantic 输入验证不完整**:`generate/question` 和 `optimize/expression` 直接接收 query param,无请求体模型
|
||||
|
||||
---
|
||||
|
||||
## 阶段 1 待 coord 交叉审查的跨模块契约对齐项
|
||||
|
||||
以下项需 coord 在交叉审查时仲裁(见 ai-allocation.md §8):
|
||||
|
||||
| # | 议题 | 涉及方 | 当前状态 | ai06 建议 |
|
||||
| --- | -------------------------------------------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | **data-ana 是否发布 `edu.insight.mastery.updated` 事件** | data-ana → core-edu / msg | 004 §7.2 列出此 topic,但当前 data-ana 未实现发布 | 阶段 2 设计发布链路;Python 无 Outbox,建议直接 Kafka producer(掌握度计算是派生数据,非事务写) |
|
||||
| 2 | **ai 是否发布 `edu.insight.ai.usage` 用量事件** | ai → data-ana | 004 §7.2 未列出此 topic | 建议 coord 新增此 topic 用于用量计费落 ClickHouse;若 coord 不同意则 ai 自建用量记录表(破坏无状态原则) |
|
||||
| 3 | **data-ana / ai 是否需要实现 gRPC server** | data-ana / ai ← teacher-bff / student-bff / ai | 004 §4.1 明确 BFF → 业务服务走 gRPC,analytics.proto / ai.proto 已定义 service,但当前两服务仅 HTTP | 阶段 2 引入 `grpc.aio` + `betterproto` 实现 gRPC server;HTTP 端点保留作为 Gateway 直连降级通道 |
|
||||
| 4 | **CDC 直连 vs Outbox 领域事件双通道** | data-ana ← core-edu | 当前 data-ana 走 Debezium CDC(监听 binlog),不消费 core-edu Outbox 事件(events.proto) | 维持 CDC 为主通道(ADR-008 决策);events.proto 作为业务语义补充,待 P4 后期评估是否双消费 |
|
||||
| 5 | **data-ana DataScope 过滤实现位置** | data-ana + iam | 004 §5.3 DataScope 6 级,业务服务在 Repository 层注入 WHERE;data-ana 是 ClickHouse 查询无 Repository 层 | 阶段 2 在 ClickHouse 查询 SQL 拼接时注入 DataScope WHERE(学生只能看自己,教师看本班,校管理员看本校);需 iam 提供 `getEffectiveDataScope(userId)` API |
|
||||
| 6 | **ai 备课工作流是否引入 Temporal** | ai + coord | 004 §2.3 列出 Temporal 用于"工作流编排(考试生命周期、AI 编排)",P3 已引入 Temporal 试点 | 阶段 2 评估:简单工作流(4 步)可用 FastAPI BackgroundTasks;复杂工作流(含教师审核等待)建议 Temporal;待 coord 仲裁 |
|
||||
| 7 | **LLM Provider 切换的配置化** | ai + coord | 当前 ai config.py 仅 OpenAI + Anthropic 字段 | 阶段 2 设计 `LLMProvider` 抽象 + 配置化路由(按 model 名路由到不同 provider);本地模型走 Ollama REST API |
|
||||
| 8 | **data-ana ClickHouse DDL 管理位置** | data-ana + coord | 当前无 DDL 文件,宽表手动创建 | 建议 coord 在 `infra/clickhouse/` 下建立 DDL 目录(类似 MySQL init.sql),data-ana 提供 DDL 内容 |
|
||||
|
||||
---
|
||||
|
||||
## 阶段 1 总结
|
||||
|
||||
ai06 已完成阶段 1 全局理解,产出本确认书。核心结论:
|
||||
|
||||
1. **data-ana**(P4):CDC 链路已跑通(Debezium → Kafka → ClickHouse),3 个分析端点已实现降级模式。**关键差距**:权限校验、gRPC server、事件发布(mastery.updated)、ClickHouse DDL 规范化、掌握度算法、测试覆盖。
|
||||
2. **ai**(P5):LLM 客户端已实现降级模式(httpx 异步 + SSE 流式),4 个端点已实现。**关键差距**:权限校验、gRPC server + client、LLM Provider 适配器、Prompt 模板管理、备课工作流、用量计费、测试覆盖。
|
||||
3. **跨模块契约**:8 项待 coord 仲裁,最关键的是 gRPC server 实现决策(影响阶段 2 设计核心)和事件发布 topic 新增决策。
|
||||
|
||||
下一步进入阶段 2,按 ai-allocation.md §7 模板产出 data-ana 与 ai 的模块架构设计文档。
|
||||
858
docs/architecture/ai06-phase2-design.md
Normal file
858
docs/architecture/ai06-phase2-design.md
Normal file
@@ -0,0 +1,858 @@
|
||||
# ai06 阶段 2 交付物:模块架构设计文档
|
||||
|
||||
> AI 标识:ai06
|
||||
> 负责模块:data-ana(P4)、ai(P5)
|
||||
> 阶段:架构设计外包 · 阶段 2(模块架构设计)
|
||||
> 日期:2026-07-09
|
||||
> 关联文档:[ai06 阶段 1 确认书](./ai06-phase1-understanding.md)、[ai-allocation.md](./ai-allocation.md)、[004 架构影响地图](./004_architecture_impact_map.md)、[pending-features.md](./roadmap/pending-features.md)
|
||||
> 审查请求:本设计文档待 coord 按 ai-allocation.md §8 交叉审查(接口一致性 / 端口冲突 / Topic 重复 / 错误码重叠 / 黄金模板对齐)
|
||||
|
||||
---
|
||||
|
||||
## 设计原则与全局约束
|
||||
|
||||
本设计遵循以下强制约束(来自 project_rules.md + coding-standards.md + 004):
|
||||
|
||||
1. **契约先行**:proto 已定义(analytics.proto / ai.proto),实现前不修改 proto,如需修改走 coord 流程
|
||||
2. **CQRS 读写分离**:data-ana 是纯读模型服务(无 MySQL 写),ClickHouse 宽表由 CDC 投影构建
|
||||
3. **事件驱动**:data-ana 消费 CDC + 领域事件;ai 不参与事件流(无状态)
|
||||
4. **gRPC 优先**:004 §4.1 明确 BFF → 业务服务走 gRPC,两服务需实现 gRPC server(HTTP 保留作 Gateway 直连降级)
|
||||
5. **DataScope 过滤**:004 §5.3 DataScope 6 级在查询层注入 WHERE
|
||||
6. **三支柱可观测**:structlog + prometheus-client + OpenTelemetry(已具备,需补业务指标)
|
||||
7. **降级模式**:外部依赖(ClickHouse / LLM / 下游 gRPC)不可用时返回骨架数据 + `degraded: true`
|
||||
8. **Python 规范**:pydantic-settings 配置 / Pydantic 模型校验 / async 优先 / 类型注解强制 / ruff 零错误
|
||||
|
||||
---
|
||||
|
||||
# 模块架构设计文档 — data-ana
|
||||
|
||||
## 1. 模块内部分层图
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Entry["入口层"]
|
||||
HTTP[FastAPI HTTP Router<br/>/analytics/* + /healthz + /readyz]
|
||||
GRPC[grpc.aio Server<br/>AnalyticsService]
|
||||
end
|
||||
|
||||
subgraph Middleware["中间件层"]
|
||||
AUTH[AuthDepends<br/>校验 x-user-id / x-user-roles]
|
||||
SCOPE[DataScopeDepends<br/>注入 data_scope 元数据]
|
||||
TRACE[OTel FastAPIInstrumentor<br/>+ grpc.aio server interceptor]
|
||||
end
|
||||
|
||||
subgraph Service["应用服务层 Application Service"]
|
||||
S1[AnalyticsService<br/>班级/学生/趋势查询编排]
|
||||
S2[MasteryService<br/>掌握度计算 + 事件发布]
|
||||
S3[ErrorBookService<br/>错题本查询]
|
||||
end
|
||||
|
||||
subgraph Repo["数据访问层 Repository"]
|
||||
R1[ClickHouseRepository<br/>宽表查询 + DataScope WHERE 注入]
|
||||
R2[KafkaProducer<br/>mastery.updated 事件发布]
|
||||
R3[IamClient<br/>gRPC 调 iam.getEffectiveDataScope]
|
||||
end
|
||||
|
||||
subgraph Consumer["CDC 消费者(后台任务)"]
|
||||
C1[CdcConsumer<br/>aiokafka AIOKafkaConsumer]
|
||||
C2[ExamCache<br/>exam_id→class_id 内存映射]
|
||||
C3[EventHandler<br/>grades/exams/homework/classes 路由]
|
||||
end
|
||||
|
||||
subgraph Storage["存储 / 总线"]
|
||||
CH[(ClickHouse<br/>edu_analytics 库)]
|
||||
KAFKA[(Kafka<br/>edu-cdc.* + edu.insight.mastery.updated)]
|
||||
IAM[iam:3002 gRPC]
|
||||
end
|
||||
|
||||
HTTP --> AUTH --> SCOPE --> S1
|
||||
HTTP --> S3
|
||||
GRPC --> S1
|
||||
S1 --> R1
|
||||
S3 --> R1
|
||||
S2 --> R1
|
||||
S2 --> R2
|
||||
SCOPE --> R3
|
||||
R1 --> CH
|
||||
R2 --> KAFKA
|
||||
R3 --> IAM
|
||||
|
||||
C1 --> C3
|
||||
C3 --> C2
|
||||
C3 --> R1
|
||||
C1 --> KAFKA
|
||||
```
|
||||
|
||||
**分层规则**:
|
||||
|
||||
- **入口层**:HTTP(保留作 Gateway 直连降级)+ gRPC(主入口,BFF 调用)。两入口共享同一 Application Service
|
||||
- **中间件层**:FastAPI Depends 链(`AuthDepends` → `DataScopeDepends`);gRPC 用 server interceptor 注入身份元数据
|
||||
- **应用服务层**:编排查询 / 计算掌握度 / 发布事件,不直接访问存储
|
||||
- **数据访问层**:ClickHouse 查询封装 + Kafka producer + gRPC client(调 iam)
|
||||
- **CDC 消费者**:独立后台任务(lifespan 启动),与 HTTP/gRPC 入口解耦
|
||||
|
||||
## 2. 领域模型
|
||||
|
||||
data-ana 是**纯读模型服务**,不持有写聚合根。领域模型为**视图聚合**(ClickHouse 物化):
|
||||
|
||||
### 聚合根(视图型)
|
||||
|
||||
| 聚合根 | 含义 | 物化载体 | 不变式 |
|
||||
| ------------------ | ---------------- | ----------------------------------------- | ------------------------------------------------------------- |
|
||||
| `StudentDashboard` | 学生学情宽表 | ClickHouse `student_dashboard_view` | 同一 (student_id, exam_id, knowledge_point_id) 仅保留最新版本 |
|
||||
| `ClassPerformance` | 班级成绩聚合 | ClickHouse 即时聚合(不物化) | 聚合维度为 class_id + 时间窗 |
|
||||
| `StudentErrorBook` | 学生错题本 | ClickHouse `student_errors` | 同一 (student_id, question_id) 累计 error_count |
|
||||
| `MasterySnapshot` | 知识点掌握度快照 | ClickHouse `mastery_snapshot`(**新增**) | 同一 (student_id, knowledge_point_id) 保留历史版本 |
|
||||
|
||||
### 值对象
|
||||
|
||||
- `WeakPoint`:knowledge_point_id + title + mastery_level
|
||||
- `TrendPoint`:date + score
|
||||
- `DataScope`:level (SELF/CLASS/GRADE/SCHOOL/DISTRICT/ALL) + scope_ids(具体可见的 class_id / grade_id 列表)
|
||||
|
||||
### 聚合间通信
|
||||
|
||||
- 同服务内:直接函数调用(Application Service → Repository)
|
||||
- 跨服务:仅通过 Kafka 事件(发布 `mastery.updated`)+ gRPC(调 iam 查 DataScope)
|
||||
|
||||
## 3. 数据模型(ClickHouse DDL)
|
||||
|
||||
> DDL 文件由 coord 统一管理在 `infra/clickhouse/ddl/`(待 coord 建立),data-ana 提供内容。
|
||||
|
||||
### 3.1 宽表 `student_dashboard_view`
|
||||
|
||||
```sql
|
||||
-- 学生学情宽表:每次成绩写入产生一行,按 ORDER BY 去重保留最新版本
|
||||
CREATE TABLE IF NOT EXISTS student_dashboard_view
|
||||
(
|
||||
student_id String,
|
||||
class_id String,
|
||||
exam_id String,
|
||||
subject_id String,
|
||||
score Float64,
|
||||
rank_in_class UInt32,
|
||||
knowledge_point_id String,
|
||||
mastery_level Float32, -- 0.0-1.0
|
||||
error_count UInt32,
|
||||
last_updated DateTime64(3, 'UTC')
|
||||
)
|
||||
ENGINE = ReplacingMergeTree(last_updated)
|
||||
PARTITION BY toYYYYMM(last_updated)
|
||||
ORDER BY (student_id, exam_id, knowledge_point_id)
|
||||
SETTINGS index_granularity = 8192;
|
||||
```
|
||||
|
||||
**索引策略**:
|
||||
|
||||
- ORDER BY `(student_id, exam_id, knowledge_point_id)`:主键索引,支持按学生查学情、按考试查成绩、按知识点查掌握度
|
||||
- PARTITION BY `toYYYYMM(last_updated)`:按月分区,支持历史数据归档
|
||||
- ReplacingMergeTree(last_updated):同 ORDER BY 自动去重,保留 last_updated 最大版本(幂等消费保证)
|
||||
|
||||
### 3.2 错题本 `student_errors`
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS student_errors
|
||||
(
|
||||
student_id String,
|
||||
question_id String,
|
||||
knowledge_point_id String,
|
||||
error_count UInt32,
|
||||
last_error_time DateTime64(3, 'UTC'),
|
||||
content String
|
||||
)
|
||||
ENGINE = ReplacingMergeTree(last_error_time)
|
||||
PARTITION BY toYYYYMM(last_error_time)
|
||||
ORDER BY (student_id, question_id);
|
||||
```
|
||||
|
||||
### 3.3 掌握度快照 `mastery_snapshot`(**新增**)
|
||||
|
||||
```sql
|
||||
-- 知识点掌握度历史快照:每次掌握度计算产生新版本,支持趋势查询
|
||||
CREATE TABLE IF NOT EXISTS mastery_snapshot
|
||||
(
|
||||
student_id String,
|
||||
knowledge_point_id String,
|
||||
subject_id String,
|
||||
mastery_level Float32,
|
||||
calculated_at DateTime64(3, 'UTC'),
|
||||
calculation_method LowCardinality(String) -- 'weighted_moving_avg' / 'simple_avg'
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
PARTITION BY toYYYYMM(calculated_at)
|
||||
ORDER BY (student_id, knowledge_point_id, calculated_at);
|
||||
```
|
||||
|
||||
### 3.4 用量计费 `ai_usage_log`(**新增,供 ai 服务写入**)
|
||||
|
||||
```sql
|
||||
-- AI 用量记录:ai 服务通过 Kafka 事件投递,data-ana 消费落库
|
||||
CREATE TABLE IF NOT EXISTS ai_usage_log
|
||||
(
|
||||
request_id String,
|
||||
user_id String,
|
||||
provider LowCardinality(String), -- 'openai' / 'anthropic' / 'baichuan' / 'local'
|
||||
model LowCardinality(String),
|
||||
prompt_tokens UInt32,
|
||||
completion_tokens UInt32,
|
||||
total_tokens UInt32,
|
||||
latency_ms UInt32,
|
||||
success Boolean,
|
||||
occurred_at DateTime64(3, 'UTC')
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
PARTITION BY toYYYYMM(occurred_at)
|
||||
ORDER BY (user_id, occurred_at);
|
||||
```
|
||||
|
||||
### 3.5 读写分离策略
|
||||
|
||||
| 操作 | 路径 | 说明 |
|
||||
| -------------- | ----------------------------------- | ------------------------------------------ |
|
||||
| 学情查询 | ClickHouse 宽表 | 实时聚合,亚秒级响应 |
|
||||
| 错题本查询 | ClickHouse student_errors | 实时查询 |
|
||||
| 掌握度趋势 | ClickHouse mastery_snapshot | 历史快照 |
|
||||
| 掌握度计算 | CDC 触发 → 内存计算 → 写 ClickHouse | 派生数据,非事务写 |
|
||||
| DataScope 解析 | gRPC 调 iam | 实时查询,结果 Redis 缓存 5min(004 §6.3) |
|
||||
|
||||
## 4. API 设计
|
||||
|
||||
### 4.1 HTTP 端点(保留作 Gateway 直连降级)
|
||||
|
||||
| method | path | 权限 | 请求 | 响应 | 说明 |
|
||||
| ------ | ------------------------------------------- | ----------------------------- | ------------------------------------------------ | ----------------------------------------------------- | -------------------------------- |
|
||||
| GET | `/healthz` | — | — | `{status, service}` | liveness |
|
||||
| GET | `/readyz` | — | — | `{status, ready, degraded, clickhouse, cdc_consumer}` | readiness |
|
||||
| GET | `/metrics` | — | — | Prometheus 格式 | 指标 |
|
||||
| GET | `/analytics/class/{class_id}/performance` | `ANALYTICS_CLASS_READ` | query: `subject_id?`, `start_date?`, `end_date?` | `ClassPerformanceResponse` | 班级成绩分析 |
|
||||
| GET | `/analytics/student/{student_id}/weakness` | `ANALYTICS_STUDENT_READ` | query: `subject_id?` | `StudentWeaknessResponse` | 学生薄弱知识点(DataScope 过滤) |
|
||||
| GET | `/analytics/student/{student_id}/errorbook` | `ANALYTICS_STUDENT_READ` | query: `page?`, `size?` | `StudentErrorBookResponse` | 学生错题本(DataScope 过滤) |
|
||||
| GET | `/analytics/student/{student_id}/trend` | `ANALYTICS_STUDENT_READ` | query: `start_date`, `end_date`, `subject_id?` | `LearningTrendResponse` | 学习趋势(**新增**) |
|
||||
| GET | `/analytics/dashboard/teacher/{user_id}` | `ANALYTICS_TEACHER_DASHBOARD` | query: `class_id?` | `TeacherDashboardResponse` | 教师仪表盘聚合(**新增**) |
|
||||
|
||||
### 4.2 gRPC 契约(analytics.proto,待实现 server)
|
||||
|
||||
| RPC | 请求 | 响应 | 权限 |
|
||||
| --------------------- | ------------------------------------------------------------------------ | ------------------ | ------------------------ |
|
||||
| `GetClassPerformance` | `GetClassPerformanceRequest{class_id, subject_id, start_date, end_date}` | `ClassPerformance` | `ANALYTICS_CLASS_READ` |
|
||||
| `GetStudentWeakness` | `GetStudentWeaknessRequest{student_id, subject_id}` | `StudentWeakness` | `ANALYTICS_STUDENT_READ` |
|
||||
| `GetLearningTrend` | `GetLearningTrendRequest{student_id, start_date, end_date}` | `LearningTrend` | `ANALYTICS_STUDENT_READ` |
|
||||
|
||||
**权限校验**:gRPC server interceptor 从 metadata 提取 `x-user-id` / `x-user-roles` / `x-data-scope`,调用 `AuthDepends` 等价逻辑。
|
||||
|
||||
### 4.3 Pydantic 请求/响应模型
|
||||
|
||||
```python
|
||||
# 示例:班级成绩分析响应
|
||||
class ClassPerformanceResponse(BaseModel):
|
||||
success: bool
|
||||
data: ClassPerformanceData
|
||||
degraded: bool = False
|
||||
|
||||
class ClassPerformanceData(BaseModel):
|
||||
class_id: str
|
||||
average_score: float
|
||||
pass_rate: float
|
||||
total_students: int
|
||||
scores: list[StudentScore] = [] # 详细成绩列表(受 DataScope 过滤)
|
||||
|
||||
class StudentScore(BaseModel):
|
||||
student_id: str
|
||||
score: float
|
||||
grade: str
|
||||
```
|
||||
|
||||
## 5. 事件设计
|
||||
|
||||
### 5.1 消费的事件
|
||||
|
||||
| Topic | 来源 | 消息格式 | 消费动作 |
|
||||
| ------------------------------------------------------ | ------------------------------------------------- | --------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| `edu-cdc.next_edu_cloud.core_edu_grades` | Debezium CDC(core-edu MySQL) | Debezium JSON(before/after/source/op/ts_ms) | 解析 → 查 ExamCache 填 class_id → 计算掌握度 → upsert student_dashboard_view |
|
||||
| `edu-cdc.next_edu_cloud.core_edu_exams` | Debezium CDC | Debezium JSON | upsert ExamCache(exam_id → class_id, subject_id) |
|
||||
| `edu-cdc.next_edu_cloud.core_edu_homework_submissions` | Debezium CDC(**新增订阅**) | Debezium JSON | 记录作业提交行为 → 更新 student_dashboard_view |
|
||||
| `edu-cdc.next_edu_cloud.classes` | Debezium CDC | Debezium JSON | 同步班级维度(head_teacher_id)用于教师 DataScope |
|
||||
| `edu-cdc.next_edu_cloud.iam_users` | Debezium CDC(**新增订阅**) | Debezium JSON | 同步用户 dataScope 用于查询过滤(避免每次查 iam) |
|
||||
| `edu.insight.ai.usage` | ai 服务 Kafka producer(**待 coord 新增 topic**) | JSON(UsageRecord) | 落 ai_usage_log 表 |
|
||||
|
||||
**幂等性**:
|
||||
|
||||
- CDC 事件:依赖 ClickHouse `ReplacingMergeTree(last_updated)` 引擎按 ORDER BY 去重
|
||||
- 领域事件(若消费):基于 `event_id` 去重(Redis SETNX,TTL 7 天)
|
||||
|
||||
### 5.2 发布的事件
|
||||
|
||||
| 事件 | Topic | 触发时机 | 消费者 | Payload |
|
||||
| ---------------- | ----------------------------- | ----------------------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------- |
|
||||
| `MasteryUpdated` | `edu.insight.mastery.updated` | 掌握度计算完成(CDC grades 事件触发后异步计算) | core-edu(推荐个性化练习)、msg(掌握度预警) | `{event_id, student_id, knowledge_point_id, mastery_level, calculated_at}` |
|
||||
|
||||
**发布实现**(Python 无 Outbox 模式):
|
||||
|
||||
- 掌握度计算是**派生数据**(非业务事务写),不需要 Outbox 保证事务一致
|
||||
- 直接用 `aiokafka.AIOKafkaProducer` 发布,`idempotent=true` + 事务性 producer
|
||||
- 失败重试 3 次,仍失败记录日志 + 落 `mastery_publish_failed` 本地表(待 P6 评估是否引入 Outbox)
|
||||
|
||||
## 6. 横切关注点对齐清单
|
||||
|
||||
### 6.1 权限装饰器等价物(FastAPI Depends)
|
||||
|
||||
```python
|
||||
# 权限点常量(与 iam 权限点对齐)
|
||||
class Permissions:
|
||||
ANALYTICS_CLASS_READ = "analytics:class:read"
|
||||
ANALYTICS_STUDENT_READ = "analytics:student:read"
|
||||
ANALYTICS_TEACHER_DASHBOARD = "analytics:teacher:dashboard"
|
||||
|
||||
async def require_permission(permission: str) -> UserContext:
|
||||
"""FastAPI Depends 权限校验.
|
||||
|
||||
从 x-user-id / x-user-roles 头提取身份,校验角色是否含 permission。
|
||||
"""
|
||||
...
|
||||
|
||||
async def inject_data_scope(ctx: UserContext = Depends(require_permission(...)))-> DataScope:
|
||||
"""注入 DataScope(从 iam.getEffectiveDataScope 查询,Redis 缓存 5min)."""
|
||||
...
|
||||
```
|
||||
|
||||
### 6.2 错误码清单(前缀 `DATA_ANA_*`)
|
||||
|
||||
| 错误码 | 触发条件 | HTTP | gRPC status |
|
||||
| --------------------------------- | --------------------------------------- | ------------------- | ------------------ |
|
||||
| `DATA_ANA_UNAUTHORIZED` | 缺失 x-user-id 头或 token 无效 | 401 | UNAUTHENTICATED |
|
||||
| `DATA_ANA_FORBIDDEN` | 角色无对应权限 | 403 | PERMISSION_DENIED |
|
||||
| `DATA_ANA_DATASCOPE_VIOLATION` | 查询目标超出 DataScope 范围 | 403 | PERMISSION_DENIED |
|
||||
| `DATA_ANA_CLICKHOUSE_UNAVAILABLE` | ClickHouse 不可达(降级模式仍返回骨架) | 200 + degraded:true | OK + degraded flag |
|
||||
| `DATA_ANA_INVALID_DATE_RANGE` | start_date > end_date | 400 | INVALID_ARGUMENT |
|
||||
| `DATA_ANA_STUDENT_NOT_FOUND` | student_id 不存在 | 404 | NOT_FOUND |
|
||||
| `DATA_ANA_CLASS_NOT_FOUND` | class_id 不存在 | 404 | NOT_FOUND |
|
||||
| `DATA_ANA_INTERNAL_ERROR` | 未捕获异常 | 500 | INTERNAL |
|
||||
|
||||
### 6.3 Logger 初始化
|
||||
|
||||
- 位置:`main.py` `init_logger()`(已具备)
|
||||
- 配置:`structlog.make_filtering_bound_logger(level)` + `TimeStamper(fmt="iso")` + `ConsoleRenderer`
|
||||
- **改进**:生产环境改用 `structlog.processors.JSONRenderer()`(当前 ConsoleRenderer 适合开发)
|
||||
|
||||
### 6.4 Metrics 指标清单
|
||||
|
||||
| 指标名 | 类型 | 标签 | 描述 |
|
||||
| --------------------------------------------- | --------- | -------------------- | -------------------------- |
|
||||
| `data_ana_http_requests_total` | Counter | method, path, status | HTTP 请求总数 |
|
||||
| `data_ana_http_request_duration_seconds` | Histogram | method, path | HTTP 请求延迟 |
|
||||
| `data_ana_clickhouse_query_duration_seconds` | Histogram | query_type | ClickHouse 查询延迟 |
|
||||
| `data_ana_clickhouse_query_total` | Counter | query_type, status | ClickHouse 查询总数 |
|
||||
| `data_ana_cdc_events_consumed_total` | Counter | table, op | CDC 事件消费总数 |
|
||||
| `data_ana_cdc_event_process_duration_seconds` | Histogram | table | CDC 事件处理延迟 |
|
||||
| `data_ana_cdc_consumer_lag` | Gauge | topic, partition | CDC 消费者 lag |
|
||||
| `data_ana_mastery_calculated_total` | Counter | — | 掌握度计算次数 |
|
||||
| `data_ana_mastery_published_total` | Counter | status | mastery.updated 事件发布数 |
|
||||
| `data_ana_datascope_cache_hits_total` | Counter | — | DataScope 缓存命中 |
|
||||
|
||||
### 6.5 Tracer 初始化
|
||||
|
||||
- 位置:`main.py` `init_tracer()`(已具备)
|
||||
- endpoint:`settings.otel_endpoint` + `/v1/traces`
|
||||
- **改进**:gRPC server 注册 `grpc.aio.ServerInterceptor` 透传 W3C trace context
|
||||
|
||||
### 6.6 /healthz 检查逻辑
|
||||
|
||||
- liveness:仅进程存活(已具备)
|
||||
|
||||
### 6.7 /readyz 检查逻辑
|
||||
|
||||
```python
|
||||
async def readyz() -> dict:
|
||||
return {
|
||||
"status": "ok" if all_ready else "degraded",
|
||||
"ready": all_ready,
|
||||
"degraded": not all_ready,
|
||||
"clickhouse": "ok" | "unreachable" | "not_configured",
|
||||
"cdc_consumer": "running" | "disabled" | "failed",
|
||||
"kafka_brokers": settings.kafka_brokers or None,
|
||||
"iam_grpc": "ok" | "unreachable", # 新增:iam gRPC 连通性
|
||||
"timestamp": datetime.now(UTC).isoformat(),
|
||||
}
|
||||
```
|
||||
|
||||
### 6.8 优雅关闭顺序
|
||||
|
||||
1. HTTP server stop accepting new requests(uvicorn graceful shutdown)
|
||||
2. gRPC server graceful stop(等待在途 RPC 完成,30s 超时)
|
||||
3. CDC consumer stop(等待在途消息处理完成,commit offset)
|
||||
4. Kafka producer flush + close(确保 mastery.updated 事件已投递)
|
||||
5. ClickHouse client close
|
||||
6. iam gRPC channel close
|
||||
|
||||
**信号处理**:注册 `signal.SIGTERM` handler,触发上述顺序。
|
||||
|
||||
## 7. 与其他模块的交互点(契约清单)
|
||||
|
||||
| 方向 | 对方服务 | 协议 | 接口/事件 | 用途 |
|
||||
| ------ | ------------------------- | ----- | ------------------------------------------------------------------- | ----------------------------- |
|
||||
| 被调用 | api-gateway | HTTP | `/analytics/*` | Gateway 代理 |
|
||||
| 被调用 | teacher-bff / student-bff | gRPC | `AnalyticsService.*` | BFF 聚合查询 |
|
||||
| 被调用 | ai | gRPC | `AnalyticsService.GetStudentWeakness / GetLearningTrend` | AI 个性化出题上下文 |
|
||||
| 调用 | iam | gRPC | `IamService.GetEffectiveDataScope`(**待 proto 新增**) | DataScope 解析 |
|
||||
| 消费 | core-edu(CDC) | Kafka | `edu-cdc.next_edu_cloud.core_edu_grades/exams/homework_submissions` | 学情数据投递 |
|
||||
| 消费 | core-edu(CDC) | Kafka | `edu-cdc.next_edu_cloud.classes` | 班级维度同步 |
|
||||
| 消费 | iam(CDC) | Kafka | `edu-cdc.next_edu_cloud.iam_users` | 用户 dataScope 同步 |
|
||||
| 消费 | ai | Kafka | `edu.insight.ai.usage`(**待 coord 新增**) | AI 用量落库 |
|
||||
| 发布 | — | Kafka | `edu.insight.mastery.updated` | 掌握度更新通知 core-edu / msg |
|
||||
|
||||
## 8. 风险与假设
|
||||
|
||||
### 8.1 假设
|
||||
|
||||
- **假设 1**:iam 提供 `GetEffectiveDataScope(userId) → DataScope` gRPC API。若 iam 未提供,fallback 为:从 `x-user-roles` 头推导(admin=ALL, teacher=CLASS_TAUGHT, student=SELF),但无法支持细粒度年级/学校范围
|
||||
- **假设 2**:core-edu 的 `core_edu_homework_submissions` 表存在 binlog。若不存在,作业相关学情无法通过 CDC 获取,需 core-edu 补表或走 Outbox 事件
|
||||
- **假设 3**:ClickHouse `ReplacingMergeTree` 在查询时需 `FINAL` 关键字确保去重生效。当前查询未加 `FINAL`,可能读到重复版本。**修复**:所有查询加 `FINAL` 或使用 `argMax` 聚合
|
||||
- **假设 4**:coord 同意新增 `edu.insight.ai.usage` topic。若不同意,ai 服务的用量计费需自建记录(破坏 ai 无状态原则)
|
||||
|
||||
### 8.2 技术风险
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
| --------------------------- | ------------------------------------ | ------------------------------------------------ |
|
||||
| ClickHouse 查询延迟超 5s | 违反 P4 退出标准 | 宽表索引优化 + 物化视图预聚合 + 查询超时 3s 降级 |
|
||||
| CDC 消费者 lag 过大 | 学情数据延迟 > 5s | 监控 lag + 告警 + 水平扩展消费者(分区数提升) |
|
||||
| ExamCache 内存泄漏 | 长期运行 OOM | LRU 淘汰策略(max 10000 条)+ 定期清理过期 exam |
|
||||
| mastery.updated 事件丢失 | 下游 core-edu/msg 收不到通知 | Kafka producer `acks=all` + 本地失败表重试 |
|
||||
| iam gRPC 不可达 | DataScope 无法解析 → 查询降级为 SELF | Redis 缓存 5min + fallback SELF 范围(最保守) |
|
||||
| ClickHouse `FINAL` 查询性能 | 查询变慢 | 使用 `argMax` 替代 `FINAL`,或在写入时去重 |
|
||||
|
||||
### 8.3 未决设计决策(需 coord 仲裁)
|
||||
|
||||
1. **mastery.updated 发布是否需要 Outbox**:Python 无 Outbox 模式,建议直接 producer;但 004 §12.2 明确"事件发布:Outbox 模式 / 禁止直接 Kafka producer"。**冲突**:data-ana 是 Python 服务无 MySQL 写事务,Outbox 不适用。建议 coord 裁定:派生数据事件(非业务事务)允许直接 producer
|
||||
2. **iam GetEffectiveDataScope proto 新增**:当前 iam.proto 仅有 `GetUserInfo`,无 DataScope 解析 API。需 coord 在 shared-proto 新增 `GetEffectiveDataScope` RPC
|
||||
3. **edu.insight.ai.usage topic 新增**:004 §7.2 未列出,需 coord 确认是否新增
|
||||
|
||||
---
|
||||
|
||||
# 模块架构设计文档 — ai
|
||||
|
||||
## 1. 模块内部分层图
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Entry["入口层"]
|
||||
HTTP[FastAPI HTTP Router<br/>/ai/* + /healthz + /readyz]
|
||||
GRPC[grpc.aio Server<br/>AiService 含 StreamChat]
|
||||
end
|
||||
|
||||
subgraph Middleware["中间件层"]
|
||||
AUTH[AuthDepends<br/>校验 x-user-id / x-user-roles]
|
||||
RATE[RateLimitDepends<br/>Redis 令牌桶限流]
|
||||
TRACE[OTel + grpc interceptor]
|
||||
end
|
||||
|
||||
subgraph Service["应用服务层"]
|
||||
S1[ChatService<br/>聊天编排 + Prompt 模板渲染]
|
||||
S2[QuestionGenerationService<br/>出题工作流编排]
|
||||
S3[ExpressionOptimizationService<br/>表达优化]
|
||||
S4[LessonPreparationWorkflow<br/>备课工作流 4 步编排]
|
||||
end
|
||||
|
||||
subgraph Provider["LLM Provider 适配层"]
|
||||
P0[LLMProvider 抽象接口<br/>chat / stream_chat]
|
||||
P1[OpenAIProvider<br/>httpx 异步]
|
||||
P2[AnthropicProvider<br/>httpx 异步]
|
||||
P3[BaichuanProvider<br/>httpx 异步]
|
||||
P4[LocalOllamaProvider<br/>httpx 异步]
|
||||
end
|
||||
|
||||
subgraph Template["Prompt 模板管理"]
|
||||
T1[PromptTemplateRegistry<br/>模板注册 + 渲染]
|
||||
T2[模板存储<br/>YAML 文件 / DB]
|
||||
end
|
||||
|
||||
subgraph Client["下游 gRPC client"]
|
||||
C1[ContentClient<br/>查询知识点 / 题库]
|
||||
C2[DataAnaClient<br/>查询学情 / 薄弱点]
|
||||
end
|
||||
|
||||
subgraph Usage["用量计费"]
|
||||
U1[UsageRecorder<br/>token 消耗统计]
|
||||
U2[KafkaProducer<br/>发布 edu.insight.ai.usage]
|
||||
end
|
||||
|
||||
subgraph External["外部 / 存储"]
|
||||
LLM[LLM Provider API<br/>OpenAI/Anthropic/百川/Ollama]
|
||||
CONTENT[content:3005 gRPC]
|
||||
DATAANA[data-ana:3006 gRPC]
|
||||
KAFKA[(Kafka)]
|
||||
REDIS[(Redis<br/>限流 + 缓存)]
|
||||
end
|
||||
|
||||
HTTP --> AUTH --> RATE --> S1
|
||||
HTTP --> S2
|
||||
HTTP --> S3
|
||||
GRPC --> S1
|
||||
GRPC --> S2
|
||||
S2 --> S4
|
||||
S4 --> C1
|
||||
S4 --> C2
|
||||
S1 --> T1
|
||||
S2 --> T1
|
||||
S1 --> P0
|
||||
S2 --> P0
|
||||
P0 --> P1
|
||||
P0 --> P2
|
||||
P0 --> P3
|
||||
P0 --> P4
|
||||
P1 --> LLM
|
||||
P2 --> LLM
|
||||
P3 --> LLM
|
||||
P4 --> LLM
|
||||
S1 --> U1
|
||||
S2 --> U1
|
||||
U1 --> U2
|
||||
U2 --> KAFKA
|
||||
RATE --> REDIS
|
||||
C1 --> CONTENT
|
||||
C2 --> DATAANA
|
||||
```
|
||||
|
||||
**分层规则**:
|
||||
|
||||
- **入口层**:HTTP(保留作 Gateway 直连)+ gRPC(主入口,含 `StreamChat` 流式 RPC)
|
||||
- **中间件层**:Auth + RateLimit(Redis 令牌桶,按 user_id 限流)
|
||||
- **应用服务层**:4 个 Service,每个对应一类 AI 能力
|
||||
- **Provider 适配层**:抽象 `LLMProvider` 接口,多适配器实现(策略模式)
|
||||
- **Prompt 模板**:模板注册 + 渲染,模板存储可配置(YAML 文件 or DB)
|
||||
- **下游 client**:gRPC 调 content / data-ana
|
||||
- **用量计费**:token 消耗统计 + Kafka 事件外发
|
||||
|
||||
## 2. 领域模型
|
||||
|
||||
ai 是**无状态服务**,不持有持久化聚合根。领域模型为**请求/响应模型 + 工作流编排**:
|
||||
|
||||
### 聚合根(请求型,无持久化)
|
||||
|
||||
| 聚合根 | 含义 | 生命周期 |
|
||||
| ---------------------------- | ------------ | ------------------------------------------------------------ |
|
||||
| `ChatConversation` | 单次聊天请求 | 单次请求 |
|
||||
| `QuestionGenerationTask` | 出题任务 | 单次请求(备课工作流中多步) |
|
||||
| `ExpressionOptimizationTask` | 表达优化任务 | 单次请求 |
|
||||
| `LessonPreparationWorkflow` | 备课工作流 | 跨多步(分析学情 → 推荐知识点 → 生成题目 → 教师审核 → 入库) |
|
||||
|
||||
### 值对象
|
||||
|
||||
- `ChatMessage`:role + content
|
||||
- `Usage`:prompt_tokens + completion_tokens + total_tokens
|
||||
- `GeneratedQuestion`:question + answer + explanation
|
||||
- `PromptTemplate`:name + system_prompt + user_template + variables
|
||||
|
||||
### 工作流编排(备课)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant T as 教师
|
||||
participant BFF as teacher-bff
|
||||
participant AI as ai 服务
|
||||
participant Content as content
|
||||
participant DA as data-ana
|
||||
|
||||
T->>BFF: 请求备课(class_id, subject_id)
|
||||
BFF->>AI: gRPC GenerateLessonPlan
|
||||
AI->>DA: gRPC GetStudentWeakness(class_id)
|
||||
DA-->>AI: 薄弱知识点列表
|
||||
AI->>Content: gRPC GetPrerequisites(knowledge_point_id)
|
||||
Content-->>AI: 前置依赖知识点
|
||||
AI->>AI: LLM 生成题目(基于学情 + 知识点)
|
||||
AI-->>BFF: 题目列表 + 推荐理由
|
||||
BFF-->>T: 题目供审核
|
||||
T->>BFF: 确认入库
|
||||
BFF->>Content: gRPC CreateQuestions
|
||||
Content-->>BFF: 入库成功
|
||||
```
|
||||
|
||||
**工作流实现**:
|
||||
|
||||
- **简单场景**(4 步内):FastAPI BackgroundTasks + asyncio.gather 并行查询
|
||||
- **复杂场景**(含教师审核等待):待 coord 仲裁是否引入 Temporal(004 §2.3 列出 Temporal 用于 AI 编排)
|
||||
|
||||
## 3. 数据模型
|
||||
|
||||
ai 服务**无独占数据库**,无 MySQL schema。所有数据通过 Kafka 事件外发(用量计费)或 gRPC 查询下游。
|
||||
|
||||
### 用量计费(Kafka 事件 → data-ana 落 ClickHouse)
|
||||
|
||||
```json
|
||||
{
|
||||
"event_id": "uuid",
|
||||
"user_id": "user-xxx",
|
||||
"request_id": "req-xxx",
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini",
|
||||
"prompt_tokens": 150,
|
||||
"completion_tokens": 80,
|
||||
"total_tokens": 230,
|
||||
"latency_ms": 1200,
|
||||
"success": true,
|
||||
"occurred_at": "2026-07-09T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Topic**:`edu.insight.ai.usage`(**待 coord 新增**)
|
||||
|
||||
### 缓存策略
|
||||
|
||||
| 数据 | 存储 | TTL | 失效策略 |
|
||||
| -------------------------------------- | ----------------------------- | -------- | ---------------------- |
|
||||
| Prompt 模板 | Redis(模板变更事件驱动失效) | 1 小时 | 文件/DB 变更时主动失效 |
|
||||
| LLM 响应(相同 prompt) | Redis(hash 缓存) | 30 分钟 | 短 TTL,避免陈旧 |
|
||||
| DataScope(ai 不需要,仅 data-ana 用) | — | — | — |
|
||||
| 限流计数 | Redis 令牌桶 | 滑动窗口 | 自动过期 |
|
||||
|
||||
## 4. API 设计
|
||||
|
||||
### 4.1 HTTP 端点(保留作 Gateway 直连降级)
|
||||
|
||||
| method | path | 权限 | 请求体 | 响应 | 说明 |
|
||||
| ------ | ------------------------- | ------------------------ | --------------------------- | ------------------------------------ | ---------------------- |
|
||||
| GET | `/healthz` | — | — | `{status, service}` | liveness |
|
||||
| GET | `/readyz` | — | — | `{status, llm_configured, degraded}` | readiness |
|
||||
| GET | `/metrics` | — | — | Prometheus | 指标 |
|
||||
| POST | `/ai/chat` | `AI_CHAT` | `ChatRequest` | `ChatResponse` | LLM 聊天 |
|
||||
| POST | `/ai/chat/stream` | `AI_CHAT` | `ChatRequest` | SSE stream | 流式聊天 |
|
||||
| POST | `/ai/generate/question` | `AI_QUESTION_GENERATE` | `GenerateQuestionRequest` | `GeneratedQuestionResponse` | 生成题目 |
|
||||
| POST | `/ai/optimize/expression` | `AI_EXPRESSION_OPTIMIZE` | `OptimizeExpressionRequest` | `OptimizedExpressionResponse` | 优化表达 |
|
||||
| POST | `/ai/lesson/preparation` | `AI_LESSON_PREPARE` | `LessonPreparationRequest` | `LessonPreparationResponse` | 备课工作流(**新增**) |
|
||||
|
||||
### 4.2 gRPC 契约(ai.proto,待实现 server)
|
||||
|
||||
| RPC | 请求 | 响应 | 权限 | 说明 |
|
||||
| -------------------- | ------------------------------------------------------ | ------------------------------------- | ------------------------ | ------------------------- |
|
||||
| `Chat` | `ChatRequest{messages, model, temperature}` | `ChatResponse{content, model, usage}` | `AI_CHAT` | 非流式聊天 |
|
||||
| `StreamChat` | `ChatRequest` | `stream ChatChunk` | `AI_CHAT` | 流式聊天(SSE over gRPC) |
|
||||
| `GenerateQuestion` | `GenerateQuestionRequest{prompt, subject, difficulty}` | `GeneratedQuestion` | `AI_QUESTION_GENERATE` | 生成题目 |
|
||||
| `OptimizeExpression` | `OptimizeExpressionRequest{text, context}` | `OptimizedExpression` | `AI_EXPRESSION_OPTIMIZE` | 优化表达 |
|
||||
|
||||
### 4.3 Pydantic 请求/响应模型
|
||||
|
||||
```python
|
||||
class ChatRequest(BaseModel):
|
||||
messages: list[ChatMessage]
|
||||
model: str = "gpt-4o-mini"
|
||||
temperature: float = Field(0.7, ge=0.0, le=2.0)
|
||||
stream: bool = False
|
||||
|
||||
class ChatMessage(BaseModel):
|
||||
role: Literal["system", "user", "assistant"]
|
||||
content: str
|
||||
|
||||
class ChatResponse(BaseModel):
|
||||
success: bool
|
||||
data: ChatData
|
||||
degraded: bool = False
|
||||
|
||||
class ChatData(BaseModel):
|
||||
content: str
|
||||
model: str
|
||||
usage: Usage
|
||||
|
||||
class Usage(BaseModel):
|
||||
prompt_tokens: int
|
||||
completion_tokens: int
|
||||
total_tokens: int
|
||||
|
||||
class GenerateQuestionRequest(BaseModel):
|
||||
prompt: str = Field(..., min_length=1, max_length=2000)
|
||||
subject: str
|
||||
difficulty: Literal["easy", "medium", "hard"]
|
||||
knowledge_point_ids: list[str] = [] # 可选:靶向知识点
|
||||
|
||||
class GeneratedQuestionResponse(BaseModel):
|
||||
success: bool
|
||||
data: GeneratedQuestionData
|
||||
degraded: bool = False
|
||||
```
|
||||
|
||||
## 5. 事件设计
|
||||
|
||||
### 5.1 消费的事件
|
||||
|
||||
ai 服务**不消费任何事件**(无状态,纯请求-响应)。
|
||||
|
||||
### 5.2 发布的事件
|
||||
|
||||
| 事件 | Topic | 触发时机 | 消费者 | Payload |
|
||||
| ----------------- | ------------------------------------------- | ----------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `AIUsageRecorded` | `edu.insight.ai.usage`(**待 coord 新增**) | 每次 LLM 调用完成 | data-ana(落 ClickHouse ai_usage_log) | `{event_id, user_id, request_id, provider, model, prompt_tokens, completion_tokens, total_tokens, latency_ms, success, occurred_at}` |
|
||||
|
||||
**发布实现**:
|
||||
|
||||
- 每次 LLM 调用后异步发布(不阻塞响应)
|
||||
- `aiokafka.AIOKafkaProducer` + `acks=all`
|
||||
- 失败重试 3 次,仍失败记录日志(不影响主流程)
|
||||
|
||||
## 6. 横切关注点对齐清单
|
||||
|
||||
### 6.1 权限装饰器等价物
|
||||
|
||||
```python
|
||||
class Permissions:
|
||||
AI_CHAT = "ai:chat"
|
||||
AI_QUESTION_GENERATE = "ai:question:generate"
|
||||
AI_EXPRESSION_OPTIMIZE = "ai:expression:optimize"
|
||||
AI_LESSON_PREPARE = "ai:lesson:prepare"
|
||||
|
||||
async def require_permission(permission: str) -> UserContext:
|
||||
"""从 x-user-id / x-user-roles 校验权限."""
|
||||
...
|
||||
```
|
||||
|
||||
### 6.2 错误码清单(前缀 `AI_*`)
|
||||
|
||||
| 错误码 | 触发条件 | HTTP | gRPC status |
|
||||
| --------------------------- | ----------------------------------------- | ------------------- | ------------------ |
|
||||
| `AI_UNAUTHORIZED` | 缺失 x-user-id 或 token 无效 | 401 | UNAUTHENTICATED |
|
||||
| `AI_FORBIDDEN` | 角色无对应权限 | 403 | PERMISSION_DENIED |
|
||||
| `AI_RATE_LIMITED` | 触发限流 | 429 | RESOURCE_EXHAUSTED |
|
||||
| `AI_LLM_UNAVAILABLE` | LLM Provider 不可达(降级模式仍返回骨架) | 200 + degraded:true | OK + degraded flag |
|
||||
| `AI_LLM_TIMEOUT` | LLM 调用超时(30s) | 504 | DEADLINE_EXCEEDED |
|
||||
| `AI_INVALID_MODEL` | model 名不支持 | 400 | INVALID_ARGUMENT |
|
||||
| `AI_INVALID_DIFFICULTY` | difficulty 不在 easy/medium/hard | 400 | INVALID_ARGUMENT |
|
||||
| `AI_DOWNSTREAM_UNAVAILABLE` | content / data-ana gRPC 不可达 | 502 | UNAVAILABLE |
|
||||
| `AI_PROMPT_RENDER_FAILED` | Prompt 模板渲染失败(变量缺失) | 500 | INTERNAL |
|
||||
| `AI_INTERNAL_ERROR` | 未捕获异常 | 500 | INTERNAL |
|
||||
|
||||
### 6.3 Logger 初始化
|
||||
|
||||
- 位置:`main.py`(已具备)
|
||||
- **改进**:生产环境改用 `JSONRenderer`
|
||||
|
||||
### 6.4 Metrics 指标清单
|
||||
|
||||
| 指标名 | 类型 | 标签 | 描述 |
|
||||
| ---------------------------------- | --------- | ----------------------------------------- | ------------------ |
|
||||
| `ai_http_requests_total` | Counter | method, path, status | HTTP 请求总数 |
|
||||
| `ai_http_request_duration_seconds` | Histogram | method, path | HTTP 请求延迟 |
|
||||
| `ai_llm_calls_total` | Counter | provider, model, status | LLM 调用总数 |
|
||||
| `ai_llm_call_duration_seconds` | Histogram | provider, model | LLM 调用延迟 |
|
||||
| `ai_llm_tokens_total` | Counter | provider, model, type (prompt/completion) | token 消耗总数 |
|
||||
| `ai_llm_stream_chunks_total` | Counter | provider, model | 流式 chunk 总数 |
|
||||
| `ai_grpc_calls_total` | Counter | downstream, method, status | 下游 gRPC 调用总数 |
|
||||
| `ai_rate_limit_hits_total` | Counter | user_id | 限流命中次数 |
|
||||
| `ai_usage_events_published_total` | Counter | status | 用量事件发布数 |
|
||||
| `ai_prompt_template_renders_total` | Counter | template_name, status | 模板渲染次数 |
|
||||
|
||||
### 6.5 Tracer 初始化
|
||||
|
||||
- 位置:`main.py` `init_tracer()`(已具备,dev_mode 跳过)
|
||||
- **改进**:gRPC server interceptor + 下游 gRPC client interceptor 透传 trace context
|
||||
|
||||
### 6.6 /healthz 检查逻辑
|
||||
|
||||
- liveness:仅进程存活(已具备)
|
||||
|
||||
### 6.7 /readyz 检查逻辑
|
||||
|
||||
```python
|
||||
async def readyz() -> dict:
|
||||
return {
|
||||
"status": "ok",
|
||||
"service": "ai",
|
||||
"llm_configured": settings.llm_available,
|
||||
"degraded": not settings.llm_available,
|
||||
"providers": {
|
||||
"openai": bool(settings.openai_api_key),
|
||||
"anthropic": bool(settings.anthropic_api_key),
|
||||
"baichuan": bool(settings.baichuan_api_key),
|
||||
"local_ollama": bool(settings.ollama_base_url),
|
||||
},
|
||||
"downstream_grpc": {
|
||||
"content": "ok" | "unreachable", # 新增
|
||||
"data_ana": "ok" | "unreachable", # 新增
|
||||
},
|
||||
"redis": "ok" | "unreachable", # 新增(限流依赖)
|
||||
}
|
||||
```
|
||||
|
||||
### 6.8 优雅关闭顺序
|
||||
|
||||
1. HTTP server stop accepting new requests
|
||||
2. gRPC server graceful stop(**关键**:等待在途 `StreamChat` 流式 RPC 完成,60s 超时,避免截断用户响应)
|
||||
3. LLM 流式请求 drain(等待 httpx stream 完成)
|
||||
4. Kafka producer flush + close(确保用量事件已投递)
|
||||
5. 下游 gRPC channels close(content / data-ana)
|
||||
6. Redis connection close
|
||||
|
||||
## 7. 与其他模块的交互点(契约清单)
|
||||
|
||||
| 方向 | 对方服务 | 协议 | 接口/事件 | 用途 |
|
||||
| ------ | ------------ | ----- | ---------------------------------------------------------- | ------------------ |
|
||||
| 被调用 | api-gateway | HTTP | `/ai/*` | Gateway 代理 |
|
||||
| 被调用 | teacher-bff | gRPC | `AiService.*` | BFF 聚合 AI 能力 |
|
||||
| 调用 | content | gRPC | `KnowledgeGraphService.GetPrerequisites / GetLearningPath` | 出题上下文查询 |
|
||||
| 调用 | content | gRPC | `TextbookService.*`(若需教材上下文) | 出题教材关联 |
|
||||
| 调用 | data-ana | gRPC | `AnalyticsService.GetStudentWeakness / GetLearningTrend` | 个性化出题学情查询 |
|
||||
| 调用 | LLM Provider | HTTP | OpenAI 兼容 REST `/chat/completions` | LLM 推理 |
|
||||
| 发布 | — | Kafka | `edu.insight.ai.usage`(**待 coord 新增**) | 用量计费外发 |
|
||||
|
||||
## 8. 风险与假设
|
||||
|
||||
### 8.1 假设
|
||||
|
||||
- **假设 1**:content 服务实现了 `KnowledgeGraphService` gRPC server。当前 content.proto 已定义但未实现 gRPC server(ai05 阶段 2 设计中)。ai 调用前需确认 content gRPC 可用
|
||||
- **假设 2**:data-ana 实现了 `AnalyticsService` gRPC server(本设计文档已设计)。ai 调用前需确认 data-ana gRPC 可用
|
||||
- **假设 3**:coord 同意新增 `edu.insight.ai.usage` topic。若不同意,用量计费降级为 ai 本地日志(不落 ClickHouse,影响成本分析)
|
||||
- **假设 4**:Redis 可用(限流依赖)。若 Redis 不可用,限流降级为"无限制"(风险:LLM 成本失控),或降级为内存令牌桶(单实例有效,多实例不一致)
|
||||
|
||||
### 8.2 技术风险
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
| ---------------------- | ---------------- | --------------------------------------------------------------- |
|
||||
| LLM 调用延迟高(>30s) | 用户体验差 | 超时 30s + 降级骨架响应 + 流式优先(用户感知首字延迟) |
|
||||
| LLM 成本失控 | 财务风险 | Redis 令牌桶限流(每用户每分钟 10 次)+ 用量计费监控 + 告警阈值 |
|
||||
| LLM Provider 单点故障 | 服务不可用 | 多 Provider 适配器 + 自动 failover(OpenAI 失败切 Anthropic) |
|
||||
| 流式 RPC 中断 | 用户响应截断 | gRPC server graceful shutdown 60s drain + 客户端重连机制 |
|
||||
| Prompt 注入攻击 | LLM 输出恶意内容 | 输入 sanitize + system prompt 加安全约束 + 输出过滤 |
|
||||
| 下游 gRPC 不可达 | 备课工作流失败 | 降级:跳过学情查询,仅基于 prompt 生成题目 + `degraded: true` |
|
||||
|
||||
### 8.3 未决设计决策(需 coord 仲裁)
|
||||
|
||||
1. **备课工作流是否引入 Temporal**:004 §2.3 列出 Temporal 用于 AI 编排,但简单 4 步工作流可用 FastAPI BackgroundTasks。建议:M14 用 BackgroundTasks,M15 评估是否迁移 Temporal
|
||||
2. **`edu.insight.ai.usage` topic 新增**:需 coord 在 shared-proto events.proto 新增 `AIUsageEvent` message + 004 §7.2 新增 topic
|
||||
3. **LLM Provider 配置化路由**:是否在 shared-py 建立通用 `LLMProvider` 抽象(供未来其他 Python 服务复用)。建议 P5 阶段在 ai 服务内部实现,P6 评估是否提取到 shared-py
|
||||
4. **Prompt 模板存储位置**:YAML 文件(简单,无 DB)vs DB(动态更新)。建议 P5 用 YAML 文件(`services/ai/src/ai/prompts/*.yaml`),P6 评估迁移 DB
|
||||
|
||||
---
|
||||
|
||||
# 阶段 2 总结
|
||||
|
||||
ai06 已完成阶段 2 模块架构设计,产出 data-ana 与 ai 两份设计文档。核心设计决策:
|
||||
|
||||
## data-ana 设计要点
|
||||
|
||||
1. **分层**:HTTP + gRPC 双入口共享 Application Service;CDC 消费者独立后台任务
|
||||
2. **数据模型**:4 张 ClickHouse 宽表(student_dashboard_view / student_errors / mastery_snapshot / ai_usage_log),ReplacingMergeTree 引擎保证幂等
|
||||
3. **权限**:FastAPI Depends 链(require_permission + inject_data_scope),DataScope WHERE 注入 ClickHouse 查询
|
||||
4. **事件**:消费 6 个 CDC topic + 发布 `edu.insight.mastery.updated`(直接 producer,非 Outbox,因派生数据非事务写)
|
||||
5. **gRPC**:实现 `AnalyticsService` server(analytics.proto 已定义)
|
||||
6. **降级**:ClickHouse 不可达返回骨架 + degraded:true;iam gRPC 不可达降级为 SELF DataScope
|
||||
|
||||
## ai 设计要点
|
||||
|
||||
1. **分层**:HTTP + gRPC 双入口;LLM Provider 适配层(策略模式,4 适配器)
|
||||
2. **无状态**:无 DB,用量计费通过 Kafka 事件外发
|
||||
3. **权限**:FastAPI Depends + Redis 令牌桶限流(每用户每分钟 10 次)
|
||||
4. **工作流**:备课 4 步编排(学情查询 → 知识点推荐 → 题目生成 → 教师审核入库),简单场景用 BackgroundTasks
|
||||
5. **gRPC**:实现 `AiService` server(含 `StreamChat` 流式 RPC);gRPC client 调 content / data-ana
|
||||
6. **降级**:LLM 不可达返回骨架 + degraded:true;下游 gRPC 不可达降级跳过
|
||||
|
||||
## 待 coord 交叉审查项(汇总)
|
||||
|
||||
| # | 议题 | 涉及文档 |
|
||||
| --- | ------------------------------------------------------------------------------------ | ----------------------- |
|
||||
| 1 | data-ana 发布 `edu.insight.mastery.updated` 用直接 producer(非 Outbox)是否合规 | 004 §12.2 |
|
||||
| 2 | 新增 `edu.insight.ai.usage` topic + `AIUsageEvent` proto message | 004 §7.2 + events.proto |
|
||||
| 3 | iam 新增 `GetEffectiveDataScope` gRPC RPC | iam.proto |
|
||||
| 4 | data-ana / ai 实现 gRPC server 决策 | 004 §4.1 |
|
||||
| 5 | ClickHouse DDL 管理位置(建议 `infra/clickhouse/ddl/`) | infra/ |
|
||||
| 6 | ai 备课工作流是否引入 Temporal | 004 §2.3 |
|
||||
| 7 | 端口冲突检查:data-ana 3006 / ai 3008(无冲突) | full-stack-runbook |
|
||||
| 8 | 错误码前缀检查:`DATA_ANA_*` / `AI_*`(与其他服务不重叠) | — |
|
||||
| 9 | 黄金模板对齐:Python 服务无 NestJS 装饰器,权限校验用 FastAPI Depends 等价物是否认可 | — |
|
||||
|
||||
下一步:等待 coord 交叉审查通过后,进入阶段 3(按图实施)。
|
||||
Reference in New Issue
Block a user