Files
Edu/services/student-bff/docs/02-architecture-design.md
SpecialX faaaf29f67 docs: ai 协作文档体系重构与多 ai 仲裁结果落地
1.AI 协作文档体系重构(objections/worklines/contracts+matrix.md)

2.coord 仲裁文档(final-decisions/cross-review/final-rulings/orchestration)

3.各服务 01/02 文档补全

4.共享包初始化(shared-ts/shared-go/hooks/ui-components/ui-tokens)

5.Proto 契约补全

6.004 架构影响地图更新

7.端口分配表

8.设计规格文档
2026-07-10 12:58:22 +08:00

1497 lines
81 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 模块架构设计文档 — 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<br/>Next.js MF Remote]
Mobile[H5/小程序<br/>未来扩展]
end
subgraph Gateway["网关层"]
APIGW[api-gateway<br/>路由 + JWT 鉴权 + 限流]
end
subgraph StudentBFF["student-bff本模块"]
direction TB
Controller["@Controller('student')<br/>HTTP 入口 + Zod 校验 + ActionState 信封"]
Service["StudentService<br/>聚合编排 + DataScope 透传"]
Aggregator["Aggregator 层<br/>场景化聚合策略"]
Transformer["Transformer 层<br/>响应裁剪 + 视口过滤"]
Cache["Cache 层<br/>Redis 5-30s 短缓存"]
Client2["DownstreamClient<br/>统一封装 fetch + 超时 + 重试 + traceId 透传"]
SSE["SSE Streamer<br/>P5 AI 答疑流式响应"]
EventSub["Event Subscriber<br/>P5 订阅 Kafka 推送给 push-gateway"]
CrossCutting["横切关注点<br/>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<br/>:3002]
CoreEdu[core-edu<br/>:3004]
Content[content<br/>:3005 P4]
Msg[msg<br/>:3007 P5]
DataAna[data-ana<br/>:3006 P4]
AI[ai<br/>:3008 P5]
end
subgraph Push["推送层"]
PushGW[push-gateway<br/>: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<br/>Authorization: Bearer <jwt>
GW->>GW: RS256 公钥校验 + 提取 userId
GW->>C: GET /student/dashboard<br/>x-user-id: u-xxx<br/>x-request-id: r-xxx
C->>C: extractUserId(req) → u-xxx<br/>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<br/>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<br/>Accept: text/event-stream
GW->>C: POST /student/ai/stream-chat
C->>SSE: streamChat(messages, model)
SSE->>AI: POST /ai/stream-chat<br/>Authorization: Bearer <internal>
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 推送<br/>{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-eduschedule 域) | 读 | SELF |
| StudentAttendance | 我的考勤(未来扩展) | core-eduattendance 域) | 读 | SELF |
### 2.2 聚合视图间关系
```mermaid
graph LR
Dashboard[StudentDashboard<br/>聚合视图]
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<br/>未来扩展]
Attendance[StudentAttendance<br/>未来扩展]
Dashboard -.未来.-> Schedule
Dashboard -.未来.-> Attendance
```
### 2.3 DataScope=SELF 的强制实现
BFF 不做权限决策(对齐 teacher-bff但做**自我越权防御**
```typescript
// StudentService 伪代码示意
async listGrades(userId: string, query: ListGradesQuery): Promise<Grade[]> {
// 防御:学生只能查自己的成绩
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 = <T extends z.ZodTypeAny>(
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<T> = z.infer<
ReturnType<typeof DownstreamEnvelopeSchema<z.ZodType<T>>>
>;
```
#### 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<typeof StudentDashboardSchema>;
```
#### 3.2.3 输入 SchemaZod
```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<T>(
data: T,
viewport: StudentViewport,
): Partial<T> {
// 按视口裁剪字段,如 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=<jwt>
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<br/>NestJS Module]
Idempotency[(Redis SETNX<br/>event_id 去重)]
Session[(Redis<br/>学生在线 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<br/>B. 加(双重校验) | **A**BFF 是聚合层,权限由下游服务校验) | 待 coord 仲裁 |
| 自我越权防御 | A. 不做(依赖下游)<br/>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 Loggerpino
| 项 | 值 |
| ------------ | --------------------------------------------------------------------- |
| 文件位置 | `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 Metricsprom-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 TracerOpenTelemetry
| 项 | 值 |
| --------------------- | ------------------------------------------------- |
| 文件位置 | `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 Contexttraceparent 头) |
### 6.6 健康检查
| 端点 | 检查逻辑 | 响应 |
| ---------- | ---------------------------------------------------- | ------------------------------------------------- |
| `/healthz` | 进程存活(直接返回 ok | `200 { status: 'ok', service: 'student-bff' }` |
| `/readyz` | P3直接返回 ok<br/>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. 停止消费 Kafkacommit 最后 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 服务定义 | coordinfra | P3 | 端口 3009加入 edu-net + edu-shared 网络 |
| 3 | full-stack-runbook 端口矩阵更新 | coorddocs | P3 | 追加 3009 行 |
| 4 | 004 §15 文档位置矩阵更新 | coorddocs | P3 | student-bff 状态从"📐 需设计"改为"✅ 已实现" |
| 5 | shared-proto 补全 iam.protoViewport / EffectivePermissions | coord | P3 | 当前走 RESTproto 补全后切换 gRPC |
| 6 | shared-proto 补全 content.protoChapter / Question / KnowledgePath | coord | P4 | P4 content 服务落地前补全 |
| 7 | shared-proto 补全 core_edu.protoSchedule / Attendance 域) | coord | P4+ | 学生课表/考勤未来扩展用 |
| 8 | buf.gen.yaml 补 gRPC 插件 | coord | P3 | 决定是否在 P3 升级到 gRPC 通信 |
| 9 | core-edu 启用 gRPC server | ai03core-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-eduP3→ + content + data-anaP4→ + msg + aiP5 | iam + core-eduP4→ + msg + data-anaP4+ |
---
## 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 不一致 | 中 | 中 | 短 TTL15-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 现状)<br/>B. GraphQL对齐 004 §11.3 设计意图) | **A**P3 阶段先 RESTP4+ 统一升级时再考虑) | 全部端点 |
| 2 | BFF 是否做权限校验 | A. 不校验(对齐 teacher-bff<br/>B. 加 `@RequirePermission` | **A**(聚合层不做权限决策) | 全部端点 |
| 3 | BFF 是否做自我越权防御 | A. 不做(依赖下游)<br/>B. BFF 层强制 userId 比对 | **B**(学生场景敏感) | 成绩/作业/学情端点 |
| 4 | `/readyz` 检查逻辑 | A. 直接返回 ok对齐 teacher-bff<br/>B. 检查下游可达性 | **A**P3/ **B**P4+,下游可达性由 Prometheus 监控) | 健康检查 |
| 5 | Kafka 事件订阅时机 | A. P3 不订阅<br/>B. P3 订阅 | **A**push-gateway P5 才落地) | P5 推送功能 |
| 6 | 缓存策略 | A. 不缓存(对齐 teacher-bff<br/>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<br/>B. 回写(统一三端 BFF | **B**(避免技术栈分裂,作为 BFF 模式 v2 | teacher-bff / parent-bff |
| 10 | 是否引入 NestJS CQRS 模块 | A. 不引入(简单 Service 即可)<br/>B. 引入Query/Command 分离) | **A**BFF 不持领域模型CQRS 收益不大) | Service 层结构 |
| 11 | SSE 实现 | A. 原生 Node Stream<br/>B. 第三方库(如 @nestjs/axios + RxJS | **A**(依赖少,可控) | P5 AI 答疑流式 |
| 12 | 是否在 P3 引入熔断器 | A. 不引入(依赖 gateway 熔断)<br/>B. 引入opossum 库) | **B**BFF→下游单链路熔断gateway 是入口熔断) | DownstreamClient |
---
## 9. 演进路线图
### 9.1 各阶段演进总览
```mermaid
graph LR
P3[P3 核心教学<br/>M7-M10]
P4[P4 内容分析<br/>M11-M13]
P5[P5 沟通AI<br/>M14-M16]
P6[P6 硬化<br/>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 切换到 GraphQLREST 端点保留 6 个月兼容期
### 9.3 通信协议演进
| 阶段 | BFF → 业务服务协议 | 理由 |
| ---- | ------------------- | ----------------------------------------- |
| P3 | HTTP REST | 下游 gRPC server 未启用,对齐 teacher-bff |
| P4 | HTTP + gRPC 混合 | iam / core-edu 启用 gRPCBFF 优先 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<StudentDashboard> {
// 透传 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<LearningPath> {
// 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-10HPA |
| 单实例 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 不持 sessionRedis 仅缓存数据 |
| Token 刷新 | 透传 401 给前端,由前端走 refresh 流程 |
### 12.2 授权与数据隔离
| 维度 | 实现 |
| -------------- | --------------------------------------------------------------------- |
| 权限校验 | 透传 `x-user-id` 给下游,下游按 `@RequirePermission` + DataScope 校验 |
| 自我越权防御 | BFF 层强制 `studentId = userId`(成绩/作业/学情端点) |
| 跨班级数据隔离 | 学生只能查自己所在班级classId 由 iam 推导,不接受前端传入) |
| AI 内容安全 | AI 答疑请求透传 content moderationai 服务侧实现) |
### 12.3 输入安全
| 维度 | 实现 |
| -------- | -------------------------------------------------------------------------------- |
| 输入验证 | 全部 Zod 校验,拒绝 unknown 字段 |
| SQL 注入 | BFF 无 DB不涉及下游用 Drizzle 参数化查询 |
| XSS | 响应 Content-Type: application/json禁止 HTML 渲染;富文本由前端 DOMPurify 清洗 |
| CSRF | Cookie 必须 SameSite=Strict + 后端校验 Origin 头 |
| 文件上传 | 作业附件走 OSS 直传(预签名 URLBFF 不接收文件流 |
### 12.4 数据合规
| 维度 | 实现 |
| ----------- | ----------------------------------------------------------------------------- |
| 学生隐私 | 成绩/学情仅本人可见,不返回同班其他学生数据 |
| 日志脱敏 | 日志中不记录 answers 内容、score 数值;仅记录 metadatahomeworkId, traceId |
| 数据保留 | BFF 不持久化数据Redis 缓存 TTL ≤ 600s |
| GDPR/个保法 | 学生数据导出/删除请求透传给 iam / core-edu 处理 |
### 12.5 审计
| 维度 | 实现 |
| ------------ | ---------------------------------------------------------------------- |
| 请求审计 | 全部请求记录 access loguserId, 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 loggerservice: 'student-bff'
- [ ] prom-client metrics11 个指标)
- [ ] OTel tracerserviceName: 'student-bff'
- [ ] GlobalErrorFilterBFF_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 | ✅ |
| loggerpino | ✅ shared/observability/logger.ts | ✅ 复制 teacher-bffservice: 'student-bff' | ✅ |
| metricsprom-client | ✅ /metrics 端点 | ✅ 复制 teacher-bff11 个 student_bff_* 指标 | ✅ |
| tracerOpenTelemetry | ✅ OTLP exporter | ✅ 复制 teacher-bffserviceName: 'student-bff' | ✅ |
| `/healthz` 健康检查 | ✅ liveness | ✅ 复制 teacher-bff | ✅ |
| `/readyz` 健康检查 | ✅ Drizzle SELECT 1 | ✅ P3 直接返回 okP4+ 检查下游可达性 | ✅ |
| 优雅关闭SIGTERM | ✅ LifecycleService | ✅ main.ts 注册 SIGTERM → app.close + shutdownTracer | ✅ |
| 测试覆盖率 ≥ 80% | ✅ Jest | ✅ Vitest对齐 classes重点测 Service/Aggregator | ✅ |
| Dockerfile 多阶段构建 | ✅ builder + runtime | ✅ 复制 teacher-bffEXPOSE 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.3DownstreamClient / Aggregator / Transformer 三层抽象是否回写 teacher-bff避免技术栈分裂。
3. **自我越权防御**§2.3BFF 层强制 `studentId = userId` 是与 teacher-bff 的差异点,请确认是否作为 BFF 通用规范。
4. **12 项未决决策**§8.3):请逐项仲裁。
5. **16 项跨模块协作需求**§7.2):请协调对应 AI 实施。
6. **GraphQL 演进时机**§9.2P3 REST / P4+ 是否切换 GraphQL需全局决策。
7. **熔断器引入**§8.3 #12BFF 层是否在 P3 引入 opossum 熔断器,还是依赖 api-gateway 熔断。