# teacher-bff 对接契约

> 负责人：ai03
> 版本：v2（2026-07-10，对齐 president §2.7/§2.17/§5.1 + coord B5/B8 + port-allocation §5）
> 关联：[matrix.md](../matrix.md)、[iam.proto](../../../../packages/shared-proto/proto/iam.proto)、[core_edu.proto](../../../../packages/shared-proto/proto/core_edu.proto)、[content.proto](../../../../packages/shared-proto/proto/content.proto)、[analytics.proto](../../../../packages/shared-proto/proto/analytics.proto)、[msg.proto](../../../../packages/shared-proto/proto/msg.proto)、[ai.proto](../../../../packages/shared-proto/proto/ai.proto)、[coord-final-decisions.md §2 B1-B8](../../coord-final-decisions.md)、[president-final-rulings.md §2.7/§2.17/§5.1](../../president-final-rulings.md)

---

## §1 我提供什么（对外接口）

### 1.1 gRPC 接口（如有）

无对外 gRPC。teacher-bff 是 GraphQL 聚合层（B1 裁决：P2 起直接 GraphQL）。

### 1.2 HTTP 端点（如有）

| Method | Path     | 用途                                      | 认证                    | 裁决依据 |
| ------ | -------- | ----------------------------------------- | ----------------------- | -------- |
| POST   | /graphql | 教师 BFF GraphQL 端点                     | JWT 必需 + teacher 角色 | B1       |
| GET    | /graphql | GraphQL Playground（开发环境，生产关闭）  | 开发环境公开            | ARB-001  |
| GET    | /healthz | 健康检查（liveness）                      | 公开                    | G3       |
| GET    | /readyz  | 就绪检查（readiness，按阶段扩展下游探针） | 公开                    | G2 + §2.4 |

### 1.3 GraphQL schema（如 BFF）

**SDL-first**（president §2.17 裁决），schema 文件路径：

```
packages/shared-ts/contracts/graphql/teacher-bff.schema.graphql
```

> 文件命名遵循 president §2.17 统一规范 `<bff-name>.schema.graphql`，由 ai03 起草、coord 在批次 1 启动前仲裁第一版。
> 前端 AI（ai13/ai16）通过 graphql-codegen 生成 TS 类型消费。

核心 Query / Mutation 域：

| 域             | Query / Mutation                                                          | 聚合下游                                                              | 阶段   |
| -------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------ |
| **auth**       | currentUser                                                               | iam.GetUserInfo + GetEffectivePermissions + GetViewports              | P2     |
| **dashboard**  | teacherDashboard                                                          | P2: iam gRPC（用户基础信息，下游字段返 null + extensions.warning）<br>P3+: data-ana.GetTeacherDashboard | P2 null → P4 真实 |
| **classes**    | myClasses                                                                 | P2: iam gRPC（按 president §3.5，P2 班级列表来自 iam 数据）<br>P3+: core-edu.ClassService.GetClassesByTeacher | P2 iam → P3 core-edu |
| **students**   | classStudents                                                             | core-edu.ClassService.ListStudentsByClass + iam.BatchGetUsers         | P3+    |
| **exams**      | classExams / createExam / updateExam / deleteExam                         | core-edu.ExamService                                                  | P3+    |
| **homework**   | classHomework / assignHomework                                            | core-edu.HomeworkService                                              | P3+    |
| **grades**     | studentGrades / recordGrade                                               | core-edu.GradeService                                                 | P3+    |
| **attendance** | classAttendance / recordAttendance                                        | core-edu.AttendanceService（C4 裁决 P3 补全）                         | P3+    |
| **content**    | textbooks / chapters / knowledgePoints / questions                        | content 4 个 Service                                                  | P4+    |
| **notifications** | myNotifications / markAsRead                                           | msg.NotificationService                                               | P5+    |
| **ai**         | aiChat / generateQuestion / generateLessonPlan                            | ai.AiService（A4 裁决 P5 补全 GenerateLessonPlan）                    | P5+    |
| **admin.\***   | admin.schoolStats / admin.listClasses / admin.listTeachers 等             | admin 命名空间，复用 teacher-bff endpoint（president §5.1）           | P2 预留 schema / P6 实现 |

**admin 命名空间预留**（president §5.1 + §7.3 强制）：

