Files
Edu/services/student-bff/docs/01-understanding.md
SpecialX f585080e70 feat(student-bff): 完整实现 student-bff 聚合层
包含 src 全部实现、Dockerfile、shared-ts/bff 包等
2026-07-10 19:10:51 +08:00

38 KiB
Raw Blame History

模块理解确认书 — student-bff

AI 标识ai04 阶段:阶段 1全局理解 日期2026-07-09 状态已对齐仲裁裁决coord-final-decisions §2 B1-B8 + president-final-rulings §2.2-2.9 关联文档:ai-allocation §6 模板004 架构影响地图pending-features P3coord-final-decisionspresident-final-rulings

仲裁对齐说明ISSUE-STU-001 修复)

  • B1: API 风格 = GraphQL Yoga + DataLoaderP2 起直接 GraphQL禁止 REST 渐进)
  • B2: 下游通信 = gRPC 首次实现即用(@grpc/grpc-js + @grpc/proto-loader禁止 HTTP fetch
  • B5: 错误码前缀 = BFF_STUDENT_BFF 在前,非 STUDENT_BFF_
  • B8: DownstreamClient 抽象复用 shared-ts回写 teacher-bff3 BFF 统一)
  • 本文档中早期将通信方式写为 HTTP REST/fetch、错误码前缀写为 STUDENT_BFF_ 的部分已修正,以本对齐说明为准。

1. 我在架构中的位置

