# 模块架构设计文档 — student-bff
> AI 标识:ai04
> 阶段:阶段 2(模块架构设计)
> 日期:2026-07-09
> 状态:待 coord 交叉审查
> 关联文档:
>
> - [阶段 1 理解确认书](./01-understanding.md)
> - [阶段 1 服务审计表](./02-audit.md)
> - [004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md)
> - [ai-allocation §7 阶段 2 模板](../../../docs/architecture/ai-allocation.md)
> - [pending-features P3](../../../docs/architecture/roadmap/pending-features.md)
> - [project_rules](../../../.trae/rules/project_rules.md)
> - [多 AI 协作指南](../../../docs/standards/multi-ai-collaboration.md)
> - 参考实现:[teacher-bff](../../teacher-bff/)、[classes 黄金模板](../../classes/)
---
## 0. 设计原则与文档导读
### 0.1 设计原则
| # | 原则 | 在 student-bff 的具体体现 |
| --- | ------------------ | ----------------------------------------------------------------------------------------------------------------- |
| P1 | 单一职责 | student-bff 仅做"学习场景域聚合 + 裁剪 + 协议转换",不持有业务状态、不直接访问 DB |
| P2 | 契约先行 | 所有跨服务调用先对齐 proto / REST 契约;契约变更通过 coord 统一管理 |
| P3 | DataScope 严格隔离 | 学生视角强制 `DataScope=SELF`,BFF 层透传 `x-user-id`,下游按 userId 过滤;BFF 不做权限决策但做"自我越权防御" |
| P4 | 渐进演进 | 通信协议(REST → gRPC)、API 风格(REST → GraphQL)、推送通道(同步 → SSE/WebSocket)按阶段演进,预留切换点 |
| P5 | 故障隔离 | 下游调用容错(超时 + 重试 + 熔断 + 降级),单服务故障不影响聚合整体,Dashboard 部分失败返回 partial 数据 + 警告位 |
| P6 | 可观测性三支柱 | pino 日志 + prom-client 指标 + OpenTelemetry 链路,全链路 traceId 透传 |
| P7 | 黄金模板对齐 | 1:1 克隆 teacher-bff 模式,与 classes/teacher-bff 共享 shared/ 实现,差异点收敛到 11 处命名改造 |
| P8 | 可逆决策优于不可逆 | API 风格、缓存策略、熔断阈值、推送通道均为可逆决策;端口分配、错误码前缀、路由前缀为不可逆决策需提前固化 |
| P9 | 为未来场景预留 | 多角色复用(学习委员/课代表)、多端适配(H5/小程序)、AI 答疑流式、家长查询子女数据等场景需在设计上预留扩展点 |
### 0.2 与阶段 1 文档的差异修正
> 阶段 1 文档(01-understanding.md / 02-audit.md)中将错误码前缀写为 `STUDENT_BFF_`,与 [004 §11.4 错误码前缀矩阵](../../../docs/architecture/004_architecture_impact_map.md#114-错误码前缀矩阵) 规定的 `BFF_STUDENT_`(BFF 在前)不一致。本设计文档统一修正为 **`BFF_STUDENT_`**,对齐 teacher-bff 的 `BFF_TEACHER_`、parent-bff 的 `BFF_PARENT_` 模式。阶段 1 文档保留历史记录,以本文档为准。
### 0.3 文档结构
1. [模块内部分层图](#1-模块内部分层图) — 物理分层与调用链
2. [领域模型(聚合视图)](#2-领域模型聚合视图) — BFF 无领域模型,定义"场景聚合"概念
3. [数据模型(缓存与 DTO)](#3-数据模型缓存与-dto) — BFF 不持 DB,定义 Redis Schema 与传输 DTO
4. [API 设计](#4-api-设计) — 14 个端点 + 阶段化交付
5. [事件设计](#5-事件设计) — BFF 订阅事件用于实时推送(P5)
6. [横切关注点对齐清单](#6-横切关注点对齐清单) — 完整对齐 classes/teacher-bff
7. [与其他模块的交互点](#7-与其他模块的交互点契约清单) — 完整契约矩阵
8. [风险与假设](#8-风险与假设) — 技术风险、外部依赖、未决决策
9. [演进路线图](#9-演进路线图) — P3/P4/P5/P6 各阶段演进路径(长远规划)
10. [扩展点设计](#10-扩展点设计) — 为未来场景预留
11. [性能与容量规划](#11-性能与容量规划)
12. [安全与合规](#12-安全与合规)
13. [可观测性详细设计](#13-可观测性详细设计)
14. [实施清单](#14-实施清单)
---
## 1. 模块内部分层图
### 1.1 物理分层
```mermaid
graph TB
subgraph Client["客户端"]
Portal[student-portal
Next.js MF Remote]
Mobile[H5/小程序
未来扩展]
end
subgraph Gateway["网关层"]
APIGW[api-gateway
路由 + JWT 鉴权 + 限流]
end
subgraph StudentBFF["student-bff(本模块)"]
direction TB
Controller["@Controller('student')
HTTP 入口 + Zod 校验 + ActionState 信封"]
Service["StudentService
聚合编排 + DataScope 透传"]
Aggregator["Aggregator 层
场景化聚合策略"]
Transformer["Transformer 层
响应裁剪 + 视口过滤"]
Cache["Cache 层
Redis 5-30s 短缓存"]
Client2["DownstreamClient
统一封装 fetch + 超时 + 重试 + traceId 透传"]
SSE["SSE Streamer
P5 AI 答疑流式响应"]
EventSub["Event Subscriber
P5 订阅 Kafka 推送给 push-gateway"]
CrossCutting["横切关注点
GlobalErrorFilter + Logger + Metrics + Tracer + Health"]
Controller --> Service
Service --> Aggregator
Aggregator --> Cache
Cache --> Client2
Aggregator --> Transformer
Controller -.可选.-> SSE
EventSub -.P5.-> Client2
CrossCutting -.拦截.-> Controller
end
subgraph Downstream["下游业务服务"]
IAM[iam
:3002]
CoreEdu[core-edu
:3004]
Content[content
:3005 P4]
Msg[msg
:3007 P5]
DataAna[data-ana
:3006 P4]
AI[ai
:3008 P5]
end
subgraph Push["推送层"]
PushGW[push-gateway
:8081 P5]
end
subgraph Infra["基础设施"]
Redis[(Redis 7)]
Kafka[(Kafka)]
OTel[OTLP Collector]
Prom[Prometheus]
end
Portal --> APIGW
Mobile --> APIGW
APIGW -->|HTTP /api/v1/student/*| Controller
Client2 -->|HTTP / gRPC P3+| IAM
Client2 -->|HTTP / gRPC P3+| CoreEdu
Client2 -->|HTTP / gRPC P4+| Content
Client2 -->|HTTP / gRPC P5+| Msg
Client2 -->|HTTP / gRPC P4+| DataAna
Client2 -->|HTTP / gRPC P5+| AI
Cache --> Redis
EventSub --> Kafka
EventSub -.推送.-> PushGW
CrossCutting --> OTel
CrossCutting --> Prom
```
### 1.2 调用链详解
#### 1.2.1 同步聚合链(P3 主要链路)
```mermaid
sequenceDiagram
participant P as student-portal
participant GW as api-gateway
participant C as StudentController
participant S as StudentService
participant A as Aggregator
participant Cache as Redis
participant DC as DownstreamClient
participant IAM as iam
participant Core as core-edu
P->>GW: GET /api/v1/student/dashboard
Authorization: Bearer
GW->>GW: RS256 公钥校验 + 提取 userId
GW->>C: GET /student/dashboard
x-user-id: u-xxx
x-request-id: r-xxx
C->>C: extractUserId(req) → u-xxx
Zod 校验 query
C->>S: getDashboard(u-xxx)
S->>A: aggregateDashboard(u-xxx)
A->>Cache: GET student:dashboard:u-xxx
alt 缓存命中
Cache-->>A: { data, cachedAt }
A-->>S: data
else 缓存未命中
par 并行下游
A->>DC: callIAM('/iam/me', u-xxx)
DC->>IAM: GET /iam/me x-user-id: u-xxx
x-request-id: r-xxx
IAM-->>DC: 200 { user, roles, dataScope }
and
A->>DC: callCoreEdu('/homework/class/:cid', u-xxx)
DC->>Core: GET /homework/class/:cid x-user-id: u-xxx
Core-->>DC: 200 { homework: [...] }
end
DC-->>A: 合并结果 + 部分失败标记
A->>A: Transformer 裁剪 + 视口过滤
A->>Cache: SET student:dashboard:u-xxx EX 15
A-->>S: data
end
S-->>C: DashboardData
C-->>GW: 200 { success: true, data, meta: { cachedAt, partial } }
GW-->>P: 200 响应
```
#### 1.2.2 SSE 流式链(P5 AI 答疑)
```mermaid
sequenceDiagram
participant P as student-portal
participant GW as api-gateway
participant C as StudentController
participant SSE as SSE Streamer
participant AI as ai 服务
P->>GW: POST /api/v1/student/ai/stream-chat
Accept: text/event-stream
GW->>C: POST /student/ai/stream-chat
C->>SSE: streamChat(messages, model)
SSE->>AI: POST /ai/stream-chat
Authorization: Bearer
loop 流式分块
AI-->>SSE: chunk: {content: "...", done: false}
SSE-->>P: data: {content: "...", done: false}
end
AI-->>SSE: chunk: {done: true}
SSE-->>P: data: {done: true}
SSE-->>C: stream closed
```
#### 1.2.3 事件订阅推送链(P5)
```mermaid
sequenceDiagram
participant K as Kafka
participant ES as EventSubscriber
participant DS as DownstreamClient
participant Push as push-gateway
participant P as student-portal
K->>ES: homework.graded (studentId=u-xxx, grade=90)
ES->>ES: 解析 studentId → 查 Redis 在线 session
ES->>DS: callPushGW('/push/user/u-xxx', payload)
DS->>Push: POST /push/user/u-xxx
Push-->>P: WebSocket 推送
{type: 'homework.graded', payload}
```
### 1.3 与 teacher-bff 分层对比
| 层 | teacher-bff(现状) | student-bff(设计目标) | 改进理由 |
| ---------------- | ------------------- | --------------------------------------------------------------- | ---------------------------- |
| Controller | ✅ 有 | ✅ 复制 | 对齐 |
| Service | ✅ 有 | ✅ 复制 + 拆分 Aggregator/Transformer | 单一职责 |
| Aggregator | ❌ 散落在 Service | ✅ 独立层,封装并行调用策略 | 解耦编排逻辑,便于测试 |
| Transformer | ❌ 无 | ✅ 独立层,裁剪响应字段 + 视口过滤 | 减少冗余字段,适配多端 |
| Cache | ❌ 无 | ✅ NestJS CacheInterceptor + Redis | 对齐 004 §6.2 BFF 混合读策略 |
| DownstreamClient | ❌ 散落 fetch() | ✅ 统一封装:超时 + 重试 + 熔断 + traceId 透传 + 错误信封归一化 | 解决 teacher-bff 痛点 |
| SSE Streamer | ❌ 无 | ✅ P5 引入(封装 ai 服务的 stream-chat) | AI 答疑流式响应 |
| EventSubscriber | ❌ 无 | ✅ P5 引入(订阅 Kafka → push-gateway) | 实时推送 |
> **改进点是否回写 teacher-bff 由 coord 仲裁**(避免技术栈分裂)。本文档定义的 DownstreamClient/Aggregator/Transformer 三层抽象可作为 BFF 模式 v2 的参考实现,待 coord 决策是否回写。
---
## 2. 领域模型(聚合视图)
> BFF 不持有 DDD 领域模型(不写 DB、不定义聚合根)。本节定义 student-bff 的"**场景聚合视图**"概念,用于组织 Service 层逻辑。
### 2.1 场景聚合视图清单
| 聚合视图 | 业务含义 | 主要下游服务 | 读写特性 | DataScope |
| -------------------- | ---------------------------- | ------------------------- | --------------- | --------- |
| StudentDashboard | 学生首页:个人信息+待办+未读 | iam + core-edu + msg | 读 / 聚合 | SELF |
| StudentExams | 我的考试列表(即将到来) | core-edu | 读 | SELF |
| StudentHomework | 我的作业列表 + 提交 | core-edu | 读 + 写(提交) | SELF |
| StudentGrades | 我的成绩历史 | core-edu | 读 | SELF |
| StudentNotifications | 消息中心:列表 + 已读 | msg | 读 + 写(已读) | SELF |
| StudentContent | 教材 + 章节 + 题库练习 | content | 读 | SELF |
| StudentAnalytics | 学情诊断 + 学习趋势 | data-ana | 读 | SELF |
| StudentAI | AI 答疑(同步 + 流式) | ai | 写(chat) | SELF |
| StudentSchedule | 我的课表(未来扩展) | core-edu(schedule 域) | 读 | SELF |
| StudentAttendance | 我的考勤(未来扩展) | core-edu(attendance 域) | 读 | SELF |
### 2.2 聚合视图间关系
```mermaid
graph LR
Dashboard[StudentDashboard
聚合视图]
Dashboard --> Exams[StudentExams]
Dashboard --> Homework[StudentHomework]
Dashboard --> Notif[StudentNotifications]
Dashboard --> Grades[StudentGrades]
Homework -.提交后触发.-> Grades
Grades -.数据流.-> Analytics[StudentAnalytics]
Analytics -.推荐.-> Content[StudentContent]
Content -.知识点弱项.-> AI[StudentAI]
Schedule[StudentSchedule
未来扩展]
Attendance[StudentAttendance
未来扩展]
Dashboard -.未来.-> Schedule
Dashboard -.未来.-> Attendance
```
### 2.3 DataScope=SELF 的强制实现
BFF 不做权限决策(对齐 teacher-bff),但做**自我越权防御**:
```typescript
// StudentService 伪代码示意
async listGrades(userId: string, query: ListGradesQuery): Promise {
// 防御:学生只能查自己的成绩
if (query.studentId && query.studentId !== userId) {
throw new ForbiddenError('STUDENT_CANNOT_VIEW_OTHERS_GRADES', {
requested: query.studentId,
actual: userId,
});
}
// 强制 studentId = userId
const safeQuery = { ...query, studentId: userId };
return this.client.callCoreEdu('/grades/student/' + userId, safeQuery);
}
```
> **设计权衡**:BFF 层做"自我越权防御"是 P3 安全硬化项。即使下游 core-edu 也按 DataScope 过滤,BFF 层先拦截可避免无效下游调用、提升审计能力、降低跨用户数据泄露风险。这与 teacher-bff 完全透传 x-user-id 不同,因为学生场景的越权风险面更敏感(成绩/作业/学情)。
---
## 3. 数据模型(缓存与 DTO)
> BFF 不持有 DB Schema。本节定义 Redis 缓存 key 规范、TTL 策略、传输 DTO。
### 3.1 Redis 缓存 Schema
#### 3.1.1 Key 命名规范
| 用途 | Key 模式 | TTL | 失效策略 |
| -------------- | --------------------------------------------- | ----- | --------------------------------- |
| 学生 Dashboard | `student:dashboard:{userId}` | 15s | 短 TTL + 主动失效(成绩发布事件) |
| 学生考试列表 | `student:exams:{userId}:{classId}` | 30s | 短 TTL |
| 学生作业列表 | `student:homework:{userId}:{classId}` | 30s | 短 TTL + 提交后失效 |
| 学生成绩列表 | `student:grades:{userId}:{page}` | 60s | 短 TTL + 成绩发布事件 |
| 学生通知列表 | `student:notifications:{userId}:{page}` | 15s | 短 TTL + 已读后失效 |
| 教材列表 | `student:textbooks:{gradeId}:{subjectId}` | 300s | 中 TTL |
| 章节树 | `student:chapters:{textbookId}` | 300s | 中 TTL |
| 题库列表 | `student:questions:{knowledgePointId}:{page}` | 60s | 短 TTL |
| 学情诊断 | `student:analytics:weakness:{userId}` | 300s | 中 TTL(每日刷新) |
| 学习趋势 | `student:analytics:trend:{userId}:{range}` | 600s | 长 TTL(趋势变化慢) |
| 学生视口缓存 | `student:viewports:{userId}` | 300s | 中 TTL + 角色变更事件 |
| 在线 session | `student:session:{userId}` | 30min | 滑动过期(P5 推送用) |
| SSE 连接映射 | `student:sse:{userId}` | 30min | 连接关闭即删 |
> **Key 设计原则**:
>
> - 全部以 `student:` 前缀,避免与 teacher-bff (`teacher:`)、parent-bff (`parent:`) 冲突
> - 按 userId 维度隔离,便于按用户失效
> - TTL 分档:15s(高频变更)/ 30-60s(中频)/ 300s(低频)/ 600s(趋势)
#### 3.1.2 缓存失效策略
```mermaid
flowchart TD
W[写操作:提交作业/已读通知] --> INV[主动失效相关 key]
E[Kafka 事件:成绩发布/作业批改] --> SUB[EventSubscriber 消费]
SUB --> INV2[失效 student:grades:userId:*]
T[TTL 到期] --> NATURAL[自然过期]
INV --> REDIS[DEL key]
INV2 --> REDIS
```
### 3.2 传输 DTO 设计
> DTO 用 Zod schema 定义,自动推导 TS 类型,避免 `unknown` 滥用(解决 teacher-bff 痛点)。
#### 3.2.1 下游响应包装
```typescript
// shared/dto/downstream-envelope.ts
import { z } from "zod";
export const DownstreamEnvelopeSchema = (
dataSchema: T,
) =>
z.object({
success: z.boolean(),
data: dataSchema.optional(),
error: z
.object({
code: z.string(),
message: z.string(),
details: z.unknown().optional(),
traceId: z.string().optional(),
})
.optional(),
});
export type DownstreamEnvelope = z.infer<
ReturnType>>
>;
```
#### 3.2.2 学生端核心 DTO
```typescript
// student/dto/student-dashboard.dto.ts
export const StudentDashboardSchema = z.object({
user: z.object({
id: z.string(),
name: z.string(),
avatar: z.string().nullable(),
grade: z.string(),
class: z.object({ id: z.string(), name: z.string() }),
}),
pendingHomework: z.array(
z.object({
id: z.string(),
title: z.string(),
dueDate: z.string(),
subject: z.string().optional(),
}),
),
upcomingExams: z.array(
z.object({
id: z.string(),
title: z.string(),
examDate: z.string(),
daysLeft: z.number(),
}),
),
unreadNotifications: z.number(),
lastGrade: z
.object({ examTitle: z.string(), score: z.number(), date: z.string() })
.nullable(),
});
export type StudentDashboard = z.infer;
```
#### 3.2.3 输入 Schema(Zod)
```typescript
// student/dto/student-inputs.dto.ts
export const SubmitHomeworkSchema = z.object({
answers: z
.array(
z.object({
questionId: z.string(),
content: z.string().max(5000),
attachments: z.array(z.string().url()).max(5).optional(),
}),
)
.min(1)
.max(100),
});
export const ListGradesQuerySchema = z.object({
page: z.coerce.number().int().min(1).default(1),
pageSize: z.coerce.number().int().min(1).max(50).default(20),
subject: z.string().optional(),
startDate: z.string().datetime().optional(),
endDate: z.string().datetime().optional(),
});
export const AIChatSchema = z.object({
messages: z
.array(
z.object({
role: z.enum(["user", "assistant"]),
content: z.string().max(8000),
}),
)
.min(1)
.max(20),
model: z
.enum(["gpt-4o-mini", "baichuan-53b", "local-qwen-7b"])
.default("gpt-4o-mini"),
context: z
.object({ subject: z.string(), knowledgePointId: z.string().optional() })
.optional(),
});
```
### 3.3 视口裁剪模型
> 学生端视口来自 iam 的 `getEffectivePermissions`,BFF 用视口过滤可见字段。
```typescript
// student/transformers/viewport-filter.ts
export interface StudentViewport {
// L1 导航:可见菜单项
navigation: string[]; // ['dashboard', 'homework', 'grades', 'content', 'analytics', 'ai']
// L4 数据范围:学生固定 SELF,但可细分(如禁看历史成绩)
dataScope: {
showHistoryGrades: boolean; // 是否显示历史成绩
showClassRanking: boolean; // 是否显示班级排名
enableAIChat: boolean; // 是否启用 AI 答疑
};
}
export function filterByViewport(
data: T,
viewport: StudentViewport,
): Partial {
// 按视口裁剪字段,如 enableAIChat=false 则隐藏 AI 入口数据
}
```
---
## 4. API 设计
### 4.1 端点全清单
> 路由前缀:`/student`(对齐 teacher-bff 用 `/teacher` 命名规律,BFF 用角色单数无 `-bff` 后缀)
> 网关路径:`/api/v1/student/*` → api-gateway 剥离 `/api/v1` 后代理到 student-bff:3009
> 响应信封:统一 ActionState([004 §11.5](../../../docs/architecture/004_architecture_impact_map.md#115-统一响应信封actionstate)),成功 `{success: true, data, meta?}`,失败 `{success: false, error: {code, message, details?, traceId?}}`
| # | method | path | 聚合下游 | 权限点(透传给下游) | 阶段 | 说明 |
| --- | ------ | ------------------------------------ | -------------------- | ------------------------- | ---- | -------------------- |
| 1 | GET | `/student/dashboard` | iam + core-edu + msg | STUDENT_DASHBOARD_READ | P3 | 学生首页聚合 |
| 2 | GET | `/student/viewports` | iam | STUDENT_VIEWPORT_READ | P3 | 学生端视口配置 |
| 3 | GET | `/student/exams` | core-edu | STUDENT_EXAM_READ | P3 | 即将到来的考试 |
| 4 | GET | `/student/exams/:id` | core-edu | STUDENT_EXAM_READ | P3 | 考试详情 |
| 5 | GET | `/student/homework` | core-edu | STUDENT_HOMEWORK_READ | P3 | 我的作业列表 |
| 6 | GET | `/student/homework/:id` | core-edu | STUDENT_HOMEWORK_READ | P3 | 作业详情(含题目) |
| 7 | POST | `/student/homework/:id/submit` | core-edu | STUDENT_HOMEWORK_SUBMIT | P3 | 提交作业(P3 核心) |
| 8 | GET | `/student/grades` | core-edu | STUDENT_GRADE_READ | P3 | 我的成绩(仅自己) |
| 9 | GET | `/student/grades/:examId` | core-edu | STUDENT_GRADE_READ | P3 | 单次考试我的成绩 |
| 10 | GET | `/student/notifications` | msg | STUDENT_NOTIFICATION_READ | P5 | 消息列表 |
| 11 | POST | `/student/notifications/:id/read` | msg | STUDENT_NOTIFICATION_READ | P5 | 标记已读 |
| 12 | GET | `/student/textbooks` | content | STUDENT_CONTENT_READ | P4 | 教材列表 |
| 13 | GET | `/student/textbooks/:id/chapters` | content | STUDENT_CONTENT_READ | P4 | 章节树 |
| 14 | GET | `/student/questions` | content | STUDENT_CONTENT_READ | P4 | 题库(按知识点过滤) |
| 15 | GET | `/student/knowledge-points/:id/path` | content | STUDENT_CONTENT_READ | P4 | 个性化学习路径 |
| 16 | GET | `/student/analytics/weakness` | data-ana | STUDENT_ANALYTICS_READ | P4 | 学情诊断 |
| 17 | GET | `/student/analytics/trend` | data-ana | STUDENT_ANALYTICS_READ | P4 | 学习趋势 |
| 18 | POST | `/student/ai/chat` | ai | STUDENT_AI_CHAT | P5 | AI 答疑(同步) |
| 19 | POST | `/student/ai/stream-chat` | ai | STUDENT_AI_CHAT | P5 | AI 答疑(SSE 流式) |
| 20 | GET | `/student/schedule` | core-edu | STUDENT_SCHEDULE_READ | P3+ | 我的课表(未来扩展) |
| 21 | GET | `/student/attendance` | core-edu | STUDENT_ATTENDANCE_READ | P4+ | 我的考勤(未来扩展) |
### 4.2 详细 API 规格(P3 必交付端点)
#### 4.2.1 GET `/student/dashboard`
**请求**:
```
GET /student/dashboard
Headers:
x-user-id: u-stu-001
x-request-id: r-abc123
cookie: access_token=
Query: ?classId=c-001 (optional, 默认从 iam 推导)
```
**响应 200**:
```json
{
"success": true,
"data": {
"user": {
"id": "u-stu-001",
"name": "张三",
"avatar": null,
"grade": "高一",
"class": { "id": "c-001", "name": "高一(1)班" }
},
"pendingHomework": [
{
"id": "h-001",
"title": "数学作业第三章",
"dueDate": "2026-07-12",
"subject": "数学"
}
],
"upcomingExams": [
{
"id": "e-001",
"title": "期中考试",
"examDate": "2026-07-15",
"daysLeft": 6
}
],
"unreadNotifications": 3,
"lastGrade": { "examTitle": "月考", "score": 92, "date": "2026-07-01" }
},
"meta": {
"cachedAt": "2026-07-09T10:00:00Z",
"partial": false,
"degradedServices": []
}
}
```
**部分降级响应**(下游 msg 不可达):
```json
{
"success": true,
"data": { "...": "...", "unreadNotifications": 0 },
"meta": {
"partial": true,
"degradedServices": ["msg"],
"traceId": "r-abc123"
}
}
```
**错误响应**:
| HTTP | code | 触发条件 |
| ---- | ---------------------------- | -------------------------------- |
| 401 | `BFF_STUDENT_UNAUTHORIZED` | 缺失 x-user-id 头 |
| 502 | `BFF_STUDENT_BAD_GATEWAY` | 必需下游全部失败(iam+core-edu) |
| 500 | `BFF_STUDENT_INTERNAL_ERROR` | 未捕获异常 |
#### 4.2.2 POST `/student/homework/:id/submit`(P3 核心 mutation)
**请求**:
```
POST /student/homework/h-001/submit
Headers:
x-user-id: u-stu-001
Content-Type: application/json
Body:
{
"answers": [
{ "questionId": "q-001", "content": "答案是..." },
{ "questionId": "q-002", "content": "解答过程...", "attachments": ["https://oss/edu/hw.pdf"] }
]
}
```
**响应 200**:
```json
{
"success": true,
"data": {
"submissionId": "sub-001",
"homeworkId": "h-001",
"submittedAt": "2026-07-09T10:30:00Z",
"status": "submitted"
}
}
```
**错误响应**:
| HTTP | code | 触发条件 |
| ---- | ------------------------------ | ------------------------------ |
| 400 | `BFF_STUDENT_VALIDATION_ERROR` | Zod 校验失败(answers 为空等) |
| 409 | `BFF_STUDENT_CONFLICT` | 重复提交 / 已过截止时间 |
| 502 | `BFF_STUDENT_BAD_GATEWAY` | core-edu 不可达 |
**BFF 行为**:
1. Zod 校验 body
2. 透传 `x-user-id` 给 core-edu
3. core-edu 写入 homework_submissions + Outbox 事件 `edu.teaching.homework.submitted`
4. BFF 失效 `student:homework:u-stu-001:*` 缓存
5. 返回提交回执
#### 4.2.3 GET `/student/grades`(自我越权防御示例)
**请求**:
```
GET /student/grades?page=1&pageSize=20&subject=数学
Headers: x-user-id: u-stu-001
```
**响应 200**:
```json
{
"success": true,
"data": {
"grades": [
{
"id": "g-001",
"examTitle": "月考",
"subject": "数学",
"score": 92,
"gradedAt": "2026-07-01T15:00:00Z",
"feedback": "解题思路清晰"
}
],
"pagination": { "page": 1, "pageSize": 20, "total": 5 }
}
}
```
**BFF 行为**:
1. 强制 `studentId = userId`(自我越权防御)
2. 调用 core-edu `GET /grades/student/u-stu-001`
3. Transformer 裁剪敏感字段(如 gradedBy 教师姓名,按视口过滤)
4. 60s Redis 缓存
### 4.3 API 风格决策(待 coord 仲裁)
| 选项 | 优势 | 劣势 | ai04 建议 |
| ----------------------------------- | ------------------------------ | ---------------------------------- | --------------------------------- |
| A. REST(对齐 teacher-bff 现状) | 实现快、与 teacher-bff 一致 | 多次往返、字段冗余 | **✅ P3 阶段采用** |
| B. GraphQL(对齐 004 §11.3 目标态) | 客户端按需取字段、聚合天然适合 | 与 teacher-bff 不一致、需引入 Yoga | P4+ 阶段统一升级三端 BFF 时再考虑 |
| C. REST + DataLoader(混合) | 解决 N+1 | 引入额外复杂度 | 不推荐 |
**决策记录**:P3 阶段 student-bff 采用 REST + Promise.allSettled + DownstreamClient 模式,对齐 teacher-bff 现状。GraphQL 演进路径在 [§9.2](#92-api-风格演进) 详述。
---
## 5. 事件设计
> BFF **不发布**领域事件(无业务事务)。BFF 可**订阅**事件用于实时推送(P5 阶段)。
### 5.1 订阅事件清单(P5 阶段)
| Topic | 事件 | 消费动作 | 幂等性 |
| -------------------------------- | ------------------------- | ----------------------------------------- | ------------------- |
| `edu.teaching.homework.assigned` | 教师布置作业 | 查 Redis 学生 session → 推送 push-gateway | event_id SETNX 去重 |
| `edu.teaching.homework.graded` | 作业批改完成 | 失效 `student:grades:*` + 推送 | event_id SETNX 去重 |
| `edu.teaching.exam.published` | 考试发布 | 失效 `student:exams:*` + 推送考试提醒 | event_id SETNX 去重 |
| `edu.teaching.exam.updated` | 考试更新(时间/地点变更) | 失效 `student:exams:*` + 推送变更通知 | event_id SETNX 去重 |
| `edu.teaching.grade.recorded` | 成绩录入 | 失效 `student:grades:*` + 推送成绩通知 | event_id SETNX 去重 |
| `edu.identity.user.role_changed` | 学生角色变更 | 失效 `student:viewports:userId` | event_id SETNX 去重 |
| `edu.notification.events` | 通知事件 | 推送给学生 | event_id SETNX 去重 |
### 5.2 事件订阅架构(P5)
```mermaid
graph LR
K[(Kafka)]
ES[EventSubscriber
NestJS Module]
Idempotency[(Redis SETNX
event_id 去重)]
Session[(Redis
学生在线 session)]
DC[DownstreamClient]
Push[push-gateway]
Cache[Redis Cache Invalidation]
K -->|homework.graded| ES
ES --> Idempotency
Idempotency -->|首次| Session
Session -->|在线| DC
DC --> Push
ES --> Cache
```
### 5.3 事件订阅消费者组设计
```yaml
# P5 阶段 Kafka 消费者组配置
consumer_group: student-bff-event-subscriber
topics:
- edu.teaching.homework.assigned
- edu.teaching.homework.graded
- edu.teaching.exam.published
- edu.teaching.exam.updated
- edu.teaching.grade.recorded
- edu.identity.user.role_changed
- edu.notification.events
commit_strategy: manual # 处理成功后手动 commit
retry_strategy:
max_retries: 3
backoff: exponential
dlq_topic: edu.student-bff.dlq
```
### 5.4 不订阅事件的设计决策
| 候选事件 | 是否订阅 | 理由 |
| -------------------------------- | --------- | ------------------------------------------------ |
| `edu.teaching.exam.deleted` | ❌ 不订阅 | 学生已查看的考试删除走 next render 自然刷新 |
| `edu.content.question.published` | ❌ 不订阅 | 题库变更不影响学生首页,按需查询即可 |
| `edu.insight.mastery.updated` | ❌ 不订阅 | 掌握度更新通过 data-ana 查询时获取,无需主动推送 |
| `edu.insight.ai.usage` | ❌ 不订阅 | AI 用量统计不推送给学生 |
---
## 6. 横切关注点对齐清单
### 6.1 权限装饰器决策
| 决策 | 选项 | ai04 建议 | 仲裁状态 |
| ------------------------------- | ----------------------------------------------------------------- | ----------------------------------------- | ------------- |
| BFF 是否加 `@RequirePermission` | A. 不加(对齐 teacher-bff,透传 x-user-id)
B. 加(双重校验) | **A**(BFF 是聚合层,权限由下游服务校验) | 待 coord 仲裁 |
| 自我越权防御 | A. 不做(依赖下游)
B. BFF 层做 userId 强制比对 | **B**(学生场景敏感,防御纵深) | 待 coord 仲裁 |
> **若 coord 选择 A 方案(不加装饰器)**:student-bff 不引入 `middleware/permission.guard.ts`,与 teacher-bff 一致。
> **若 coord 选择 B 方案(双重校验)**:student-bff 引入 PermissionGuard,但要避免与下游重复校验造成性能损耗,可只校验"导航级"权限(如能否进入 AI 答疑菜单),不校验"数据级"权限(留给下游)。
### 6.2 错误码清单(BFF_STUDENT_ 前缀)
| 错误码 | HTTP | 触发条件 | 详情字段 |
| --------------------------------- | ---- | ----------------------------------- | ---------------------------------------- |
| `BFF_STUDENT_VALIDATION_ERROR` | 400 | Zod 校验失败 | `{ field, message }` |
| `BFF_STUDENT_UNAUTHORIZED` | 401 | 缺失 x-user-id 头 | — |
| `BFF_STUDENT_FORBIDDEN` | 403 | 自我越权防御拦截 | `{ requested, actual }` |
| `BFF_STUDENT_NOT_FOUND` | 404 | 资源不存在(BFF 自身资源) | `{ resource, id }` |
| `BFF_STUDENT_CONFLICT` | 409 | 重复提交 / 状态冲突 | `{ reason }` |
| `BFF_STUDENT_BUSINESS_ERROR` | 422 | 业务规则违反 | `{ rule }` |
| `BFF_STUDENT_BAD_GATEWAY` | 502 | 下游服务返回非 ok 或 fetch rejected | `{ service, endpoint, status, traceId }` |
| `BFF_STUDENT_GATEWAY_TIMEOUT` | 504 | 下游调用超时 | `{ service, endpoint, timeoutMs }` |
| `BFF_STUDENT_SERVICE_UNAVAILABLE` | 503 | 熔断器开启 | `{ service, circuitState }` |
| `BFF_STUDENT_INTERNAL_ERROR` | 500 | 未捕获异常 | `{ traceId }` |
### 6.3 Logger(pino)
| 项 | 值 |
| ------------ | --------------------------------------------------------------------- |
| 文件位置 | `src/shared/observability/logger.ts` |
| service 字段 | `'student-bff'` |
| level | env.LOG_LEVEL(默认 `info`) |
| 输出 | JSON stdout |
| 字段 | `time, level, service, msg, traceId, userId, endpoint, duration, err` |
| 采样 | 生产环境 warn+ 100% 采样,info 10% 采样 |
### 6.4 Metrics(prom-client)
| 指标名 | 类型 | 标签 | 描述 |
| ----------------------------------------- | --------- | ------------------------------- | ------------------------------------------- |
| `student_bff_requests_total` | Counter | `method, path, status` | 请求总数 |
| `student_bff_request_duration_seconds` | Histogram | `method, path` | 请求延迟 |
| `student_bff_downstream_calls_total` | Counter | `service, endpoint, status` | 下游调用次数 |
| `student_bff_downstream_duration_seconds` | Histogram | `service, endpoint` | 下游调用延迟 |
| `student_bff_downstream_errors_total` | Counter | `service, endpoint, error_type` | 下游调用错误数 |
| `student_bff_cache_hits_total` | Counter | `cache_key_pattern` | 缓存命中 |
| `student_bff_cache_misses_total` | Counter | `cache_key_pattern` | 缓存未命中 |
| `student_bff_circuit_state` | Gauge | `service, state` | 熔断器状态(0=closed, 1=open, 2=half-open) |
| `student_bff_sse_connections` | Gauge | — | SSE 连接数(P5) |
| `student_bff_event_consumed_total` | Counter | `topic, event_type` | 事件消费数(P5) |
| `student_bff_event_pushed_total` | Counter | `topic, push_status` | 推送数(P5) |
### 6.5 Tracer(OpenTelemetry)
| 项 | 值 |
| --------------------- | ------------------------------------------------- |
| 文件位置 | `src/shared/observability/tracer.ts` |
| serviceName | `'student-bff'` |
| exporter | OTLP HTTP → collector |
| auto-instrumentations | http, nestjs-core, express, fetch, redis, kafka |
| span 属性 | `userId, endpoint, downstream.service, cache.hit` |
| 采样率 | 生产 10%,开发 100% |
| 上下文传播 | W3C Trace Context(traceparent 头) |
### 6.6 健康检查
| 端点 | 检查逻辑 | 响应 |
| ---------- | ---------------------------------------------------- | ------------------------------------------------- |
| `/healthz` | 进程存活(直接返回 ok) | `200 { status: 'ok', service: 'student-bff' }` |
| `/readyz` | P3:直接返回 ok
P4+:检查下游可达性(HEAD 请求) | `200 { status: 'ready', checks: {...} }` 或 `503` |
> **/readyz 增强方案(P4+)**:
>
> - 并行 HEAD 请求 iam / core-edu 的 /healthz
> - 任一关键下游不可达 → 返回 503(让 K8s 不分发流量)
> - 非关键下游(如 data-ana)不可达 → 返回 200 + `degraded: true`
> - 检查结果 5s Redis 缓存,避免高频探测
### 6.7 优雅关闭
```mermaid
sequenceDiagram
participant K8s as K8s/SIGTERM
participant App as NestApp
participant HTTP as HTTP Server
participant SSE as SSE Connections
participant Sub as EventSubscriber
participant Cache as Redis
participant Tracer as OTel
K8s->>App: SIGTERM
App->>App: 1. 拒绝新请求(readyz 返回 503)
App->>Sub: 2. 停止消费 Kafka(commit 最后 offset)
App->>SSE: 3. 通知所有 SSE 连接关闭(发送 done 事件)
App->>HTTP: 4. 等待 in-flight 请求完成(最多 10s)
App->>Cache: 5. 关闭 Redis 连接
App->>Tracer: 6. flush 剩余 span
App-->>K8s: 7. 进程退出
```
### 6.8 输入验证(Zod)
| 端点 | Zod Schema | 校验项 |
| -------------------------------------- | ----------------------- | -------------------------------------- |
| POST `/student/homework/:id/submit` | `SubmitHomeworkSchema` | answers 非空、每题 content ≤ 5000 字符 |
| POST `/student/notifications/:id/read` | 无 body | path param `id` 非空 |
| POST `/student/ai/chat` | `AIChatSchema` | messages 1-20 条、content ≤ 8000 字符 |
| GET `/student/grades` | `ListGradesQuerySchema` | page ≥ 1、pageSize 1-50 |
| 所有 GET 端点 | query schema | 分页参数、过滤参数 |
### 6.9 全局错误过滤器
- 文件:`src/shared/errors/global-error.filter.ts`
- 装饰:`@Catch()` 全局
- 行为:
1. ApplicationError → 按 statusCode + code 返回 ActionState
2. ZodError → 400 + `BFF_STUDENT_VALIDATION_ERROR` + details
3. 未知 Error → 500 + `BFF_STUDENT_INTERNAL_ERROR` + traceId
4. 注入 traceId(从 `x-request-id` 头或新生成)
### 6.10 Dockerfile
```dockerfile
# 多阶段构建,对齐 teacher-bff
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
COPY tsconfig.json nest-cli.json ./
COPY src/ ./src/
RUN pnpm run build
FROM node:22-alpine AS runtime
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --prod --frozen-lockfile
COPY --from=builder /app/dist ./dist
EXPOSE 3009
CMD ["node", "dist/main.js"]
```
---
## 7. 与其他模块的交互点(契约清单)
### 7.1 完整交互矩阵
| 方向 | 对方服务 | 协议 | 接口/事件 | 用途 | 阶段 |
| ------ | -------------- | ------------------ | --------------------------------------------------------------------------------------------------------------- | --------------------------- | ------ |
| 被调用 | api-gateway | HTTP | `/student/*`(全部端点) | 网关路由 | P3 |
| 被调用 | student-portal | HTTP | 同上(经网关) | 前端调用 | P3 |
| 调用 | iam | HTTP→gRPC P3+ | `GET /iam/me` / `GET /iam/viewports` / `GET /iam/permissions/effective` | 学生信息 + 视口 + 权限 | P3 |
| 调用 | core-edu | HTTP→gRPC P3+ | `GET /exams/class/:cid` / `GET /homework/class/:cid` / `POST /homework/:id/submit` / `GET /grades/student/:sid` | 考试/作业/成绩 | P3 |
| 调用 | content | HTTP→gRPC P4+ | `GET /textbooks` / `GET /chapters` / `GET /questions` / `GET /knowledge-points/:id/learning-path` | 教材/章节/题库/学习路径 | P4 |
| 调用 | data-ana | HTTP→gRPC P4+ | `GET /analytics/student/:id/weakness` / `GET /analytics/student/:id/trend` | 学情诊断 | P4 |
| 调用 | msg | HTTP→gRPC P5+ | `GET /notifications` / `POST /notifications/:id/read` | 消息中心 | P5 |
| 调用 | ai | HTTP→gRPC P5+ | `POST /ai/chat` / `POST /ai/stream-chat` | AI 答疑 | P5 |
| 调用 | push-gateway | HTTP | `POST /push/user/:userId` | 推送给在线学生 | P5 |
| 消费 | Kafka | Kafka | `edu.teaching.homework.assigned` | 推送作业通知 | P5 |
| 消费 | Kafka | Kafka | `edu.teaching.homework.graded` | 推送批改完成通知 + 失效缓存 | P5 |
| 消费 | Kafka | Kafka | `edu.teaching.exam.published` | 推送考试提醒 | P5 |
| 消费 | Kafka | Kafka | `edu.teaching.exam.updated` | 推送考试变更 | P5 |
| 消费 | Kafka | Kafka | `edu.teaching.grade.recorded` | 推送成绩 + 失效缓存 | P5 |
| 消费 | Kafka | Kafka | `edu.identity.user.role_changed` | 失效视口缓存 | P5 |
| 消费 | Kafka | Kafka | `edu.notification.events` | 推送通知 | P5 |
| 依赖 | shared-proto | 静态导入 | iam.proto / core_edu.proto / content.proto / analytics.proto / ai.proto / msg.proto / events.proto | 契约定义 | 跨阶段 |
| 依赖 | shared-ts | 静态导入(待建立) | BFF 通用工具(DownstreamClient、CacheKey 生成器) | 共享工具 | P3+ |
| 依赖 | Redis | TCP | 缓存 | 短缓存 | P3 |
| 依赖 | OTLP Collector | HTTP | trace 上报 | 可观测性 | P3 |
### 7.2 跨模块协作需求(需提交 coord 协调)
| # | 需求 | 涉及 AI | 阻塞阶段 | 协调内容 |
| --- | --------------------------------------------------------------------- | --------------------- | -------- | --------------------------------------------------------- |
| 1 | api-gateway 新增 `/student` 路由 | ai01 | P3 | 在 main.go + config.go 新增 `StudentBffURL` 字段 + 路由块 |
| 2 | docker-compose.deploy.yml 新增 student-bff 服务定义 | coord(infra) | P3 | 端口 3009,加入 edu-net + edu-shared 网络 |
| 3 | full-stack-runbook 端口矩阵更新 | coord(docs) | P3 | 追加 3009 行 |
| 4 | 004 §15 文档位置矩阵更新 | coord(docs) | P3 | student-bff 状态从"📐 需设计"改为"✅ 已实现" |
| 5 | shared-proto 补全 iam.proto(Viewport / EffectivePermissions) | coord | P3 | 当前走 REST,proto 补全后切换 gRPC |
| 6 | shared-proto 补全 content.proto(Chapter / Question / KnowledgePath) | coord | P4 | P4 content 服务落地前补全 |
| 7 | shared-proto 补全 core_edu.proto(Schedule / Attendance 域) | coord | P4+ | 学生课表/考勤未来扩展用 |
| 8 | buf.gen.yaml 补 gRPC 插件 | coord | P3 | 决定是否在 P3 升级到 gRPC 通信 |
| 9 | core-edu 启用 gRPC server | ai03(core-edu 负责) | P3 | student-bff 切换 gRPC 前提 |
| 10 | iam 启用 gRPC server | ai02 | P3 | 同上 |
| 11 | core-edu `POST /homework/:id/submit` REST 端点必须落地 | ai03 | P3 | student-bff P3 核心依赖 |
| 12 | core-edu `GET /grades/student/:sid` REST 端点必须落地 | ai03 | P3 | 学生查成绩依赖 |
| 13 | msg 服务落地 `/notifications` REST 端点 | ai05 | P5 | student-bff P5 消息中心依赖 |
| 14 | ai 服务落地 `/ai/chat` + `/ai/stream-chat` | ai06 | P5 | student-bff P5 AI 答疑依赖 |
| 15 | push-gateway 落地 `/push/user/:userId` | ai01 | P5 | student-bff P5 推送依赖 |
| 16 | data-ana 落地 `/analytics/student/:id/weakness` + trend REST | ai06 | P4 | student-bff P4 学情诊断依赖 |
### 7.3 与 teacher-bff / parent-bff 的复用与差异
| 维度 | teacher-bff(教师场景) | student-bff(学生场景) | parent-bff(家长场景) |
| ---------- | ---------------------------------------- | ------------------------------------------------------------------ | --------------------------------------------- |
| DataScope | CLASS(教师看本班) | SELF(学生只看自己) | CHILDREN(家长看绑定子女) |
| 越权防御 | 不做(透传 x-user-id) | **做**(强制 userId 比对) | **做**(强制 childId 必须在绑定列表) |
| 端口 | 3003 | 3009 | 3010 |
| 路由前缀 | `/teacher` | `/student` | `/parent` |
| 错误码前缀 | `BFF_TEACHER_` | `BFF_STUDENT_` | `BFF_PARENT_` |
| 指标前缀 | `teacher_bff_` | `student_bff_` | `parent_bff_` |
| 聚合复杂度 | 高(教师跨班跨年级) | 低(学生单维度) | 中(多子女切换) |
| 主要下游 | iam + core-edu + content + data-ana + ai | iam + core-edu(P3)→ + content + data-ana(P4)→ + msg + ai(P5) | iam + core-edu(P4)→ + msg + data-ana(P4+) |
---
## 8. 风险与假设
### 8.1 技术风险
| 风险 | 概率 | 影响 | 缓解措施 |
| ------------------------------- | ---- | ---- | ----------------------------------------------------------------------------- |
| 下游服务故障导致 Dashboard 全白 | 中 | 高 | Promise.allSettled 容错 + partial 标记 + 关键下游(iam)熔断降级返回基础信息 |
| AI 流式响应中断(SSE 断连) | 中 | 中 | 客户端断线重连机制 + 服务端清理孤儿连接 + last-event-id 续传 |
| Redis 缓存雪崩 | 低 | 高 | TTL 加随机抖动(±20%)+ 熔断 + 单飞模式(同 key 并发只放一个去下游) |
| Kafka 消费堆积 | 低 | 中 | 消费者组并行度配置 + DLQ + 告警阈值(lag > 1000) |
| 高并发作业提交(截止前扎堆) | 中 | 中 | 透传 core-edu 处理(Redis 分布式锁),BFF 层加 IP+userId 限流(透传 gateway) |
| 缓存与 DB 不一致 | 中 | 中 | 短 TTL(15-60s)+ 事件驱动主动失效 + 提交后立即 DEL |
| BFF 单点故障 | 低 | 高 | 无状态设计,K8s 多副本部署 + HPA |
### 8.2 外部依赖假设
| 假设 | 若假设不成立的影响 | Fallback 方案 |
| ------------------------------------------- | -------------------------- | --------------------------------------------------- |
| iam `/iam/me` 返回包含 classId | Dashboard 无法聚合待办作业 | 调用 core-edu 反查学生所在班级 |
| core-edu `POST /homework/:id/submit` 已实现 | P3 核心端点无法交付 | 阻塞 P3 退出标准,提请 coord 协调 ai03 |
| core-edu `GET /grades/student/:sid` 已实现 | 学生查成绩端点无法交付 | 阻塞 P3,提请 coord 协调 ai03 |
| api-gateway 已注册 `/student` 路由 | 前端请求 404 | P3 阻塞,提请 coord 协调 ai01 |
| Redis 已部署且网络可达 | 缓存层失效,性能下降 | Cache 层降级为内存 LRU(如 cache-manager 内存模式) |
| OTLP Collector 已部署 | 链路追踪缺失 | 不影响业务,仅日志降级 |
| Kafka 已部署且 topic 已创建 | P5 事件订阅无法实现 | P5 阻塞;P3/P4 不依赖 Kafka |
### 8.3 未决设计决策(待 coord 仲裁)
| # | 决策点 | 选项 | ai04 建议 | 影响范围 |
| --- | ---------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------- | ------------------------ |
| 1 | BFF API 风格 | A. REST(对齐 teacher-bff 现状)
B. GraphQL(对齐 004 §11.3 设计意图) | **A**(P3 阶段先 REST,P4+ 统一升级时再考虑) | 全部端点 |
| 2 | BFF 是否做权限校验 | A. 不校验(对齐 teacher-bff)
B. 加 `@RequirePermission` | **A**(聚合层不做权限决策) | 全部端点 |
| 3 | BFF 是否做自我越权防御 | A. 不做(依赖下游)
B. BFF 层强制 userId 比对 | **B**(学生场景敏感) | 成绩/作业/学情端点 |
| 4 | `/readyz` 检查逻辑 | A. 直接返回 ok(对齐 teacher-bff)
B. 检查下游可达性 | **A**(P3)/ **B**(P4+,下游可达性由 Prometheus 监控) | 健康检查 |
| 5 | Kafka 事件订阅时机 | A. P3 不订阅
B. P3 订阅 | **A**(push-gateway P5 才落地) | P5 推送功能 |
| 6 | 缓存策略 | A. 不缓存(对齐 teacher-bff)
B. Redis 5-30s 短缓存 | **B**(对齐 004 §6.2 BFF 混合读策略) | 全部 GET 端点 |
| 7 | 端口分配 | 3009 | **3009**(3001-3008 已用) | 部署 |
| 8 | 错误码前缀 | `STUDENT_BFF_`(阶段 1 文档) / `BFF_STUDENT_`(004 §11.4 规定) | **`BFF_STUDENT_`**(对齐 004 与 teacher-bff) | 全部错误码 |
| 9 | DownstreamClient/Aggregator/Transformer 抽象是否回写 teacher-bff | A. 不回写(仅 student-bff)
B. 回写(统一三端 BFF) | **B**(避免技术栈分裂,作为 BFF 模式 v2) | teacher-bff / parent-bff |
| 10 | 是否引入 NestJS CQRS 模块 | A. 不引入(简单 Service 即可)
B. 引入(Query/Command 分离) | **A**(BFF 不持领域模型,CQRS 收益不大) | Service 层结构 |
| 11 | SSE 实现 | A. 原生 Node Stream
B. 第三方库(如 @nestjs/axios + RxJS) | **A**(依赖少,可控) | P5 AI 答疑流式 |
| 12 | 是否在 P3 引入熔断器 | A. 不引入(依赖 gateway 熔断)
B. 引入(opossum 库) | **B**(BFF→下游单链路熔断,gateway 是入口熔断) | DownstreamClient |
---
## 9. 演进路线图
### 9.1 各阶段演进总览
```mermaid
graph LR
P3[P3 核心教学
M7-M10]
P4[P4 内容分析
M11-M13]
P5[P5 沟通AI
M14-M16]
P6[P6 硬化
M17-M18]
P3 --> P4 --> P5 --> P6
P3 -.REST + fetch.-> P3
P4 -.+content+data-ana.-> P4
P4 -.+gRPC iam/core-edu.-> P4
P5 -.+msg+ai+Kafka订阅.-> P5
P5 -.+SSE流式+WebSocket推送.-> P5
P6 -.+HPA+Istio mTLS.-> P6
P6 -.+全链路可观测.-> P6
```
### 9.2 API 风格演进
| 阶段 | API 风格 | 触发条件 | 迁移策略 |
| ---- | ------------------- | ------------------------------- | --------------------------------------------------------- |
| P3 | REST + fetch | 对齐 teacher-bff 现状 | — |
| P4 | REST + fetch + 缓存 | 引入 Redis 短缓存 | CacheInterceptor 透明引入 |
| P5+ | REST + gRPC 混合 | iam / core-edu gRPC server 启用 | DownstreamClient 内部根据 service 配置选择协议 |
| P6+ | GraphQL(可选) | coord 决策统一升级三端 BFF | REST 端点保留作为兼容,新增 `/graphql` 端点;前端逐步迁移 |
> **GraphQL 演进路径(若 coord 决策升级)**:
>
> 1. 引入 `@nestjs/graphql` + Apollo Server 或 GraphQL Yoga
> 2. 定义 schema:`Query.studentDashboard / Query.studentHomework / Mutation.submitHomework` 等
> 3. Resolver 复用 Service 层逻辑
> 4. 引入 DataLoader 解决 N+1(如一个 Dashboard 同时查多个学生成绩)
> 5. 前端逐步从 REST 切换到 GraphQL,REST 端点保留 6 个月兼容期
### 9.3 通信协议演进
| 阶段 | BFF → 业务服务协议 | 理由 |
| ---- | ------------------- | ----------------------------------------- |
| P3 | HTTP REST | 下游 gRPC server 未启用,对齐 teacher-bff |
| P4 | HTTP + gRPC 混合 | iam / core-edu 启用 gRPC,BFF 优先 gRPC |
| P5 | HTTP + gRPC + SSE | 引入 AI 流式 + Kafka 消费 |
| P6 | gRPC + Service Mesh | Istio mTLS + 流量治理 |
> **协议切换设计**:DownstreamClient 抽象层封装协议选择,根据 `env.IamUseGrpc=true/false` 切换,业务代码无感知。
### 9.4 推送通道演进
| 阶段 | 推送方式 | 触发场景 |
| ---- | ---------------------- | -------------------------- |
| P3 | 无推送 | 学生主动查询 |
| P5 | SSE 单向推送 | AI 答疑流式 + 成绩发布通知 |
| P5+ | WebSocket 双向推送 | 实时通知 + 在线状态 |
| P6+ | 移动端推送(FCM/APNs) | 离线推送(未来扩展) |
### 9.5 多角色复用演进
| 阶段 | student-bff 复用角色 | 视口差异化策略 |
| ---- | ---------------------------------- | --------------------- |
| P3 | 学生 | 单一视口 |
| P4+ | 学习委员(学生 + 班级汇总视口) | L1 增加"班级学情"菜单 |
| P5+ | 课代表(学生 + 学科作业收集视口) | L1 增加"作业收集"菜单 |
| P6+ | 走读生/住宿生差异化(作息/课程表) | L4 数据范围细化 |
> **设计预留**:视口模型(§3.3)已支持 `dataScope.showHistoryGrades` 等细粒度开关,新角色只需在 iam 配置视口,BFF 自动适配,无需改代码。
---
## 10. 扩展点设计
### 10.1 多端适配扩展(H5/小程序)
```mermaid
graph LR
A[student-bff API] --> B[Transformer 层]
B --> C[Web 版响应]
B --> D[H5 版响应(字段精简)]
B --> E[小程序版响应(字段精简 + 数据预取)]
```
**实现**:在 Transformer 层根据 `x-client-type` 头(web/h5/miniapp)选择不同的字段裁剪策略。
### 10.2 国际化扩展
```typescript
// 预留 i18n 接入点
export interface I18nContext {
locale: 'zh-CN' | 'en-US';
timezone: 'Asia/Shanghai' | 'America/Los_Angeles';
}
// Service 层接收 i18n context,透传给下游
async getDashboard(userId: string, i18n: I18nContext): Promise {
// 透传 Accept-Language 头给下游
}
```
### 10.3 多子女切换扩展(家长场景借鉴)
虽然 parent-bff 是独立服务,但 student-bff 的设计可为 parent-bff 提供借鉴:
| 共享模式 | student-bff 实现 | parent-bff 复用方式 |
| -------------------- | ---------------- | --------------------------- |
| DownstreamClient | 通用封装 | 直接复用,仅改 service 名 |
| Cache key 命名 | `student:*` | 改为 `parent:*` |
| 自我越权防御 | userId 强制比对 | 改为 childId 必须在绑定列表 |
| Transformer 视口过滤 | StudentViewport | 改为 ParentViewport |
| EventSubscriber | 订阅学生相关事件 | 订阅子女相关事件 |
### 10.4 离线模式扩展
未来支持学生端离线查看已加载的作业/教材:
| 端点 | 离线策略 |
| ------------------------------------- | --------------------------------------- |
| `GET /student/homework/:id` | 返回 ETag + Last-Modified,支持条件请求 |
| `GET /student/textbooks/:id/chapters` | 长缓存(300s)+ ETag |
### 10.5 AI 答疑增强扩展
```mermaid
graph LR
A[学生提问] --> B{是否需查知识点?}
B -- 是 --> C[调用 content 查知识点]
B -- 否 --> D[直接调 ai/chat]
C --> D
D --> E{是否需查学情?}
E -- 是 --> F[调用 data-ana 查弱项]
E -- 否 --> G[LLM 生成]
F --> G
G --> H[返回答案 + 引用知识点]
```
> 设计上 StudentService.AIChat 方法预留 context 参数(subject + knowledgePointId),未来可扩展为多步编排。
### 10.6 学习路径推荐扩展
```typescript
// 预留接口
async getRecommendedLearningPath(userId: string): Promise {
// 1. 查 data-ana 学情诊断
const weakness = await this.client.callDataAna(`/analytics/student/${userId}/weakness`);
// 2. 调 content 知识图谱
const path = await this.client.callContent(`/knowledge-points/${weakness.weakPoints[0].knowledgePointId}/learning-path`);
// 3. 返回个性化路径
return path;
}
```
---
## 11. 性能与容量规划
### 11.1 性能目标(SLO)
| 端点 | P50 延迟 | P95 延迟 | P99 延迟 | 错误率 |
| -------------------------------------- | ---------- | -------- | -------- | ------ |
| `GET /student/dashboard`(缓存命中) | 50ms | 100ms | 200ms | <0.1% |
| `GET /student/dashboard`(缓存未命中) | 300ms | 800ms | 1500ms | <0.5% |
| `GET /student/homework` | 100ms | 300ms | 500ms | <0.1% |
| `POST /student/homework/:id/submit` | 200ms | 500ms | 1000ms | <0.5% |
| `GET /student/grades` | 100ms | 300ms | 500ms | <0.1% |
| `POST /student/ai/stream-chat` | 200ms TTFT | 1s TTFT | 2s TTFT | <1% |
### 11.2 容量规划
| 维度 | P3 估算 | P5 估算 | P6 估算 |
| -------------- | -------- | ------- | ----------- |
| 日活学生 | 1,000 | 10,000 | 50,000 |
| QPS 峰值 | 50 | 500 | 2,500 |
| SSE 连接数 | 0 | 1,000 | 10,000 |
| Kafka 消费 TPS | 0 | 100 | 500 |
| Redis 内存 | 50MB | 500MB | 2GB |
| 实例数 | 1 | 2-3 | 5-10(HPA) |
| 单实例 CPU | 0.5 core | 1 core | 2 core |
| 单实例内存 | 256MB | 512MB | 1GB |
### 11.3 限流策略(透传 api-gateway)
| 端点 | 限流维度 | 阈值 |
| ----------------------------------- | ----------- | -------------- |
| `POST /student/homework/:id/submit` | userId | 10/min |
| `POST /student/ai/chat` | userId + IP | 30/min |
| `POST /student/ai/stream-chat` | userId | 5/min + 1 并发 |
| `GET /student/*`(读) | userId | 600/min |
---
## 12. 安全与合规
### 12.1 身份与认证
| 维度 | 实现 |
| ---------- | ---------------------------------------------------------------- |
| 认证 | JWT RS256 在 api-gateway 校验,BFF 仅读 `x-user-id` 头(不验签) |
| 会话 | 无状态(BFF 不持 session),Redis 仅缓存数据 |
| Token 刷新 | 透传 401 给前端,由前端走 refresh 流程 |
### 12.2 授权与数据隔离
| 维度 | 实现 |
| -------------- | --------------------------------------------------------------------- |
| 权限校验 | 透传 `x-user-id` 给下游,下游按 `@RequirePermission` + DataScope 校验 |
| 自我越权防御 | BFF 层强制 `studentId = userId`(成绩/作业/学情端点) |
| 跨班级数据隔离 | 学生只能查自己所在班级(classId 由 iam 推导,不接受前端传入) |
| AI 内容安全 | AI 答疑请求透传 content moderation(ai 服务侧实现) |
### 12.3 输入安全
| 维度 | 实现 |
| -------- | -------------------------------------------------------------------------------- |
| 输入验证 | 全部 Zod 校验,拒绝 unknown 字段 |
| SQL 注入 | BFF 无 DB,不涉及;下游用 Drizzle 参数化查询 |
| XSS | 响应 Content-Type: application/json,禁止 HTML 渲染;富文本由前端 DOMPurify 清洗 |
| CSRF | Cookie 必须 SameSite=Strict + 后端校验 Origin 头 |
| 文件上传 | 作业附件走 OSS 直传(预签名 URL),BFF 不接收文件流 |
### 12.4 数据合规
| 维度 | 实现 |
| ----------- | ----------------------------------------------------------------------------- |
| 学生隐私 | 成绩/学情仅本人可见,不返回同班其他学生数据 |
| 日志脱敏 | 日志中不记录 answers 内容、score 数值;仅记录 metadata(homeworkId, traceId) |
| 数据保留 | BFF 不持久化数据,Redis 缓存 TTL ≤ 600s |
| GDPR/个保法 | 学生数据导出/删除请求透传给 iam / core-edu 处理 |
### 12.5 审计
| 维度 | 实现 |
| ------------ | ---------------------------------------------------------------------- |
| 请求审计 | 全部请求记录 access log(userId, endpoint, status, duration, traceId) |
| 越权尝试审计 | 自我越权防御触发时记录 warn 日志 + 告警 |
| 异常行为 | 短时高频请求(如 1s 内 10 次 `/student/grades`)触发告警 |
---
## 13. 可观测性详细设计
### 13.1 日志规范
```typescript
// 标准日志字段
interface StudentBFFLog {
time: string;
level: "info" | "warn" | "error" | "debug";
service: "student-bff";
msg: string;
traceId?: string;
userId?: string;
endpoint?: string;
method?: string;
status?: number;
duration?: number;
downstream?: {
service: string;
endpoint: string;
status: number;
duration: number;
};
cache?: { key: string; hit: boolean };
err?: { message: string; stack: string; code: string };
}
```
### 13.2 关键业务 span
| Span 名 | 触发点 | 关键属性 |
| ----------------------------- | --------------------------------- | ----------------------------------------- |
| `student_bff.dashboard` | GET /student/dashboard | userId, cached, partial, degradedServices |
| `student_bff.submit_homework` | POST /student/homework/:id/submit | homeworkId, questionCount |
| `student_bff.list_grades` | GET /student/grades | userId, page, total |
| `student_bff.ai_chat` | POST /student/ai/chat | userId, model, tokens |
| `student_bff.ai_stream_chat` | POST /student/ai/stream-chat | userId, model, chunks, duration |
| `student_bff.downstream_call` | 任一下游调用 | service, endpoint, status, duration |
### 13.3 告警规则
| 告警名 | 触发条件 | 严重度 |
| ------------------------------ | ------------------------------- | ------ |
| `StudentBFFHighErrorRate` | 5xx 错误率 > 1% 持续 5 分钟 | 严重 |
| `StudentBFFHighLatency` | P95 延迟 > 1s 持续 5 分钟 | 警告 |
| `StudentBFFDownstreamFailures` | 下游调用失败率 > 5% 持续 5 分钟 | 警告 |
| `StudentBFFCacheHitRateLow` | 缓存命中率 < 50% 持续 10 分钟 | 提示 |
| `StudentBFFCircuitOpen` | 熔断器开启持续 1 分钟 | 严重 |
| `StudentBFFSSEConnectionsHigh` | SSE 连接数 > 5000 | 警告 |
| `StudentBFFKafkaLag` | Kafka 消费 lag > 1000 | 警告 |
### 13.4 Grafana Dashboard 面板
| 面板 | 内容 |
| ------------ | -------------------------------------- |
| 总览 | QPS / 错误率 / P95 延迟 / 缓存命中率 |
| 下游服务健康 | 各下游服务调用成功率 / 延迟 / 熔断状态 |
| 端点细分 | 各端点 QPS / 延迟 / 错误率 |
| SSE 推送 | 连接数 / 推送成功率 / 推送延迟 |
| Kafka 消费 | 消费 TPS / lag / DLQ 数量 |
---
## 14. 实施清单
### 14.1 P3 阶段交付清单
#### 14.1.1 文件结构
```
services/student-bff/
├─ src/
│ ├─ config/
│ │ └─ env.ts # 环境变量(PORT=3009 + 下游 URL)
│ ├─ shared/
│ │ ├─ errors/
│ │ │ ├─ application-error.ts # 错误类(BFF_STUDENT_ 前缀)
│ │ │ └─ global-error.filter.ts # 全局错误过滤器
│ │ ├─ health/
│ │ │ ├─ health.controller.ts # /healthz + /readyz
│ │ │ └─ health.module.ts
│ │ ├─ observability/
│ │ │ ├─ logger.ts # pino
│ │ │ ├─ metrics.ts # prom-client + student_bff_* 指标
│ │ │ └─ tracer.ts # OTel
│ │ ├─ cache/
│ │ │ └─ cache.module.ts # Redis CacheInterceptor
│ │ ├─ downstream/
│ │ │ ├─ downstream-client.ts # 统一封装 fetch + 超时 + 重试 + traceId
│ │ │ ├─ circuit-breaker.ts # opossum 熔断器
│ │ │ └─ downstream.module.ts
│ │ └─ dto/
│ │ └─ downstream-envelope.ts # 下游响应包装
│ ├─ student/
│ │ ├─ student.controller.ts # @Controller('student')
│ │ ├─ student.service.ts # 聚合编排
│ │ ├─ student.module.ts
│ │ ├─ aggregators/
│ │ │ ├─ dashboard.aggregator.ts # Dashboard 并行聚合策略
│ │ │ └─ homework.aggregator.ts
│ │ ├─ transformers/
│ │ │ ├─ dashboard.transformer.ts # 字段裁剪 + 视口过滤
│ │ │ └─ grades.transformer.ts
│ │ ├─ dto/
│ │ │ ├─ student-dashboard.dto.ts
│ │ │ ├─ student-homework.dto.ts
│ │ │ └─ student-grades.dto.ts
│ │ └─ schemas/
│ │ └─ submit-homework.schema.ts # Zod 输入校验
│ ├─ app.module.ts
│ └─ main.ts # 启动 + /metrics + SIGTERM
├─ test/
│ └─ unit/
│ ├─ student.service.test.ts
│ ├─ aggregators/*.test.ts
│ └─ transformers/*.test.ts
├─ docs/
│ ├─ 01-understanding.md
│ ├─ 02-audit.md
│ ├─ 02-architecture-design.md # 本文档
│ └─ experience-log.md
├─ Dockerfile # 多阶段构建,EXPOSE 3009
├─ nest-cli.json
├─ package.json # @edu/student-bff
├─ tsconfig.json # NodeNext + incremental: false
└─ vitest.config.ts # 对齐 classes 测试框架
```
#### 14.1.2 P3 必交付端点
- [ ] `GET /student/dashboard`
- [ ] `GET /student/viewports`
- [ ] `GET /student/exams`
- [ ] `GET /student/exams/:id`
- [ ] `GET /student/homework`
- [ ] `GET /student/homework/:id`
- [ ] `POST /student/homework/:id/submit` ← P3 核心
- [ ] `GET /student/grades`
- [ ] `GET /student/grades/:examId`
- [ ] `/healthz` + `/readyz`
- [ ] `/metrics`
#### 14.1.3 P3 横切关注点对齐
- [ ] pino logger(service: 'student-bff')
- [ ] prom-client metrics(11 个指标)
- [ ] OTel tracer(serviceName: 'student-bff')
- [ ] GlobalErrorFilter(BFF_STUDENT_* 错误码)
- [ ] Zod 输入校验
- [ ] DownstreamClient(超时 + 重试 + traceId)
- [ ] 熔断器(opossum)
- [ ] Redis 缓存(CacheInterceptor)
- [ ] 自我越权防御(studentId 强制 = userId)
- [ ] 优雅关闭(SIGTERM)
- [ ] Dockerfile 多阶段构建
- [ ] 测试覆盖率 ≥ 80%
### 14.2 P4 阶段扩展清单
- [ ] `GET /student/textbooks`
- [ ] `GET /student/textbooks/:id/chapters`
- [ ] `GET /student/questions`
- [ ] `GET /student/knowledge-points/:id/path`
- [ ] `GET /student/analytics/weakness`
- [ ] `GET /student/analytics/trend`
- [ ] 双轨读策略(实时查 core-edu 主库 + 聚合查 data-ana ClickHouse 宽表)
- [ ] /readyz 增强为下游可达性检查
### 14.3 P5 阶段扩展清单
- [ ] `GET /student/notifications`
- [ ] `POST /student/notifications/:id/read`
- [ ] `POST /student/ai/chat`
- [ ] `POST /student/ai/stream-chat`(SSE 流式)
- [ ] Kafka EventSubscriber 模块
- [ ] push-gateway 推送通道
- [ ] SSE 连接管理
### 14.4 P6 阶段硬化清单
- [ ] HPA 自动扩缩容
- [ ] Istio mTLS
- [ ] 全链路 trace + Grafana 仪表盘
- [ ] 灾备演练
- [ ] 99.9% 可用性压测
---
## 15. 与黄金模板对齐自检
> 对照 [ai-allocation §6 模板第 6 节](../../../docs/architecture/ai-allocation.md) + [004 §15](../../../docs/architecture/004_architecture_impact_map.md#15-ai-架构设计文档索引)
| 对齐项 | classes 黄金模板 | student-bff 设计 | 状态 |
| ------------------------------- | --------------------------------- | ---------------------------------------------------- | ------ |
| 权限装饰器 `@RequirePermission` | ✅ 全部 Controller 方法 | ⚠️ 不对齐(BFF 不做权限校验,透传 x-user-id) | 已识别 |
| 错误码前缀统一 | ✅ `CLASSES_` | ✅ `BFF_STUDENT_`(对齐 004 §11.4) | ✅ |
| logger(pino) | ✅ shared/observability/logger.ts | ✅ 复制 teacher-bff,service: 'student-bff' | ✅ |
| metrics(prom-client) | ✅ /metrics 端点 | ✅ 复制 teacher-bff,11 个 student_bff_* 指标 | ✅ |
| tracer(OpenTelemetry) | ✅ OTLP exporter | ✅ 复制 teacher-bff,serviceName: 'student-bff' | ✅ |
| `/healthz` 健康检查 | ✅ liveness | ✅ 复制 teacher-bff | ✅ |
| `/readyz` 健康检查 | ✅ Drizzle SELECT 1 | ✅ P3 直接返回 ok;P4+ 检查下游可达性 | ✅ |
| 优雅关闭(SIGTERM) | ✅ LifecycleService | ✅ main.ts 注册 SIGTERM → app.close + shutdownTracer | ✅ |
| 测试覆盖率 ≥ 80% | ✅ Jest | ✅ Vitest(对齐 classes),重点测 Service/Aggregator | ✅ |
| Dockerfile 多阶段构建 | ✅ builder + runtime | ✅ 复制 teacher-bff,EXPOSE 3009 | ✅ |
| Zod 输入验证 | ✅ Controller 层 | ✅ SubmitHomeworkSchema / AIChatSchema 等 | ✅ |
| GlobalErrorFilter | ✅ @Catch() | ✅ 复制 teacher-bff | ✅ |
| ESM `.js` 后缀 import | ✅ tsconfig NodeNext | ✅ 复制 teacher-bff tsconfig | ✅ |
| `import type` 纯类型导入 | ✅ | ✅ | ✅ |
| 环境变量 Zod 校验 | ✅ config/env.ts | ✅ 复制 teacher-bff,扩展 7 个下游 URL | ✅ |
| 端口分配 | — | ✅ 3009(对齐 004 §1.2) | ✅ |
| 路由前缀 | — | ✅ `/student`(对齐 teacher-bff `/teacher` 模式) | ✅ |
---
## 16. 阶段 2 自检结论
| 检查项 | 状态 |
| ------------------------------- | ------- |
| 模块内部分层图 | ✅ §1 |
| 领域模型(聚合视图) | ✅ §2 |
| 数据模型(缓存 + DTO) | ✅ §3 |
| API 设计(21 端点,含未来扩展) | ✅ §4 |
| 事件设计(订阅清单 + 架构) | ✅ §5 |
| 横切关注点对齐清单 | ✅ §6 |
| 与其他模块的交互点 | ✅ §7 |
| 风险与假设 | ✅ §8 |
| 演进路线图(长远规划) | ✅ §9 |
| 扩展点设计(为未来铺垫) | ✅ §10 |
| 性能与容量规划 | ✅ §11 |
| 安全与合规 | ✅ §12 |
| 可观测性详细设计 | ✅ §13 |
| 实施清单(P3/P4/P5/P6 分阶段) | ✅ §14 |
| 黄金模板对齐自检 | ✅ §15 |
| 错误码前缀修正(BFF_STUDENT_) | ✅ §0.2 |
| 未决决策清单(待 coord 仲裁) | ✅ §8.3 |
| 跨模块协作需求 | ✅ §7.2 |
**ai04 阶段 2 交付完成,请 coord 交叉审查。**
### 16.1 重点请 coord 审查的事项
1. **错误码前缀不一致修正**(§0.2):阶段 1 文档用 `STUDENT_BFF_`,本设计文档统一为 `BFF_STUDENT_`(对齐 004 §11.4),请确认是否回写阶段 1 文档。
2. **BFF 模式 v2 抽象**(§1.3):DownstreamClient / Aggregator / Transformer 三层抽象是否回写 teacher-bff,避免技术栈分裂。
3. **自我越权防御**(§2.3):BFF 层强制 `studentId = userId` 是与 teacher-bff 的差异点,请确认是否作为 BFF 通用规范。
4. **12 项未决决策**(§8.3):请逐项仲裁。
5. **16 项跨模块协作需求**(§7.2):请协调对应 AI 实施。
6. **GraphQL 演进时机**(§9.2):P3 REST / P4+ 是否切换 GraphQL,需全局决策。
7. **熔断器引入**(§8.3 #12):BFF 层是否在 P3 引入 opossum 熔断器,还是依赖 api-gateway 熔断。