- **P2 预留**：schema 文件中预留 `admin.*` Query/Mutation 命名空间占位（含类型定义但 Resolver 返 null）
- **P6 实现**：ai16 admin-portal 复用 teacher-bff GraphQL endpoint，ai03 在 P6 实现 admin Resolver 真实数据
- **理由**：admin 操作低 QPS，无需独立 admin-bff 服务；减少 ai16 工作量

### 1.4 Kafka 事件发布（如有）

无。teacher-bff 不发布事件，仅做 gRPC 聚合（B7 裁决：P2-P4 不订阅 Kafka，P5 push-gateway 落地后再订阅）。

### 1.5 错误码前缀

`BFF_TEACHER_`（B5 + G14 裁决，统一 BFF_ 前缀）。

**BFF 越权防御 3 类错误码**（president §2.7 裁决）：

| 错误码                           | HTTP | 场景                                                 | i18n key                              |
| -------------------------------- | ---- | ---------------------------------------------------- | ------------------------------------- |
| `BFF_TEACHER_UNAUTHORIZED`       | 401  | x-user-id 缺失或无效                                 | `error.bffTeacher.unauthorized`       |
| `BFF_TEACHER_FORBIDDEN_RESOURCE` | 403  | teacherId 与资源无归属关系（场景 A）                 | `error.bffTeacher.forbidden_resource` |
| `BFF_TEACHER_IDENTITY_MISMATCH`  | 403  | JWT teacherId 与请求 body teacherId 不一致（场景 B） | `error.bffTeacher.identity_mismatch`  |

**其他 BFF_TEACHER_ 错误码**（聚合层）：

| 错误码                                | HTTP | 场景                                   |
| ------------------------------------- | ---- | -------------------------------------- |
| `BFF_TEACHER_UPSTREAM_UNAVAILABLE`    | 502  | 下游 gRPC 不可达                        |
| `BFF_TEACHER_AGGREGATION_FAILED`      | 500  | 聚合多下游时部分失败且无降级数据        |
| `BFF_TEACHER_VALIDATION_FAILED`       | 400  | 输入参数校验失败                        |
| `BFF_TEACHER_INTERNAL_ERROR`          | 500  | 兜底内部错误                            |

**B3 裁决澄清**：BFF 豁免 `@RequirePermission` 指不做"功能权限决策"，但必须做"数据权限防御"（B4 越权防御，见 §3.3）。

---

## §2 我消费什么（依赖上游）

### 2.1 gRPC 调用（同步）

**B2 裁决**：首次实现即 gRPC 调用下游，禁止 REST fetch 过渡。
**B8 裁决**：通过 `DownstreamClient` 抽象统一封装（BFF 模式 v2 标准抽象，3 个 BFF 共用）。

> **RPC 现状标注说明**：
> - ✅ = proto 中已定义（按 packages/shared-proto/proto/ 现状核对）
> - ❌ = proto 中未定义，待 coord 补全（已在裁决中明确补全时机）

#### 2.1.1 iam (ai06) — gRPC 50052

| Service.RPC                       | 用途             | 现状 | 阶段 | mock 策略                                   |
| --------------------------------- | ---------------- | ---- | ---- | ------------------------------------------- |
| IamService.GetUserInfo            | 获取当前教师信息 | ✅   | P2   | 返回固定 UserInfo（teacher 角色）           |
| IamService.GetViewports           | 教师导航菜单     | ❌   | P2   | 返回固定视口列表（待 coord 补 proto）       |
| IamService.GetEffectivePermissions | 权限校验        | ❌   | P2   | 返回全权限（放行）                          |
| IamService.BatchGetUsers          | 批量补全学生姓名 | ❌   | P3+  | 返回固定用户名（"学生001"~"学生030"）       |
| IamService.GetEffectiveDataScope  | 数据范围校验     | ❌   | P3+  | 返回 OWN 范围                               |
| IamService.GetChildrenByParent    | （parent-bff 用）| ❌   | P4   | teacher-bff 不调用                          |

> iam.proto 现状仅 4 RPC（Register/Login/RefreshToken/GetUserInfo），matrix.md §2 标称 12 RPC，待 ai06 + coord 按 I1-I8 裁决补全。

#### 2.1.2 core-edu (ai08) — gRPC 50053

