364 lines
38 KiB
Markdown
364 lines
38 KiB
Markdown
# 模块理解确认书 — student-bff
|
||
|
||
> AI 标识:ai04
|
||
> 阶段:阶段 1(全局理解)
|
||
> 日期:2026-07-09
|
||
> 状态:已对齐仲裁裁决(coord-final-decisions §2 B1-B8 + president-final-rulings §2.2-2.9)
|
||
> 关联文档:[ai-allocation §6 模板](../../docs/architecture/ai-allocation.md)、[004 架构影响地图](../../docs/architecture/004_architecture_impact_map.md)、[pending-features P3](../../docs/architecture/roadmap/pending-features.md)、[coord-final-decisions](../../docs/architecture/coord-final-decisions.md)、[president-final-rulings](../../docs/architecture/president-final-rulings.md)
|
||
>
|
||
> **仲裁对齐说明(ISSUE-STU-001 修复)**:
|
||
> - B1: API 风格 = GraphQL Yoga + DataLoader(P2 起直接 GraphQL,禁止 REST 渐进)
|
||
> - B2: 下游通信 = gRPC 首次实现即用(@grpc/grpc-js + @grpc/proto-loader,禁止 HTTP fetch)
|
||
> - B5: 错误码前缀 = `BFF_STUDENT_`(BFF 在前,非 `STUDENT_BFF_`)
|
||
> - B8: DownstreamClient 抽象复用 shared-ts(回写 teacher-bff,3 BFF 统一)
|
||
> - 本文档中早期将通信方式写为 HTTP REST/fetch、错误码前缀写为 `STUDENT_BFF_` 的部分已修正,以本对齐说明为准。
|
||
|
||
---
|
||
|
||
## 1. 我在架构中的位置
|
||
|
||
| 维度 | 内容 |
|
||
| -------------- | ----------------------------------------------------------------------------- |
|
||
| 层级 | **L4 BFF 聚合层**(004 §3.1 六层架构) |
|
||
| 上游调用方 | api-gateway(Go Gin,反向代理 `/api/v1/student/*` → student-bff:3009) |
|
||
| 下游被调用方 | iam、core-edu、content、data-ana(按 004 §4 服务依赖图) |
|
||
| 通信方式(入) | **GraphQL Yoga over HTTP**(api-gateway → student-bff:3009,B1 裁决:P2 起直接 GraphQL,禁止 REST 渐进) |
|
||
| 通信方式(出) | **gRPC**(@grpc/grpc-js + @grpc/proto-loader,B2 裁决:首次实现即用 gRPC,禁止 HTTP fetch) |
|
||
| 微前端对接 | student-portal(ai07 负责,P3 阶段)通过 api-gateway 调用 student-bff |
|
||
| 推送通道 | push-gateway(P5 阶段,WebSocket/SSE 推送考试通知、成绩发布等) |
|
||
|
||
**架构定位**(004 §1.1a + §5.4):
|
||
|
||
- 按"使用场景域"分 BFF,student-bff 服务于**学习场景域**,复用角色:学生
|
||
- 不按角色分 BFF,新角色复用现有 BFF 通过视口差异化
|
||
- DataScope = **SELF(L0)**:学生只能看自己的数据(004 §5.3)
|
||
|
||
---
|
||
|
||
## 2. 我的限界上下文
|
||
|
||
### 2.1 我负责什么
|
||
|
||
student-bff 是**纯聚合层**,不持有业务状态、不直接访问 DB(对齐 teacher-bff 模式)。职责:
|
||
|
||
1. **聚合**:并行调用多个下游业务服务,组装学生视角的复合数据
|
||
2. **裁剪**:将下游返回的领域数据裁剪为学生端所需的最小字段集
|
||
3. **协议转换**:对外暴露场景化 HTTP/GraphQL 端点,对内调用下游 REST/gRPC
|
||
4. **缓存**:聚合结果 Redis 短缓存 5-30s(004 §6.2 BFF 混合读策略)
|
||
|
||
### 2.2 我的聚合场景(学生视角)
|
||
|
||
| 场景 | 聚合的下游服务 | 用途 |
|
||
| ------------------ | --------------------------------------------------------------------------- | ------------------------------ |
|
||
| 学生首页 Dashboard | iam `/iam/me` + core-edu `/homework/class/:classId` + msg `/notifications` | 个人信息 + 待办作业 + 未读消息 |
|
||
| 即将到来的考试 | core-edu `/exams/class/:classId` | 考试日程提醒 |
|
||
| 我的作业列表 | core-edu `/homework/class/:classId` | 查看待完成作业 |
|
||
| 提交作业 | core-edu `/homework/:id/submit` | 学生提交作业答案 |
|
||
| 我的成绩 | core-edu `/grades/student/:studentId` | 查询历史成绩 |
|
||
| 消息中心 | msg `/notifications` + `/notifications/:id/read` | 通知列表 + 已读 |
|
||
| 教材浏览 | content `/textbooks` + `/chapters` | 按章节学习 |
|
||
| 题库练习 | content `/questions` | 按知识点刷题 |
|
||
| 学情诊断 | data-ana `/analytics/student/:id/weakness` + `/analytics/student/:id/trend` | 自我掌握度分析 |
|
||
| AI 答疑 | ai `/ai/chat` + `/ai/stream-chat`(SSE 流式) | 智能答疑辅助 |
|
||
| 个性化学习路径 | content `/knowledge-points/:id/learning-path` | 基于学情推荐学习路径 |
|
||
|
||
### 2.3 我不负责什么(明确边界外)
|
||
|
||
| 不负责项 | 归属服务 | 说明 |
|
||
| -------------- | ------------------------------ | ----------------------------------------------------------------- |
|
||
| 业务数据持久化 | core-edu / content / msg / iam | BFF 不写 DB |
|
||
| 权限校验 | 下游业务服务 + iam | BFF 不做权限校验(对齐 teacher-bff),透传 `x-user-id` 让下游校验 |
|
||
| 用户认证 | iam + api-gateway | JWT 校验在 Gateway,BFF 只读 `x-user-id` 头 |
|
||
| 领域事件发布 | core-edu / content | BFF 不发布事件,仅可选订阅事件用于实时推送 |
|
||
| 数据范围过滤 | 下游业务服务 Repository 层 | BFF 透传 userId,下游按 DataScope=SELF 过滤 |
|
||
| 班级管理 | core-edu(classes 模块) | 学生只读自己所在班级 |
|
||
| 考试批改 | core-edu | 学生不能批改,只能查看成绩 |
|
||
|
||
---
|
||
|
||
## 3. 我与外部的契约
|
||
|
||
### 3.1 我消费的 proto message / 下游接口
|
||
|
||
> ✅ **B2 裁决落地**:BFF→Service 首次实现即用 gRPC(@grpc/grpc-js + @grpc/proto-loader),通过 `DownstreamClient` 抽象(B8 裁决,复用 shared-ts,3 个 BFF 统一)。proto 即契约,不再是"文档"。
|
||
|
||
| 下游服务 | proto service | gRPC method(B2 裁决) | 用途 |
|
||
| ------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------ |
|
||
| iam | `IamService.GetUserInfo` | `iam.GetUserInfo` | 获取学生个人信息 + roles + dataScope |
|
||
| iam | `IamService.GetViewports`(proto 缺失) | `iam.GetViewports` | 获取学生端导航视口 |
|
||
| iam | `IamService.GetEffectivePermissions`(proto 缺失) | `iam.GetEffectivePermissions` | 获取有效权限列表 |
|
||
| classes(core-edu) | `ClassService.GetClass` / `ListClasses` | `classes.GetClass` / `classes.ListClasses` | 查自己所在班级 |
|
||
| core-edu | `ExamService.GetExam` / `ListExamsByClass` | `core-edu.GetExam` / `core-edu.ListExamsByClass` | 查班级考试 |
|
||
| core-edu | `HomeworkService.GetHomework` / `ListHomeworkByClass` / `SubmitHomework` | `core-edu.ListHomeworkByStudent` / `core-edu.SubmitHomework` | 查作业 + 提交 |
|
||
| core-edu | `GradeService.GetGrade` / `ListGradesByStudent` | `core-edu.ListGradesByStudent` | 查自己成绩 |
|
||
| content | `TextbookService.GetTextbook` / `ListTextbooks` | `content.ListTextbooks` | 查教材 |
|
||
| content | `ChapterService`(proto 缺失) | `content.ListChapters` | 查章节 |
|
||
| content | `QuestionService`(proto 缺失) | `content.ListQuestions` | 查题库 |
|
||
| content | `KnowledgeGraphService.GetLearningPath` | `content.GetLearningPath` | 学习路径 |
|
||
| msg | `NotificationService.ListNotifications` / `MarkAsRead` / `SearchNotifications` | `msg.ListNotifications` / `msg.MarkAsRead` | 消息中心 |
|
||
| data-ana | `AnalyticsService.GetStudentWeakness` / `GetLearningTrend` | `data-ana.GetStudentWeakness` / `data-ana.GetLearningTrend` | 学情分析 |
|
||
| ai | `AiService.Chat` / `StreamChat` / `GenerateQuestion` | `ai.Chat`(同步) / `ai.StreamChat`(server-streaming,B2 裁决) | AI 答疑 |
|
||
|
||
### 3.2 我暴露的 GraphQL API(student-bff 对外)
|
||
|
||
> ✅ **B1 裁决落地**:P2 起直接 GraphQL Yoga(禁止 REST 渐进),schema 存放于 `packages/shared-ts/contracts/graphql/student-bff.schema.graphql`(president §2.2.1)。
|
||
> 网关路径:`/api/v1/student/*` → api-gateway 剥离 `/api/v1` 后代理到 student-bff:3009 GraphQL endpoint。
|
||
> 分页采用 Relay Cursor Connections 规范(`{ edges, pageInfo, totalCount }`)。
|
||
> 权限点标注于 schema 注释 `# @permission:`,DataScope 固定 `OWN`(学生数据隔离 SELF)。
|
||
|
||
#### Query(14 个字段)
|
||
|
||
| Query 字段 | 聚合下游 | 权限点(注释标注) | 说明 |
|
||
| ------------------------- | -------------------- | ----------------------- | -------------------- |
|
||
| `currentUser` | iam | AUTH_READ | 学生信息 + 权限 + 视口 |
|
||
| `myClasses` | core-edu | CLASS_READ | 我的班级列表 |
|
||
| `myExams` | core-edu | EXAM_READ | 即将到来的考试 |
|
||
| `myHomework` | core-edu | HOMEWORK_READ | 我的作业列表 |
|
||
| `myGrades` | core-edu | GRADE_READ | 我的成绩(B4 比对) |
|
||
| `myAttendance` | core-edu | ATTENDANCE_READ | 我的考勤记录 |
|
||
| `textbooks` | content | TEXTBOOK_READ | 教材列表 |
|
||
| `chapters` | content | CHAPTER_READ | 章节树 |
|
||
| `learningPath` | content | LEARNING_PATH_READ | 学习路径推荐 |
|
||
| `studentDashboard` | data-ana | DASHBOARD_VIEW | 学生仪表盘聚合 |
|
||
| `myWeakness` | data-ana | WEAKNESS_READ | 学情诊断(薄弱点) |
|
||
| `myTrend` | data-ana | TREND_READ | 学习趋势 |
|
||
| `myNotifications` | msg | NOTIFICATION_READ | 通知列表 |
|
||
| `myNotificationUnreadCount` | msg | NOTIFICATION_READ | 通知未读数 |
|
||
|
||
#### Mutation(2 个字段)
|
||
|
||
| Mutation 字段 | 下游 | 权限点 | 说明 |
|
||
| ---------------------------- | --------------------- | ------------------- | ---------------------------- |
|
||
| `submitHomework` | core-edu.SubmitHomework | HOMEWORK_SUBMIT | 提交作业(B4 强制 userId) |
|
||
| `markNotificationAsRead` | msg.MarkAsRead | NOTIFICATION_UPDATE | 标记通知已读(B4 强制 userId)|
|
||
|
||
#### Subscription(1 个字段,SSE 传输)
|
||
|
||
| Subscription 字段 | 下游 | 权限点 | 说明 |
|
||
| ----------------- | ------------------- | --------------- | ----------------------------- |
|
||
| `aiStreamChat` | ai.StreamChat | STUDENT_AI_CHAT | AI 答疑流式响应(gRPC server-streaming 透传) |
|
||
|
||
### 3.3 错误码前缀
|
||
|
||
> ✅ **B5 裁决**:BFF 在前,统一 `BFF_STUDENT_` 前缀(非 `STUDENT_BFF_`)。
|
||
> **G14 裁决**:服务名大写前缀。**F4 裁决**:i18n key 格式 `error.bffStudent.<code_snake>`。
|
||
|
||
| 前缀 | 用途 | 示例 |
|
||
| -------------- | ---------------------- | ------------------------------------------------------------- |
|
||
| `BFF_STUDENT_` | student-bff 自身错误 | `BFF_STUDENT_UNAUTHORIZED`、`BFF_STUDENT_BAD_GATEWAY` |
|
||
| 下游错误透传 | 下游服务错误码原样返回 | `CLASSES_NOT_FOUND`、`IAM_USER_NOT_FOUND` |
|
||
|
||
错误类清单(`shared/errors/application-error.ts`,11 个类,G8 ActionState 信封):
|
||
|
||
| 错误类 | statusCode | code | 说明 |
|
||
| ------------------------------ | ---------- | --------------------------------- | ----------------------------- |
|
||
| `ValidationError` | 400 | `BFF_STUDENT_VALIDATION_ERROR` | Zod 校验失败 |
|
||
| `UnauthorizedError` | 401 | `BFF_STUDENT_UNAUTHORIZED` | 缺失 `x-user-id` 头 |
|
||
| `ForbiddenResourceError` | 403 | `BFF_STUDENT_FORBIDDEN_RESOURCE` | 场景 A:资源无归属(president §2.7) |
|
||
| `IdentityMismatchError` | 403 | `BFF_STUDENT_IDENTITY_MISMATCH` | 场景 B:JWT/body userId 不一致(president §2.7) |
|
||
| `NotFoundError` | 404 | `BFF_STUDENT_NOT_FOUND` | 资源不存在 |
|
||
| `ConflictError` | 409 | `BFF_STUDENT_CONFLICT` | 重复提交 / 状态冲突 |
|
||
| `BusinessError` | 422 | `BFF_STUDENT_BUSINESS_ERROR` | 业务规则违反 |
|
||
| `BadGatewayError` | 502 | `BFF_STUDENT_BAD_GATEWAY` | 下游 gRPC 失败 |
|
||
| `ServiceUnavailableError` | 503 | `BFF_STUDENT_SERVICE_UNAVAILABLE` | 熔断器开启(P6) |
|
||
| `GatewayTimeoutError` | 504 | `BFF_STUDENT_GATEWAY_TIMEOUT` | 下游超时 |
|
||
| `InternalError` | 500 | `BFF_STUDENT_INTERNAL_ERROR` | 未捕获异常 |
|
||
|
||
### 3.4 我订阅的 Kafka 事件(P5 起订阅,B7 裁决)
|
||
|
||
> ✅ **B7 裁决**:P2-P4 阶段不订阅 Kafka,P5 起 student-bff 订阅事件用于实时推送。
|
||
> 消费者组:`student-bff-event-subscriber`。幂等去重:Redis SETNX `event_id`。
|
||
> 推送通道:push-gateway HTTP POST `/push/user/:userId`(失败软处理,仅 warn 日志)。
|
||
|
||
| Topic | 事件 | 消费动作 |
|
||
| -------------------------- | --------------------------------------- | ------------------------------ |
|
||
| `edu.homework.events` | `homework.assigned` / `homework.graded` | 推送给学生(push-gateway) |
|
||
| `edu.exam.events` | `exam.published` / `exam.updated` | 考试提醒推送 |
|
||
| `edu.grade.events` | `grade.recorded` | 成绩发布推送 |
|
||
| `edu.notification.events` | `notification.created` | 通知推送 |
|
||
| `edu.attendance.events` | `attendance.recorded` | 考勤提醒推送 |
|
||
| `edu.class.events` | `class.updated` | 班级信息变更推送 |
|
||
| `edu.content.events` | `content.published` | 教材/章节更新推送 |
|
||
|
||
> ⚠️ P3-P4 阶段 student-bff 不消费事件,仅做同步聚合。事件订阅逻辑在 `src/student/events/event-subscriber.ts`。
|
||
|
||
---
|
||
|
||
## 4. 我的技术栈
|
||
|
||
| 维度 | 选型 | 依据 |
|
||
| ------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| 语言 | TypeScript 5.5+ | 004 §2.1 |
|
||
| 框架 | NestJS 10 | 004 §2.1,对齐 teacher-bff 模板 |
|
||
| ORM | **无**(BFF 不访问 DB) | 对齐 teacher-bff,无 repository/schema/dto |
|
||
| 缓存 | Redis 7(短缓存 5-30s) | 004 §6.2 BFF 混合读策略 |
|
||
| 可观测性日志 | pino | 对齐 classes/teacher-bff |
|
||
| 可观测性指标 | prom-client(`/metrics` 端点) | 对齐 teacher-bff main.ts |
|
||
| 可观测性链路 | OpenTelemetry SDK + OTLP exporter | 对齐 teacher-bff tracer.ts |
|
||
| API 风格 | **GraphQL Yoga**(B1 裁决) | P2 起直接 GraphQL + DataLoader,禁止 REST 渐进;schema 存放 `packages/shared-ts/contracts/graphql/student-bff.schema.graphql` |
|
||
| 下游通信 | **gRPC**(B2 裁决) | @grpc/grpc-js + @grpc/proto-loader,DownstreamClient 抽象(B8 复用 shared-ts) |
|
||
| 输入校验 | Zod | Resolver 层 `schema.safeParse(args.input)`(G7 裁决) |
|
||
| 错误处理 | GlobalErrorFilter + ApplicationError | 11 个错误类,ActionState 信封(G8 裁决) |
|
||
| ESM 模式 | NodeNext + `.js` 后缀 import | 对齐 teacher-bff tsconfig |
|
||
| 测试框架 | **Vitest** | 覆盖率 ≥ 80%(lines/functions),branches ≥ 70% |
|
||
|
||
### 4.1 GraphQL 设计决策(已裁决 B1)
|
||
|
||
> ✅ **B1 裁决**:student-bff 从 P2 起直接采用 GraphQL Yoga + DataLoader,禁止 REST 渐进。
|
||
|
||
**裁决结论**:
|
||
|
||
- **API 风格**:GraphQL Yoga over HTTP(SSE 传输 Subscription)
|
||
- **DataLoader**:解决 N+1 查询问题,按下游服务分批聚合
|
||
- **schema 存放**:`packages/shared-ts/contracts/graphql/student-bff.schema.graphql`(president §2.2.1)
|
||
- **分页规范**:Relay Cursor Connections(`{ edges, pageInfo, totalCount }`)
|
||
- **降级模式**:方案 B(president §2.6),`success=true + data 内 degraded=true + degradedFields`
|
||
- **越权防御**:B4 强制自我越权防御,AuthorizationGuard 拦截(president §2.9 方案 D,DEV_MODE 放行)
|
||
|
||
**已落地的 GraphQL 核心文件**:
|
||
|
||
| 文件 | 职责 |
|
||
| --------------------------------------------- | ------------------------------------------------- |
|
||
| `src/shared/graphql/yoga.ts` | GraphQL Yoga 实例 + context 构建 |
|
||
| `src/shared/graphql/dataloader.ts` | DataLoader 工厂(按下游服务分批) |
|
||
| `src/student/resolvers/*.resolver.ts` | Query/Mutation/Subscription Resolver(6 个文件) |
|
||
| `src/student/guards/authorization.guard.ts` | B4 越权防御(assertOwnData + assertIdentityMatch)|
|
||
| `packages/shared-ts/contracts/graphql/student-bff.schema.graphql` | GraphQL schema 定义 |
|
||
|
||
---
|
||
|
||
## 5. 我的阶段归属
|
||
|
||
| 维度 | 内容 |
|
||
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||
| 阶段 | **P3 核心教学阶段**(M7-M10) |
|
||
| 退出标准(pending-features P3) | 教师创建考试 → 发布 → 学生作答提交 → 教师批改 → 事件发到 Kafka → 成绩统计更新 → 全链路可观测 |
|
||
| student-bff 在 P3 的最小交付 | 学生作答作业页面所需 API:`/student/homework` 列表 + `/student/homework/:id/submit` 提交 + `/student/grades` 成绩查看 |
|
||
| 依赖上游阶段产出 | P1(api-gateway 路由骨架 + classes 黄金模板 + shared-proto)、P2(iam 认证 + teacher-bff BFF 模板 + teacher-portal 微前端骨架) |
|
||
| P3 同阶段依赖 | core-edu(考试/作业/成绩域 CRUD + Outbox 事件) |
|
||
| P4 阶段扩展 | 学情诊断查询(双轨读:实时查 core-edu 主库 + 聚合查 data-ana ClickHouse 宽表) |
|
||
| P5 阶段扩展 | AI 答疑流式响应 + Kafka 事件订阅推送 |
|
||
|
||
### 5.1 P3 阶段最小可行集合(MVP)
|
||
|
||
student-bff 在 P3 阶段不一定要实现全部 17 个 GraphQL 字段,优先级:
|
||
|
||
| 优先级 | GraphQL 字段 | P3 必需 | 说明 |
|
||
| ------ | ------------------------------------------------- | ------- | ------------------------- |
|
||
| P0 | `Query.myHomework` + `Mutation.submitHomework` | ✅ | 学生作答作业页面核心 |
|
||
| P0 | `Query.myGrades` | ✅ | 成绩查看 |
|
||
| P0 | `Query.currentUser` | ✅ | 学生信息 + 权限 + 视口 |
|
||
| P0 | `Query.studentDashboard` | ✅ | 学生首页聚合 |
|
||
| P1 | `Query.myExams` | ✅ | 考试日程 |
|
||
| P1 | `Query.myNotifications` | ⚠️ 可选 | P5 msg 服务落地后才有意义 |
|
||
| P2 | `Query.textbooks` / `chapters` / `learningPath` | ❌ P4 | content 服务 P4 才落地 |
|
||
| P2 | `Query.myWeakness` / `myTrend` | ❌ P4 | data-ana 学情诊断 P4 |
|
||
| P2 | `Subscription.aiStreamChat` | ❌ P5 | ai 服务 P5 |
|
||
|
||
---
|
||
|
||
## 6. 我需要对齐的黄金模板项(对照 classes 服务)
|
||
|
||
> 对照 ai-allocation.md §6 模板第 6 节 + §10 审计模板
|
||
|
||
| 对齐项 | classes 黄金模板 | student-bff 计划 | 备注 |
|
||
| ------------------------------- | ---------------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------- |
|
||
| 权限装饰器 `@RequirePermission` | ✅ 全部 Controller 方法 | ⚠️ **B3 豁免** | BFF 豁免 `@RequirePermission`(B3 裁决),但强制自我越权防御(B4,AuthorizationGuard) |
|
||
| 错误码前缀统一 | ✅ `CLASSES_` | ✅ `BFF_STUDENT_` | B5 裁决:BFF 在前,非 `STUDENT_BFF_` |
|
||
| logger(pino) | ✅ `shared/observability/logger.ts` | ✅ 复制 teacher-bff 实现 | service 名改 `student-bff` |
|
||
| metrics(prom-client) | ✅ `/metrics` 端点 | ✅ 复制 teacher-bff main.ts 注册方式 | 指标名前缀 `student_bff_` |
|
||
| tracer(OpenTelemetry) | ✅ OTLP exporter + auto-instrumentations | ✅ 复制 teacher-bff tracer.ts | serviceName 改 `student-bff` |
|
||
| `/healthz` 健康检查 | ✅ liveness | ✅ 复制 teacher-bff | BFF 不查 DB,直接返回 ok |
|
||
| `/readyz` 健康检查 | ✅ Drizzle `SELECT 1` | ✅ 检查下游 6 个服务可达性 | 必需失败返回 503,可选软失败返回 200 + degraded=true |
|
||
| 优雅关闭(SIGTERM) | ✅ LifecycleService 关闭 DB 连接池 | ✅ main.ts 注册 SIGTERM → `app.close()` + `shutdownTracer()` + `circuitBreaker.shutdown()` | BFF 无 DB 连接,关闭 HTTP server + tracer + 熔断器 |
|
||
| 测试覆盖率 ≥ 80% | ✅ Jest | ✅ **Vitest**(已落地) | 5 个测试文件:action-state / application-error / authorization.guard / homework.resolver / push-gateway.service |
|
||
| Dockerfile 多阶段构建 | ✅ builder + runtime | ✅ 复制 teacher-bff Dockerfile | EXPOSE 改 3009 |
|
||
| Zod 输入验证 | ✅ Controller 层 `schema.parse(body)` | ✅ Controller 层校验 | 提交作业 body 需 Zod 校验 |
|
||
| GlobalErrorFilter | ✅ `@Catch()` 全局过滤器 | ✅ 复制 teacher-bff | 注册到 main.ts |
|
||
| ESM `.js` 后缀 import | ✅ tsconfig NodeNext | ✅ 复制 teacher-bff tsconfig | 所有相对 import 带 `.js` |
|
||
| `import type` 纯类型导入 | ✅ | ✅ | 对齐 classes 规范 |
|
||
| 环境变量 Zod 校验 | ✅ `config/env.ts` | ✅ 复制 teacher-bff env.ts | 下游 URL 配置项扩展 |
|
||
|
||
### 6.1 与 teacher-bff 模板的差异点(克隆时必须改)
|
||
|
||
| 文件 | teacher-bff 现值 | student-bff 应改为 |
|
||
| ------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||
| `package.json` name | `@edu/teacher-bff` | `@edu/student-bff` |
|
||
| `src/config/env.ts` `PORT` default | `"3003"` | `"3009"` |
|
||
| `src/config/env.ts` 下游 URL | IamServiceUrl / ClassesServiceUrl / CoreEduServiceUrl | + ContentServiceUrl / DataAnaServiceUrl / MsgServiceUrl / AiServiceUrl(按聚合需求) |
|
||
| `src/teacher/` 目录名 | `teacher/` | `student/` |
|
||
| `@Controller("teacher")` | `"teacher"` | `"student"` |
|
||
| `health.controller.ts` `SERVICE_NAME` | `"teacher-bff"` | `"student-bff"` |
|
||
| `application-error.ts` 错误码前缀 | `TEACHER_BFF_` | `BFF_STUDENT_`(B5 裁决:BFF 在前) |
|
||
| `metrics.ts` 指标名前缀 | `teacher_bff_` | `student_bff_` |
|
||
| `tracer.ts` serviceName | `"teacher-bff"` | `"student-bff"` |
|
||
| `logger.ts` service | `"teacher-bff"` | `"student-bff"` |
|
||
| `main.ts` 启动日志 | `"Teacher BFF started"` | `"Student BFF started"` |
|
||
| `Dockerfile` `EXPOSE` | `3003` | `3009` |
|
||
|
||
---
|
||
|
||
## 7. 风险与依赖(待 coord 仲裁)
|
||
|
||
### 7.1 上游依赖缺口
|
||
|
||
| 风险 | 影响 | 缓解措施 |
|
||
| -------------------------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------- |
|
||
| data-ana 服务未实现查询 API(analytics.proto 3 个 method 无 REST 端点) | P4 学情诊断端点无法实现 | P3 阶段先不实现 `/student/analytics/*`,等 ai06 在 P4 实现 data-ana 查询 API 后再补 |
|
||
| ai 服务未实现 REST/gRPC 端点 | P5 AI 答疑端点无法实现 | P3/P4 阶段先不实现 `/student/ai/*`,等 ai06 在 P5 实现 ai 服务后再补 |
|
||
| content.proto 缺 Chapter/Question 域 | P4 教材/题库端点 proto 契约不全 | 推动 coord 在 shared-proto 补全 content.proto |
|
||
| iam.proto 缺 Viewport/EffectivePermissions | 学生端导航视口 proto 契约不全 | 当前走 REST `/iam/viewports`,proto 补全后切换 |
|
||
| 出勤(attendance)全局缺失 | 学生端无法查出勤 | 推动 coord 在 core_edu.proto 补 Attendance 域(P3 后期或 P4) |
|
||
| 学生-家长关联表缺失(pending-features P2 提到 `parent_student_relations`) | 影响 parent-bff,不影响 student-bff | 报告给 coord,由 ai02 在 iam 或 ai03 在 core-edu 补表 |
|
||
|
||
### 7.2 设计决策(已裁决 B1-B8)
|
||
|
||
> ✅ 全部 8 项决策已由 coord-final-decisions §2 B1-B8 + president-final-rulings §2.2-2.9 裁决。
|
||
|
||
| 决策点 | 裁决编号 | 裁决结论 |
|
||
| ------------------ | -------- | ---------------------------------------------------------------------------------------------- |
|
||
| BFF API 风格 | **B1** | GraphQL Yoga + DataLoader(P2 起直接 GraphQL,禁止 REST 渐进) |
|
||
| 下游通信 | **B2** | gRPC 首次实现即用(@grpc/grpc-js + @grpc/proto-loader,禁止 HTTP fetch) |
|
||
| BFF 权限校验 | **B3** | BFF 豁免 `@RequirePermission`,但透传 `x-user-id` 给下游校验 |
|
||
| 自我越权防御 | **B4** | 强制 B4 越权防御(AuthorizationGuard,场景 A + 场景 B,president §2.9 方案 D DEV_MODE 放行) |
|
||
| 错误码前缀 | **B5** | `BFF_STUDENT_`(BFF 在前,非 `STUDENT_BFF_`) |
|
||
| Redis 缓存 | **B6** | 5-30s 短缓存,TTL ±20% 随机抖动防雪崩 |
|
||
| Kafka 订阅 | **B7** | P2-P4 不订阅,P5 起订阅 7 个 topic,Redis SETNX `event_id` 幂等去重 |
|
||
| DownstreamClient | **B8** | 抽象复用 shared-ts,回写 teacher-bff,3 个 BFF 统一使用 |
|
||
| 降级模式 | president §2.6 | 方案 B:`success=true + data 内 degraded=true + degradedFields` |
|
||
| 越权防御错误码 | president §2.7 | 3 类:ForbiddenResourceError / IdentityMismatchError / DEV_MODE 放行 |
|
||
| GraphQL schema 存放 | president §2.2.1 | `packages/shared-ts/contracts/graphql/student-bff.schema.graphql` |
|
||
| 端口分配 | - | 3009(HTTP),无 gRPC 端口对外 |
|
||
|
||
### 7.3 跨模块协作需求(需提交 coord 协调)
|
||
|
||
| 需求 | 涉及 AI | 协调内容 |
|
||
| ------------------------------------------------------------ | -------------- | --------------------------------------------------------- |
|
||
| api-gateway 新增 `/student` 路由 | ai01 | 在 main.go + config.go 新增 `StudentBffURL` 字段 + 路由块 |
|
||
| docker-compose.deploy.yml 新增 student-bff 服务定义 | coord(infra) | 端口 3009,加入 edu-net + edu-shared 网络 |
|
||
| full-stack-runbook 端口矩阵更新 | coord(docs) | 追加 3009 行 |
|
||
| 004 架构图状态更新 | coord(docs) | student-bff 状态从"📐 需设计"改为"✅ 已实现" |
|
||
| shared-proto 补全 content.proto(Chapter/Question) | coord | P4 阶段 content 服务落地前补全 |
|
||
| shared-proto 补全 iam.proto(Viewport/EffectivePermissions) | coord | 推动 ai02 补 proto |
|
||
| buf.gen.yaml 补 gRPC 插件 | coord | ✅ B2 裁决已落地:TS 走 @grpc/proto-loader 动态加载,无需 buf generate gRPC 插件 |
|
||
|
||
---
|
||
|
||
## 8. 阶段 1 自检结论
|
||
|
||
| 检查项 | 状态 |
|
||
| ------------------------------------ | --------------------------- |
|
||
| 已读必读文档清单(ai-allocation §4) | ✅ |
|
||
| 已运行 arch:scan 更新 arch.db | ✅ |
|
||
| 已查 arch:query modules / stats | ✅ |
|
||
| 已读 classes 黄金模板源码 | ✅ |
|
||
| 已读 teacher-bff BFF 模板源码 | ✅ |
|
||
| 已读 iam 认证服务源码 | ✅ |
|
||
| 已读 shared-proto 全部 .proto | ✅ |
|
||
| 已识别 proto 契约缺口 | ✅(见 §7.1) |
|
||
| 已识别端口/路由预留情况 | ✅(3009 可用,路由未预留) |
|
||
| 已识别设计决策待仲裁项 | ✅ → **已裁决**(B1-B8 + president §2.2-2.9,见 §7.2) |
|
||
| 已识别跨模块协作需求 | ✅(见 §7.3) |
|
||
|
||
**ai04 阶段 1 交付完成,已对齐仲裁裁决(coord-final-decisions B1-B8 + president-final-rulings §2.2-2.9)。P3-P6 全部代码已实现。**
|