维度 内容
层级 L4 BFF 聚合层004 §3.1 六层架构)
上游调用方 api-gatewayGo Gin反向代理 /api/v1/student/* → student-bff:3009
下游被调用方 iam、core-edu、content、data-ana按 004 §4 服务依赖图)
通信方式(入) GraphQL Yoga over HTTPapi-gateway → student-bff:3009B1 裁决P2 起直接 GraphQL禁止 REST 渐进)
通信方式(出) gRPC@grpc/grpc-js + @grpc/proto-loaderB2 裁决:首次实现即用 gRPC禁止 HTTP fetch
微前端对接 student-portalai07 负责P3 阶段)通过 api-gateway 调用 student-bff
推送通道 push-gatewayP5 阶段WebSocket/SSE 推送考试通知、成绩发布等)

架构定位004 §1.1a + §5.4

  • 按"使用场景域"分 BFFstudent-bff 服务于学习场景域,复用角色:学生
  • 不按角色分 BFF新角色复用现有 BFF 通过视口差异化
  • DataScope = SELFL0学生只能看自己的数据004 §5.3

2. 我的限界上下文

2.1 我负责什么

student-bff 是纯聚合层,不持有业务状态、不直接访问 DB对齐 teacher-bff 模式)。职责:

  1. 聚合:并行调用多个下游业务服务,组装学生视角的复合数据
  2. 裁剪:将下游返回的领域数据裁剪为学生端所需的最小字段集
  3. 协议转换:对外暴露场景化 HTTP/GraphQL 端点,对内调用下游 REST/gRPC
  4. 缓存:聚合结果 Redis 短缓存 5-30s004 §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-chatSSE 流式) 智能答疑辅助
个性化学习路径 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 校验在 GatewayBFF 只读 x-user-id
领域事件发布 core-edu / content BFF 不发布事件,仅可选订阅事件用于实时推送
数据范围过滤 下游业务服务 Repository 层 BFF 透传 userId下游按 DataScope=SELF 过滤
班级管理 core-educlasses 模块) 学生只读自己所在班级
考试批改 core-edu 学生不能批改,只能查看成绩

3. 我与外部的契约

3.1 我消费的 proto message / 下游接口

B2 裁决落地BFF→Service 首次实现即用 gRPC@grpc/grpc-js + @grpc/proto-loader通过 DownstreamClient 抽象B8 裁决,复用 shared-ts3 个 BFF 统一。proto 即契约,不再是"文档"。

下游服务 proto service gRPC methodB2 裁决) 用途
iam IamService.GetUserInfo iam.GetUserInfo 获取学生个人信息 + roles + dataScope
iam IamService.GetViewportsproto 缺失) iam.GetViewports 获取学生端导航视口
iam IamService.GetEffectivePermissionsproto 缺失) iam.GetEffectivePermissions 获取有效权限列表
classescore-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 ChapterServiceproto 缺失) content.ListChapters 查章节
content QuestionServiceproto 缺失) 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.StreamChatserver-streamingB2 裁决) AI 答疑

3.2 我暴露的 GraphQL APIstudent-bff 对外)

B1 裁决落地P2 起直接 GraphQL Yoga禁止 REST 渐进schema 存放于 packages/shared-ts/contracts/graphql/student-bff.schema.graphqlpresident §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

Query14 个字段)

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 通知未读数

Mutation2 个字段)

Mutation 字段 下游 权限点 说明
submitHomework core-edu.SubmitHomework HOMEWORK_SUBMIT 提交作业B4 强制 userId
markNotificationAsRead msg.MarkAsRead NOTIFICATION_UPDATE 标记通知已读B4 强制 userId

Subscription1 个字段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_UNAUTHORIZEDBFF_STUDENT_BAD_GATEWAY
下游错误透传 下游服务错误码原样返回 CLASSES_NOT_FOUNDIAM_USER_NOT_FOUND

错误类清单(shared/errors/application-error.ts11 个类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 场景 BJWT/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 阶段不订阅 KafkaP5 起 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 YogaB1 裁决) P2 起直接 GraphQL + DataLoader禁止 REST 渐进schema 存放 packages/shared-ts/contracts/graphql/student-bff.schema.graphql
下游通信 gRPCB2 裁决) @grpc/grpc-js + @grpc/proto-loaderDownstreamClient 抽象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/functionsbranches ≥ 70%

4.1 GraphQL 设计决策(已裁决 B1

B1 裁决student-bff 从 P2 起直接采用 GraphQL Yoga + DataLoader禁止 REST 渐进。

裁决结论

  • API 风格GraphQL Yoga over HTTPSSE 传输 Subscription
  • DataLoader:解决 N+1 查询问题,按下游服务分批聚合
  • schema 存放packages/shared-ts/contracts/graphql/student-bff.schema.graphqlpresident §2.2.1
  • 分页规范Relay Cursor Connections{ edges, pageInfo, totalCount }
  • 降级模式:方案 Bpresident §2.6success=true + data 内 degraded=true + degradedFields
  • 越权防御B4 强制自我越权防御AuthorizationGuard 拦截president §2.9 方案 DDEV_MODE 放行)

已落地的 GraphQL 核心文件

文件 职责
src/shared/graphql/yoga.ts GraphQL Yoga 实例 + context 构建
src/shared/graphql/dataloader.ts DataLoader 工厂(按下游服务分批)
src/student/resolvers/*.resolver.ts Query/Mutation/Subscription Resolver6 个文件)
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 成绩查看
依赖上游阶段产出 P1api-gateway 路由骨架 + classes 黄金模板 + shared-proto、P2iam 认证 + 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 豁免 @RequirePermissionB3 裁决但强制自我越权防御B4AuthorizationGuard
错误码前缀统一 CLASSES_ BFF_STUDENT_ B5 裁决BFF 在前,非 STUDENT_BFF_
loggerpino shared/observability/logger.ts 复制 teacher-bff 实现 service 名改 student-bff
metricsprom-client /metrics 端点 复制 teacher-bff main.ts 注册方式 指标名前缀 student_bff_
tracerOpenTelemetry 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 服务未实现查询 APIanalytics.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/viewportsproto 补全后切换
出勤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 + DataLoaderP2 起直接 GraphQL禁止 REST 渐进)
下游通信 B2 gRPC 首次实现即用(@grpc/grpc-js + @grpc/proto-loader禁止 HTTP fetch
BFF 权限校验 B3 BFF 豁免 @RequirePermission,但透传 x-user-id 给下游校验
自我越权防御 B4 强制 B4 越权防御AuthorizationGuard场景 A + 场景 Bpresident §2.9 方案 D DEV_MODE 放行)
错误码前缀 B5 BFF_STUDENT_BFF 在前,非 STUDENT_BFF_
Redis 缓存 B6 5-30s 短缓存TTL ±20% 随机抖动防雪崩
Kafka 订阅 B7 P2-P4 不订阅P5 起订阅 7 个 topicRedis SETNX event_id 幂等去重
DownstreamClient B8 抽象复用 shared-ts回写 teacher-bff3 个 BFF 统一使用
降级模式 president §2.6 方案 Bsuccess=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
端口分配 - 3009HTTP无 gRPC 端口对外

7.3 跨模块协作需求(需提交 coord 协调)

需求 涉及 AI 协调内容
api-gateway 新增 /student 路由 ai01 在 main.go + config.go 新增 StudentBffURL 字段 + 路由块
docker-compose.deploy.yml 新增 student-bff 服务定义 coordinfra 端口 3009加入 edu-net + edu-shared 网络
full-stack-runbook 端口矩阵更新 coorddocs 追加 3009 行
004 架构图状态更新 coorddocs student-bff 状态从"📐 需设计"改为" 已实现"
shared-proto 补全 content.protoChapter/Question coord P4 阶段 content 服务落地前补全
shared-proto 补全 iam.protoViewport/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 全部代码已实现。