| Service.RPC                          | 用途           | 现状 | 阶段 | mock 策略                          |
| ------------------------------------ | -------------- | ---- | ---- | ---------------------------------- |
| ExamService.CreateExam               | 创建考试       | ✅   | P3   | 返回固定 examId                    |
| ExamService.GetExam                  | 获取考试详情   | ✅   | P3   | 返回固定考试数据                   |
| ExamService.ListExamsByClass         | 按班级列考试   | ✅   | P3   | 返回固定 5 场考试                  |
| ExamService.UpdateExam               | 更新考试       | ✅   | P3   | 返回 success=true                  |
| ExamService.DeleteExam               | 删除考试       | ✅   | P3   | 返回 success=true                  |
| HomeworkService.AssignHomework       | 布置作业       | ✅   | P3   | 返回固定 homeworkId                |
| HomeworkService.GetHomework          | 获取作业详情   | ✅   | P3   | 返回固定作业数据                   |
| HomeworkService.ListHomeworkByClass  | 按班级列作业   | ✅   | P3   | 返回固定 5 份作业                  |
| HomeworkService.SubmitHomework       | 提交作业       | ✅   | P3   | 返回 success=true（student-bff 用）|
| GradeService.RecordGrade             | 录入成绩       | ✅   | P3   | 返回 success=true                  |
| GradeService.GetGrade                | 获取成绩       | ✅   | P3   | 返回固定成绩数据                   |
| GradeService.ListGradesByStudent     | 按学生列成绩   | ✅   | P3   | 返回固定 10 条成绩                 |
| GradeService.ListGradesByExam        | 按考试列成绩   | ✅   | P3   | 返回固定 30 条成绩                 |
| GradeService.ListGradesByHomework    | 按作业列成绩   | ✅   | P3   | 返回固定 30 条成绩                 |
| ClassService.GetClassesByTeacher     | 教师班级列表   | ❌   | P3   | 返回固定 3 个 ClassInfo            |
| ClassService.ListStudentsByClass     | 班级学生名单   | ❌   | P3   | 返回固定 30 个 StudentInfo         |
| ClassService.BatchGetClasses         | 批量获取班级   | ❌   | P3   | 返回固定班级数据                   |
| AttendanceService.RecordAttendance   | 记录考勤       | ❌   | P3   | 返回 success=true（C4 裁决补全）   |
| AttendanceService.GetClassAttendance | 查询考勤       | ❌   | P3   | 返回固定考勤数据（C4 裁决补全）    |

> core_edu.proto 现状 14 RPC（ExamService 5 + HomeworkService 4 + GradeService 5），matrix.md §2 标称 22 RPC 5 Service，待 coord 按 C4/C5 裁决补 AttendanceService + ClassService（GetClassesByTeacher / ListStudentsByClass / BatchGetClasses）。

#### 2.1.3 content (ai09) — gRPC 50054

| Service.RPC                          | 用途         | 现状 | 阶段 | mock 策略                       |
| ------------------------------------ | ------------ | ---- | ---- | ------------------------------- |
| TextbookService.ListTextbooks        | 教材列表     | ✅   | P4   | 返回固定 5 个教材               |
| TextbookService.GetTextbook          | 获取教材详情 | ✅   | P4   | 返回固定教材数据                |
| TextbookService.CreateTextbook       | 创建教材     | ✅   | P4   | 返回固定 textbookId             |
| KnowledgeGraphService.GetPrerequisites | 知识点前置 | ✅   | P4   | 返回固定知识点依赖              |
| KnowledgeGraphService.GetLearningPath | 学习路径    | ✅   | P4   | 返回固定学习路径                |
| ChapterService.ListChapters          | 章节列表     | ❌   | P4   | 返回固定章节树（N5 裁决补全）   |
| ChapterService.GetChapter            | 章节详情     | ❌   | P4   | 返回固定章节（N5 裁决补全）     |
| QuestionService.SearchQuestions      | 题库检索     | ❌   | P4   | 返回固定 20 题（N3 裁决补全）   |

> content.proto 现状 5 RPC（TextbookService 3 + KnowledgeGraphService 2），matrix.md §2 标称 18 RPC 4 Service，待 coord 按 N3/N5 裁决补 ChapterService + QuestionService。

#### 2.1.4 data-ana (ai11) — gRPC 50055

