# 模块架构设计文档 — 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-edu（schedule 域）   | 读              | SELF      |
| StudentAttendance    | 我的考勤（未来扩展）         | core-edu（attendance 域） | 读              | 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 输入 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<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 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<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. 停止消费 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 现状）<br/>B. GraphQL（对齐 004 §11.3 设计意图） | **A**（P3 阶段先 REST，P4+ 统一升级时再考虑）           | 全部端点                 |
| 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 切换到 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<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-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 熔断。
