feat(teacher-bff): 完整实现 teacher-bff GraphQL 聚合层

包含 clients/graphql/middleware、health probes、shared-ts contracts 等
This commit is contained in:
SpecialX
2026-07-10 19:10:07 +08:00
parent b82593aac2
commit 99155a5ea1
37 changed files with 2862 additions and 324 deletions

View File

@@ -189,16 +189,16 @@ graph LR
**目标**:在不改变对外 REST 契约的前提下,补齐黄金模板差距,为后续 gRPC/GraphQL 切换铺路。
| 工作项 | 详情 | 优先级 |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | ------ |
| 引入 Client 抽象层 | 新增 `src/clients/` 目录,定义 `IamClient`/`ClassesClient`/`CoreEduClient` 接口,当前实现为 REST fetch adapterP3 替换为 gRPC adapter | P0 |
| 错误码迁移 | `TEACHER_BFF_*``BFF_TEACHER_*`全量替换 [application-error.ts](../src/shared/errors/application-error.ts) | P0 |
| Zod 输入验证 | Controller 层引入 Zod schema 解析 path/bodyclassId/examId 等) | P0 |
| `/readyz` 下游探针 | 并行 ping iam + classes + core-edu 的 `/healthz`,全部 ok 才返回 ok | P0 |
| Vitest 测试框架 | 引入 Vitest覆盖 Service 聚合逻辑 + Controller + 错误处理,目标 ≥ 80% | P1 |
| env 重构 | `IamServiceUrl``IamServiceTarget`(兼容 REST URL 和 gRPC target为 P3 切换铺路 | P1 |
| DataLoader 骨架 | 新增 `src/dataloader/` 目录,定义 DataLoader 注册器骨架P2 不启用P4 接入 GraphQL | P2 |
| Redis 客户端引入 | 新增 `src/shared/cache/redis.client.ts`P2 仅用于会话/权限缓存P3 接入聚合缓存 | P2 |
| 工作项 | 详情 | 优先级 |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
| 引入 Client 抽象层 | 新增 `src/clients/` 目录,定义 `IamClient`/`ClassesClient`/`CoreEduClient` 接口,当前实现为 REST fetch adapterP3 替换为 gRPC adapter | P0 |
| 错误码迁移 | `TEACHER_BFF_*``BFF_TEACHER_*`已完成,见 [application-error.ts](../src/shared/errors/application-error.ts)+ 3 类越权错误码president §2.7 | P0 |
| Zod 输入验证 | Controller 层引入 Zod schema 解析 path/bodyclassId/examId 等) | P0 |
| `/readyz` 下游探针 | 并行 ping iam + classes + core-edu 的 `/healthz`,全部 ok 才返回 ok | P0 |
| Vitest 测试框架 | 引入 Vitest覆盖 Service 聚合逻辑 + Controller + 错误处理,目标 ≥ 80% | P1 |
| env 重构 | `IamServiceUrl``IamServiceTarget`(兼容 REST URL 和 gRPC target为 P3 切换铺路 | P1 |
| DataLoader 骨架 | 新增 `src/dataloader/` 目录,定义 DataLoader 注册器骨架P2 不启用P4 接入 GraphQL | P2 |
| Redis 客户端引入 | 新增 `src/shared/cache/redis.client.ts`P2 仅用于会话/权限缓存P3 接入聚合缓存 | P2 |
**P2 退出标准**lint + typecheck 零错误;测试覆盖率 ≥ 80%`/readyz` 真实探针;错误码全部 `BFF_TEACHER_*`Zod 校验全 Controller 覆盖。
@@ -228,7 +228,7 @@ graph LR
| Schema 设计 | 按教学场景域组织 Query/Mutation见 §5 |
| DataLoader 接入 | Resolver 内调用 DataLoader覆盖全部 N+1 风险点(见 §6 |
| ContentClient gRPC adapter | 调用 content:50054KnowledgeGraphService + ChapterService/QuestionService 待 ai09 补) |
| DataAnaClient gRPC adapter | 调用 data-ana:50055AnalyticsService + GetTeacherDashboardStats 待 ai11 补) |
| DataAnaClient gRPC adapter | 调用 data-ana:50055AnalyticsService + GetTeacherDashboard 待 ai11 补) |
| 双轨读支持 | data-ana 学情查询支持实时查主库 + 聚合查 ClickHouse 宽表 |
| REST 兼容期 | REST 端点保留deprecation header前端逐步迁移到 GraphQL |
| 查询复杂度限制 | GraphQL depth limit + query cost analysis防恶意查询 |
@@ -469,13 +469,13 @@ services/teacher-bff/src/
### 4.3 P3 REST 扩展
| method | path | 说明 |
| ------ | ------------------------------------ | ----------------------------------------------------------------- |
| POST | /teacher/exams | 创建考试(透传 core-edu CreateExam |
| POST | /teacher/homework | 布置作业(透传 core-edu AssignHomework |
| POST | /teacher/grades | 录入成绩(透传 core-edu RecordGrade |
| GET | /teacher/classes/:classId/attendance | 出勤列表core-edu AttendanceService待 ai08 补 proto |
| GET | /teacher/dashboard/stats | 仪表盘统计data-ana GetTeacherDashboardStats,待 ai11 补 proto |
| method | path | 说明 |
| ------ | ------------------------------------ | ------------------------------------------------------------ |
| POST | /teacher/exams | 创建考试(透传 core-edu CreateExam |
| POST | /teacher/homework | 布置作业(透传 core-edu AssignHomework |
| POST | /teacher/grades | 录入成绩(透传 core-edu RecordGrade |
| GET | /teacher/classes/:classId/attendance | 出勤列表core-edu AttendanceService待 ai08 补 proto |
| GET | /teacher/dashboard/stats | 仪表盘统计data-ana GetTeacherDashboard待 ai11 补 proto |
### 4.4 P4 GraphQL API目标态
@@ -1168,18 +1168,22 @@ BFF 是聚合层,**不发布领域事件**无业务状态变更。BFF
| /graphqlP4 | 校验 x-user-id + 注入 context | 同上 |
| /ai/chat/streamP5 | 校验 x-user-id | 同上 |
### 15.2 错误码清单(迁移为 `BFF_TEACHER_*`
### 15.2 错误码清单(`BFF_TEACHER_*` 前缀B5 + G14 裁决
| 错误码 | HTTP | 触发条件 | 当前状态 |
| ------------------------------- | ---- | ------------------------ | --------------------------------------------- |
| `BFF_TEACHER_VALIDATION_ERROR` | 400 | Zod 校验失败 | 需迁移(当前 `TEACHER_BFF_VALIDATION_ERROR` |
| `BFF_TEACHER_UNAUTHORIZED` | 401 | 缺 x-user-id | 需迁移 |
| `BFF_TEACHER_PERMISSION_DENIED` | 403 | 保留BFF 一般不触发) | 需迁移 |
| `BFF_TEACHER_NOT_FOUND` | 404 | 资源不存在 | 需迁移 |
| `BFF_TEACHER_CONFLICT` | 409 | 并发冲突 | 需迁移 |
| `BFF_TEACHER_BUSINESS_ERROR` | 422 | 业务规则违反 | 需迁移 |
| `BFF_TEACHER_BAD_GATEWAY` | 502 | 下游不可达 | 需迁移 |
| `BFF_TEACHER_INTERNAL_ERROR` | 500 | 未知异常 | 需迁移 |
> 越权防御 3 类错误码president §2.7UNAUTHORIZED / FORBIDDEN_RESOURCE / IDENTITY_MISMATCH
| 错误码 | HTTP | 触发条件 | 状态 |
| ---------------------------------- | ---- | ------------------------------------------------------ | --------- |
| `BFF_TEACHER_VALIDATION_FAILED` | 400 | Zod 校验失败 | ✅ 已实现 |
| `BFF_TEACHER_UNAUTHORIZED` | 401 | x-user-id 缺失或无效president §2.7 | ✅ 已实现 |
| `BFF_TEACHER_FORBIDDEN_RESOURCE` | 403 | teacherId 与资源无归属关系(场景 A§2.7 | ✅ 已实现 |
| `BFF_TEACHER_IDENTITY_MISMATCH` | 403 | JWT teacherId 与 body teacherId 不一致(场景 B§2.7 | ✅ 已实现 |
| `BFF_TEACHER_NOT_FOUND` | 404 | 资源不存在 | ✅ 已实现 |
| `BFF_TEACHER_CONFLICT` | 409 | 并发冲突 | ✅ 已实现 |
| `BFF_TEACHER_BUSINESS_ERROR` | 422 | 业务规则违反(含 P2 未就绪字段) | ✅ 已实现 |
| `BFF_TEACHER_UPSTREAM_UNAVAILABLE` | 502 | 下游 gRPC 不可达 | ✅ 已实现 |
| `BFF_TEACHER_AGGREGATION_FAILED` | 500 | 聚合多下游部分失败且无降级数据president §2.6 | ✅ 已实现 |
| `BFF_TEACHER_INTERNAL_ERROR` | 500 | 未知异常 | ✅ 已实现 |
**下游业务错误透传**iam 抛 `IAM_*`、core-edu 抛 `CORE_EDU_*`、classes 抛 `CLASSES_*`BFF 不改写错误码,直接透传给前端。
@@ -1269,7 +1273,7 @@ async readiness() {
| 调用 | content | gRPC 50054P4+ | KnowledgeGraphService.GetPrerequisites / GetLearningPath | 知识图谱 | P4 | ✅ 已有 |
| 调用 | content | gRPC 50054P4+ | _*ChapterService.* / QuestionService._** | 章节/题库 | P4 | ❌ 待 ai09 补 |
| 调用 | data-ana | gRPC 50055P4+ | AnalyticsService.GetClassPerformance / GetStudentWeakness / GetLearningTrend | 学情诊断 | P4 | ✅ 已有 |
| 调用 | data-ana | gRPC 50055P4+ | **GetTeacherDashboardStats**(建议新增) | 仪表盘统计 | P4 | ❌ 待 coord 仲裁 |
| 调用 | data-ana | gRPC 50055P4+ | **GetTeacherDashboard**(建议新增) | 仪表盘统计 | P4 | ❌ 待 coord 仲裁 |
| 调用 | ai | gRPC 50057P5+ | AiService.GenerateQuestion / Chat / OptimizeExpression | AI 辅助 | P5 | ✅ 已有 |
| 调用 | ai | gRPC 50057 streamingP5+ | **AiService.StreamChat** | SSE 流式对话 | P5 | ✅ 已有 |
| 调用 | msg | gRPC 50056P5+ | NotificationService.ListNotifications / SearchNotifications / MarkAsRead | 通知聚合 | P5 | ✅ 已有 |
@@ -1463,7 +1467,7 @@ Redis 单实例 256MB 足够,无需集群。
| 3 | **iam 补 4 个 RPC** | GetViewports / GetEffectivePermissions / Logout / GetPublicKey | 强烈建议 P2 补全teacher-bff 直接依赖) | P2 |
| 4 | **core-edu 补 AttendanceService** | service 级新增 | 建议 P3 补全 | P3 |
| 5 | **core-edu 补 GetClassesByTeacher RPC** | 按教师拉班级 | 建议新增(当前 ListClasses 仅按 gradeId | P3 |
| 6 | **data-ana 补 GetTeacherDashboardStats RPC** | 仪表盘统计聚合 | 建议新增 | P4 |
| 6 | **data-ana 补 GetTeacherDashboard RPC** | 仪表盘统计聚合 | 建议新增 | P4 |
| 7 | **iam 补 BatchGetUsers RPC** | DataLoader 批量化 | 建议新增(否则 DataLoader 退化为一对一) | P4 |
| 8 | **core-edu 补 BatchGetClasses RPC** | DataLoader 批量化 | 建议新增 | P4 |
| 9 | **Kafka consumer 引入时机** | A. P5 引入精确失效 / B. 全程短 TTL 兜底 | **P5 评估**(取决于权限变更频率) | P5 |