| Service.RPC                          | 用途           | 现状 | 阶段 | mock 策略                          |
| ------------------------------------ | -------------- | ---- | ---- | ---------------------------------- |
| AnalyticsService.GetClassPerformance | 班级成绩分析   | ✅   | P4   | 返回固定分析数据                   |
| AnalyticsService.GetStudentWeakness  | 学生薄弱点     | ✅   | P4   | 返回固定 3 个薄弱知识点            |
| AnalyticsService.GetLearningTrend     | 学习趋势       | ✅   | P4   | 返回固定 12 个月趋势               |
| AnalyticsService.GetTeacherDashboard | 教师仪表盘     | ❌   | P4   | 返回固定仪表盘数据（D4 裁决补全）  |
| AnalyticsService.GetWarningList      | 预警列表       | ❌   | P4   | 返回固定 5 条预警（D4 裁决补全）   |
| AnalyticsService.GetMasteryDistribution | 知识掌握分布 | ❌   | P4   | 返回固定分布数据（D4 裁决补全）    |
| AnalyticsService.SubscribeMasteryUpdate (stream) | 掌握度订阅 | ❌ | P5+ | 流式 mock（D4 裁决补全）           |

> analytics.proto 现状 3 RPC，matrix.md §2 标称 12 RPC，待 coord 按 D4 裁决补全 4 端 Dashboard + Warning + MasteryDistribution + SubscribeMasteryUpdate Stream RPC。

#### 2.1.5 msg (ai10) — gRPC 50056

| Service.RPC                              | 用途         | 现状 | 阶段 | mock 策略                          |
| ---------------------------------------- | ------------ | ---- | ---- | ---------------------------------- |
| NotificationService.SendNotification      | 发送通知     | ✅   | P5   | 返回固定 notificationId            |
| NotificationService.ListNotifications     | 教师通知列表 | ✅   | P5   | 返回固定 10 条通知                 |
| NotificationService.MarkAsRead            | 标记已读     | ✅   | P5   | 返回 success=true                  |
| NotificationService.SearchNotifications   | 搜索通知     | ✅   | P5   | 返回固定搜索结果                   |
| NotificationPreferenceService.*           | 通知偏好     | ❌   | P5   | 返回默认偏好（待 coord 补 proto）  |
| NotificationTemplateService.*             | 通知模板     | ❌   | P5   | 返回固定模板（待 coord 补 proto）  |

> msg.proto 现状 4 RPC（NotificationService），matrix.md §2 标称 13 RPC 3 Service，待 coord 补 NotificationPreferenceService + NotificationTemplateService。

#### 2.1.6 ai (ai12) — gRPC 50058

| Service.RPC                       | 用途     | 现状 | 阶段 | mock 策略                       |
| --------------------------------- | -------- | ---- | ---- | ------------------------------- |
| AiService.Chat                    | AI 对话  | ✅   | P5   | 返回固定回复                    |
| AiService.StreamChat (stream)     | 流式对话 | ✅   | P5   | 流式 mock（SSE）                |
| AiService.GenerateQuestion        | AI 出题  | ✅   | P5   | 返回固定题目                    |
| AiService.OptimizeExpression      | 表达优化 | ✅   | P5   | 返回固定优化结果                |
| AiService.GenerateLessonPlan      | AI 备课  | ❌   | P5   | 返回固定教案（A4 裁决补全）     |
| AiService.StreamGenerateQuestion (stream) | 流式出题 | ❌ | P5 | 流式 mock（A4 裁决补全）        |

> ai.proto 现状 4 RPC，matrix.md §2 标称 6 RPC，待 coord 按 A4 裁决补 GenerateLessonPlan + StreamGenerateQuestion。
> **端口说明**：ai 服务端口为 **50058**（port-allocation.md §5 最终值，push-gateway 50057 豁免释放后 50058 让给 ai）。

### 2.2 Kafka 事件订阅（异步）

无。teacher-bff 不订阅 Kafka 事件（B7 裁决：P2-P4 不订阅，仅同步聚合；P5 push-gateway 落地后再订阅，届时评估订阅 edu.notification.* 用于实时通知推送）。

### 2.3 HTTP 调用（如有）

无。B2 裁决：首次实现即 gRPC，禁止 REST fetch。

---

## §3 就绪信号

### 3.1 我依赖的上游就绪标志

| 上游       | AI   | 就绪信号                                         | 阶段 |
| ---------- | ---- | ------------------------------------------------ | ---- |
| iam        | ai06 | gRPC 50052 + 12 RPC + HealthService SERVING      | P2   |
| core-edu   | ai08 | gRPC 50053 + 22 RPC + HealthService SERVING      | P3   |
| content    | ai09 | gRPC 50054 + 18 RPC + HealthService SERVING      | P4   |
| data-ana   | ai11 | gRPC 50055 + 12 RPC + HealthService SERVING      | P4   |
| msg        | ai10 | gRPC 50056 + 13 RPC + HealthService SERVING      | P5   |
| ai         | ai12 | gRPC 50058 + 6 RPC + HealthService SERVING       | P5   |
| coord      | coord | shared-ts DownstreamClient 抽象包就绪（B8 裁决）| P2 启动前 |
| coord      | coord | teacher-bff.schema.graphql 第一版仲裁完成（含 admin namespace）| P2 启动前 |

### 3.2 我的就绪标志（供下游消费）

| 信号                                          | 阶段 | 消费方              |
| --------------------------------------------- | ---- | ------------------- |
| teacher-bff GraphQL :3003 启用（/healthz 200）| P2   | teacher-portal      |
| /readyz 返回 200（按阶段扩展探针，见 §3.3）   | P2+  | api-gateway         |
| GraphQL schema 可内省（POST /graphql）        | P2   | teacher-portal / admin-portal |
| 核心 Query 可执行：currentUser / myClasses / teacherDashboard | P2 | teacher-portal |
| 核心 Mutation 可执行：createExam / assignHomework / recordGrade | P3 | teacher-portal |
| admin namespace 预留（schema 含 admin.* 类型，P6 实现 Resolver）| P2 预留 / P6 实现 | admin-portal |
| DownstreamClient 抽象落地（3 BFF 共用，B8）   | P2   | student-bff / parent-bff |

### 3.3 /readyz 探针按阶段扩展（president §2.4 + G2）

**DownstreamHealthCheck 注册表模式**，每个下游注册独立探针，按阶段启用：

| 阶段 | 探针列表                                                              | 项数 |
| ---- | --------------------------------------------------------------------- | ---- |
| P2   | Redis PING + iam gRPC 50052                                           | 2    |
| P3   | + core-edu gRPC 50053                                                 | 3    |
| P4   | + content gRPC 50054 + data-ana gRPC 50055                            | 5    |
| P5   | + ai gRPC 50058 + msg gRPC 50056                                      | 7    |

> 总裁裁决 §2.4：探针列表扩展属"跨阶段扩展例外"（president §2.3），允许新增探针但禁止修改已有探针检查项。
> **软失败规则**：必需依赖（Redis / 已启用 gRPC 下游）失败返 503；可选依赖（Kafka 消费 / 未启用 gRPC 下游）失败仅告警返 200 + `degraded: true`。

### 3.4 越权防御（B4 + president §2.9）

**AuthorizationGuard** 实现 teacherId 与资源归属校验：

| 阶段 | 实现方式                                                              | 裁决依据 |
| ---- | --------------------------------------------------------------------- | -------- |
| P2   | DEV_MODE 放行（环境变量 TEACHER_BFF_DEV_MODE=true）+ 日志告警         | §2.9     |
| P3+  | 接入 core-edu gRPC 真实校验（GetClassesByTeacher 比对 teacherId）     | §2.9     |

> B3 裁决澄清：BFF 豁免 `@RequirePermission` 不做"功能权限决策"，但必须做"数据权限防御"（B4）。错误码见 §1.5。

---

## §4 Mock 策略

### 4.1 我提供的 mock

在 teacher-bff 真实就绪前，为下游（teacher-portal / admin-portal）提供以下 mock：

- **GraphQL mock**：使用 Apollo Server mockProviders 或 MSW 拦截 POST /graphql
  - currentUser 返回固定教师（id="teacher-001", name="张老师", roles=["teacher"]）
  - myClasses 返回固定 3 个班级
  - teacherDashboard 返回固定仪表盘数据
  - myNotifications 返回固定 10 条通知
- **admin namespace mock**：admin-portal 查询返回固定管理员视角数据（全校统计）

### 4.2 我消费的 mock

在真实上游就绪前，teacher-bff 使用以下 mock（详见 §2.1 各表的 mock 策略列）：

- **iam mock**：固定 UserInfo + 全权限 + 固定视口
- **core-edu mock**：固定班级/学生/考试/作业/成绩/考勤数据
- **content mock**：固定教材/章节/知识点/题目
- **data-ana mock**：固定仪表盘/分析/预警
- **msg mock**：固定通知列表 + MarkAsRead success
- **ai mock**：固定 AI 回复/题目/教案

> 所有上游 mock 通过 gRPC client 拦截器实现（DownstreamClient 抽象内置 mock 开关，B8 裁决），上游就绪后移除拦截器切换真实调用。
