docs(student-bff): 更新架构问题记录、工作排期和对接契约,对齐已裁决规则

完成已有仲裁核查,新增待仲裁问题归档,细化全阶段排期与依赖,对齐coord B1-B8和总裁裁决,更新GraphQL规范、越权防御、DownstreamClient契约
This commit is contained in:
SpecialX
2026-07-10 14:43:11 +08:00
parent 9ba368477d
commit e5ca4c6c7b
3 changed files with 1091 additions and 108 deletions

View File

@@ -1,50 +1,310 @@
# student-bff 对接契约
> 负责人ai04
> 关联:[matrix.md](./matrix.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)、[core_edu.proto](../../../packages/shared-proto/proto/core_edu.proto)、[content.proto](../../../packages/shared-proto/proto/content.proto)、[analytics.proto](../../../packages/shared-proto/proto/analytics.proto)、[msg.proto](../../../packages/shared-proto/proto/msg.proto)
> 阶段P3批次 2 启动)
> 版本v2对齐 coord-final-decisions B1-B8 + president-final-rulings §2.2/2.3/2.4/2.6/2.7/2.8/2.9
> 日期2026-07-10
> 关联文档:
>
> - [matrix.md](../matrix.md)
> - [coord-final-decisions.md](../../coord-final-decisions.md) §2 BFF 专项裁决 B1-B8
> - [president-final-rulings.md](../../president-final-rulings.md) §2.2 GraphQL schema 仲裁机制
> - [iam.proto](../../../../packages/shared-proto/proto/iam.proto)
> - [core_edu.proto](../../../../packages/shared-proto/proto/core_edu.proto)
> - [content.proto](../../../../packages/shared-proto/proto/content.proto)
> - [analytics.proto](../../../../packages/shared-proto/proto/analytics.proto)
> - [msg.proto](../../../../packages/shared-proto/proto/msg.proto)
---
## §0 裁决对齐声明
本文件已对齐以下裁决(无遗漏):
| 裁决编号 | 内容 | 本文件章节 |
| -------- | ------------------------------------------ | -------------- |
| B1 | P2 起直接 GraphQLGraphQL Yoga + DataLoader | §1.2 / §1.3 |
| B2 | 首次实现即 gRPC 调用下游 | §2.1 / §2.4 |
| B3 | BFF 豁免 @RequirePermission | §1.6 / §1.7 |
| B4 | 全部 BFF 强制自我越权防御 | §1.7 |
| B5 | 错误码前缀 `BFF_STUDENT_` | §1.6 |
| B6 | Redis 5-30s 短缓存 | §7 |
| B7 | P2-P4 不订阅 KafkaP5 后再订阅 | §1.5 / §2.2 |
| B8 | DownstreamClient 抽象回写 teacher-bff | §2.4 |
| G14 | 错误码前缀服务名大写 | §1.6 |
| F4 | i18n key `error.bffStudent.<code_snake>` | §1.6 |
| F7 | 权限点命名 `<RESOURCE>_<ACTION>[_<SCOPE>]` | §1.3.4 |
| F9 | 前端用 urql/apollo 消费 GraphQL | §1.2 |
| §2.2 | GraphQL schema 仲裁机制 | §1.3.2 / §1.3.3 |
| §2.3 | BFF 跨阶段扩展例外 | §3 |
| §2.4 | /readyz 探针按阶段扩展 | §4 |
| §2.6 | 降级模式方案 Bdata 内 degraded 字段) | §1.4.2 |
| §2.7 | BFF 错误码语义区分3 类) | §1.6 / §1.7.3 |
| §2.8 | Dashboard Query Resolver P2 实现方式 | §1.3.5 |
| §2.9 | 越权防御 P2 实现方式DEV_MODE 放行) | §1.7.4 |
| §5.1 | admin-portal 复用 teacher-bff不占 student-bff 命名空间 | §1.3.6 |
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口(如有)
### 1.1 服务基础信息
无对外 gRPC。student-bff 是 GraphQL 聚合层。
| 项目 | 值 |
| ------------- | --------------------------------------------- |
| 服务名 | student-bff |
| 服务类型 | BFF 聚合层(无 DB无 Outbox |
| HTTP 端口 | 3009 |
| gRPC 端口 | 不暴露BFF 仅对下游走 gRPC不对外提供 gRPC |
| 路由前缀 | `/student/*`api-gateway 透传) |
| 部署目录 | `services/student-bff/` |
| 角色要求 | `student`JWT 必需 + student 角色) |
### 1.2 HTTP 端点(如有)
### 1.2 HTTP 端点
| Method | Path | 用途 | 认证 |
| ------ | -------- | ----------------------------------------- | ----------------------- |
| POST | /graphql | 学生 BFF GraphQL 端点 | JWT 必需 + student 角色 |
| GET | /graphql | GraphQL Playground开发环境 | 开发环境公开 |
| GET | /healthz | 健康检查liveness | 公开 |
| GET | /readyz | 就绪检查readiness含下游 gRPC 连通性) | 公开 |
| Method | Path | 用途 | 认证 | 实现 |
| ------ | ---------- | ------------------------------------------ | ---------------------------- | ------- |
| POST | /graphql | 学生 BFF GraphQL 端点B1 裁决GraphQL Yoga | JWT 必需 + student 角色 | P3 |
| GET | /graphql | GraphQL Playground开发环境 | 开发环境公开 | P3 |
| GET | /healthz | 健康检查liveness | 公开 | P3 |
| GET | /readyz | 就绪检查readiness按阶段扩展探针) | 公开 | P3 |
| GET | /metrics | Prometheus 指标端点 | 公开(生产限内网) | P3 |
### 1.3 GraphQL schema如 BFF
> **B1 裁决**P2 起直接 GraphQLGraphQL Yoga + DataLoader禁止 REST → GraphQL 渐进式过渡。
> **B2 裁决**:对下游通信首次实现即 gRPC禁止 HTTP fetch → gRPC 渐进式过渡。
> **F9 裁决**:前端 student-portal 用 urql/apollo 消费 GraphQL。
GraphQL schema 文件路径:`apps/student-bff/src/schema/*.graphql`(端口 :3009
### 1.3 GraphQL schema(核心契约
核心 Query / Mutation 域:
#### 1.3.1 schema 存放路径
- **auth**currentUser聚合 iam.GetUserInfo + GetEffectivePermissions + GetViewports
- **myClasses**:我的班级(聚合 core-edu.ClassService.GetClass + ListStudentsByClass
- **myExams**:我的考试列表(聚合 core-edu.ExamService.ListExamsByClass
- **myHomework**:我的作业(聚合 core-edu.HomeworkService.ListHomeworkByClass + SubmitHomework
- **myGrades**:我的成绩(聚合 core-edu.GradeService.ListGradesByStudent
- **myAttendance**:我的考勤(聚合 core-edu.AttendanceService.ListAttendanceByStudent
- **content**textbooks / chapters / learningPath聚合 content.KnowledgeGraphService.GetLearningPath
- **dashboard**studentDashboard聚合 data-ana.AnalyticsService.GetStudentDashboard
- **weakness**myWeakness聚合 data-ana.AnalyticsService.GetStudentWeakness
- **trend**myTrend聚合 data-ana.AnalyticsService.GetLearningTrend
- **notifications**myNotifications / markAsRead聚合 msg.NotificationService
**强制路径**`packages/shared-ts/contracts/graphql/student-bff.schema.graphql`
### 1.4 Kafka 事件发布(如有)
> 依据 president-final-rulings §2.2.13 个 BFF schema 统一存放于 `packages/shared-ts/contracts/graphql/`,由各 BFF 负责 AI 起草、coord 仲裁。student-bff schema 不放在 `services/student-bff/src/` 下,避免前端 AI 难以发现。
无。student-bff 不发布事件,仅做 gRPC 聚合。
#### 1.3.2 起草与仲裁流程
### 1.5 错误码前缀
依据 president-final-rulings §2.2.6
`BFF_STUDENT_`(如 BFF_STUDENT_UPSTREAM_UNAVAILABLE、BFF_STUDENT_AGGREGATION_FAILED、BFF_STUDENT_FORBIDDEN
1. **起草方**ai04 在批次 1 等待期起草 student-bff GraphQL schema 草案
2. **仲裁方**coord 在**批次 2 启动前**仲裁第一版 schema
3. **消费方**ai14student-portal基于仲裁版 schema 消费
4. **变更流程**
- schema 变更需 PR + ai04BFF+ ai14前端双方 review
- 重大变更(删除字段 / 修改类型)需 coord 仲裁
- 新增字段允许,无需 coord 仲裁,但需通知 ai14
5. **版本管理**SDL-first`schema.graphql` 文件),配合 `graphql-codegen` 生成 TS 类型
#### 1.3.3 schema 设计规范
依据 president-final-rulings §2.2.5
| 规范项 | 规则 |
| -------------- | ----------------------------------------------------------------- |
| 命名风格 | Query/Mutation 用 camelCase`studentDashboard` / `myClasses` |
| 分页规范 | **Relay Cursor Connections**`{ edges, pageInfo, totalCount }` |
| 错误响应 | GraphQL errors 数组 + `extensions.code` + `extensions.traceId` |
| 权限点标注 | 注释形式 `# @permission: DASHBOARD_VIEW`F7 规范) |
| DataScope 标注 | 注释形式 `# @dataScope: OWN`(学生数据隔离 SELF |
| 字段命名 | Type 用 PascalCase字段用 camelCase枚举用 UPPER_SNAKE_CASE |
| 非空与可选 | 必填字段用 `!`,可空字段不标 `!`(避免破坏性变更) |
#### 1.3.4 核心 Query / Mutation 域
> 完整 SDL 定义见 `packages/shared-ts/contracts/graphql/student-bff.schema.graphql`ai04 起草coord 仲裁)。
**Query 域**
| Query | 用途 | 聚合下游 RPC | 权限点标注 | DataScope |
| ---------------------- | ------------------------ | ----------------------------------------------------------- | --------------------------- | --------- |
| `currentUser` | 当前学生信息 + 权限 + 视口 | iam.GetUserInfo + GetEffectivePermissions + GetViewports | `# @permission: AUTH_READ` | OWN |
| `myClasses` | 我的班级列表 | core-edu.ClassService.GetClass + ListStudentsByClass | `# @permission: CLASS_READ` | OWN |
| `myExams` | 我的考试列表 | core-edu.ExamService.ListExamsByClass | `# @permission: EXAM_READ` | OWN |
| `myHomework` | 我的作业列表 | core-edu.HomeworkService.ListHomeworkByClass | `# @permission: HOMEWORK_READ` | OWN |
| `myGrades` | 我的成绩列表 | core-edu.GradeService.ListGradesByStudent | `# @permission: GRADE_READ` | OWN |
| `myAttendance` | 我的考勤记录 | core-edu.AttendanceService.ListAttendanceByStudent | `# @permission: ATTENDANCE_READ` | OWN |
| `textbooks` | 教材列表 | content.TextbookService.ListTextbooks | `# @permission: TEXTBOOK_READ` | OWN |
| `chapters` | 章节列表 | content.ChapterService.ListChapters | `# @permission: CHAPTER_READ` | OWN |
| `learningPath` | 学习路径推荐 | content.KnowledgeGraphService.GetLearningPath | `# @permission: LEARNING_PATH_READ` | OWN |
| `studentDashboard` | 学生仪表盘 | data-ana.AnalyticsService.GetStudentDashboard | `# @permission: DASHBOARD_VIEW` | OWN |
| `myWeakness` | 我的薄弱点 | data-ana.AnalyticsService.GetStudentWeakness | `# @permission: WEAKNESS_READ` | OWN |
| `myTrend` | 学习趋势 | data-ana.AnalyticsService.GetLearningTrend | `# @permission: TREND_READ` | OWN |
| `myNotifications` | 我的通知列表 | msg.NotificationService.ListNotifications | `# @permission: NOTIFICATION_READ` | OWN |
| `myNotificationUnreadCount` | 通知未读数 | msg.NotificationService.GetUnreadCount | `# @permission: NOTIFICATION_READ` | OWN |
**Mutation 域**
| Mutation | 用途 | 聚合下游 RPC | 权限点标注 | DataScope |
| ---------------------- | ---------------- | ----------------------------------------- | --------------------------- | --------- |
| `submitHomework` | 提交作业 | core-edu.HomeworkService.SubmitHomework | `# @permission: HOMEWORK_SUBMIT` | OWN |
| `markNotificationAsRead` | 标记通知已读 | msg.NotificationService.MarkAsRead | `# @permission: NOTIFICATION_UPDATE` | OWN |
> **分页**:所有列表 QuerymyClasses / myExams / myHomework / myGrades / myAttendance / textbooks / chapters / myNotifications使用 Relay Cursor Connections 规范,返回 `{ edges, pageInfo, totalCount }`。
> **DataScope=OWN**:学生数据隔离为 SELF所有 Query 透传 `x-user-id` 给下游,下游 Repository 按 DataScope=SELF 过滤。
#### 1.3.5 Dashboard Query Resolver 实现方式§2.8 裁决)
依据 president-final-rulings §2.8
1. **GraphQL schema 设计完整**(含全部 Query/Mutation 字段定义P3 即定型,后续不重构
2. **Resolver 实现**
- **P3 阶段**`studentDashboard` Query Resolver 内部调 iam + core-edu gRPC返回学生基础信息 + 班级列表 + 考试列表 + 作业列表 + 成绩列表
- **P4 扩展**:增加 content教材/章节)+ data-ana仪表盘/薄弱点/趋势)数据源,将 null 字段替换为真实数据
- **P5 扩展**:增加 msg通知未读数+ aiAI 助教入口)数据源
- 未启用的下游字段返回 `null` + `extensions.warning = "field_unavailable_in_p3"`
3. **前端配合**ai14 student-portal 对 null 字段做 UI 降级展示(如"数据加载中"或隐藏模块)
4. **此方案不违反"不分阶段"**schema 即最终方案Resolver 内部数据源扩展属"跨阶段扩展例外"(见 §3
#### 1.3.6 admin-portal 命名空间
依据 president-final-rulings §5.1
- **admin-portal 复用 teacher-bff GraphQL endpoint**,不新建 admin-bff 服务
- **student-bff 不预留 admin schema 命名空间**admin 操作走 teacher-bff 的 `admin.*` 命名空间)
- ai04 无需为 admin-portal 做任何 schema 预留
### 1.4 GraphQL 错误响应格式
#### 1.4.1 GraphQL errors 数组 + ActionState 扩展
依据 president-final-rulings §2.2.3 + G8 裁决:
GraphQL 错误响应遵循标准 errors 数组格式,扩展 ActionState 字段:
```json
{
"errors": [
{
"message": "学生身份验证失败",
"extensions": {
"code": "BFF_STUDENT_UNAUTHORIZED",
"traceId": "abc-123-def-456",
"i18nKey": "error.bffStudent.unauthorized",
"severity": "error"
}
}
],
"data": null
}
```
| 字段 | 类型 | 说明 |
| ------------------- | ------ | ----------------------------------------------- |
| `extensions.code` | string | 错误码BFF_STUDENT_* 前缀,见 §1.6 |
| `extensions.traceId`| string | 全链路追踪 ID由 Gateway 注入 X-Request-Id |
| `extensions.i18nKey`| string | i18n keyF4 规范:`error.bffStudent.<code_snake>` |
| `extensions.severity` | string | `error` / `warning` / `info` |
#### 1.4.2 降级模式(方案 B§2.6 裁决)
依据 president-final-rulings §2.6:当下游服务不可用但需返回部分数据时,采用**方案 B**success=true + error=null + data 内 degraded 字段):
```json
{
"data": {
"studentDashboard": {
"user": { "id": "stu-001", "name": "李同学" },
"classes": [{ "id": "cls-001", "name": "高三1班" }],
"weakness": null,
"degraded": true,
"degradedReason": "data_ana_unavailable",
"degradedFields": ["weakness"]
}
}
}
```
**规则**
1. 降级时 HTTP 200GraphQL `data` 非 null
2. 降级字段返回 `null`,并在父对象内加 `degraded: true` + `degradedReason: string` + `degradedFields: string[]`
3. 前端检查 `data.degraded` 判断降级,对 `degradedFields` 内字段做 UI 降级展示
4. 降级场景示例:
- data-ana gRPC 不可用 → `studentDashboard.weakness` / `myTrend` / `myWeakness` 降级
- content gRPC 不可用 → `textbooks` / `chapters` / `learningPath` 降级
- msg gRPC 不可用 → `myNotifications` / `myNotificationUnreadCount` 降级
### 1.5 Kafka 事件发布
**无**。student-bff 是纯聚合层,不发布 Kafka 事件,不写 Outbox。
### 1.6 错误码前缀与列表
依据 B5 + G14 + F4 + §2.7 裁决:
**错误码前缀**`BFF_STUDENT_`(统一 BFF_ 前缀,服务名大写)
**i18n key 规范**`error.bffStudent.<code_snake>`F4 裁决)
**错误码清单**
| 错误码 | HTTP | 场景 | i18n key |
| ------------------------------------- | ---- | ---------------------------------------------- | --------------------------------------------- |
| `BFF_STUDENT_UNAUTHORIZED` | 401 | x-user-id 缺失或无效 | `error.bffStudent.unauthorized` |
| `BFF_STUDENT_FORBIDDEN_RESOURCE` | 403 | 学生越权访问他人数据(场景 A | `error.bffStudent.forbidden_resource` |
| `BFF_STUDENT_IDENTITY_MISMATCH` | 403 | JWT userId 与请求 body userId 不一致(场景 B | `error.bffStudent.identity_mismatch` |
| `BFF_STUDENT_BAD_GATEWAY` | 502 | 下游 gRPC 调用失败(非业务错误) | `error.bffStudent.bad_gateway` |
| `BFF_STUDENT_UPSTREAM_UNAVAILABLE` | 503 | 下游服务不可用(降级模式触发) | `error.bffStudent.upstream_unavailable` |
| `BFF_STUDENT_AGGREGATION_FAILED` | 500 | 聚合逻辑异常(未知错误) | `error.bffStudent.aggregation_failed` |
| `BFF_STUDENT_VALIDATION_ERROR` | 400 | 输入参数校验失败Zod 校验) | `error.bffStudent.validation_error` |
| `BFF_STUDENT_GRAPHQL_PARSE_ERROR` | 400 | GraphQL 语法解析错误 | `error.bffStudent.graphql_parse_error` |
| `BFF_STUDENT_GRAPHQL_VALIDATION_ERROR`| 400 | GraphQL 字段类型校验错误 | `error.bffStudent.graphql_validation_error` |
| `BFF_STUDENT_RATE_LIMITED` | 429 | 限流触发Gateway 层处理BFF 兜底) | `error.bffStudent.rate_limited` |
> **B3 裁决澄清**BFF 豁免 `@RequirePermission` 指不做"功能权限决策"(如"能否查看仪表盘"),但必须做"数据权限防御"(如"只能看自己的数据"),见 §1.7。
### 1.7 BFF 越权防御契约B4 裁决)
#### 1.7.1 越权防御场景
依据 B4 + §2.7 裁决student-bff 强制自我越权防御:
| 场景 | 描述 | 防御方式 |
| ---- | ---------------------------------------------- | --------------------------------------------------- |
| A | 学生查询他人数据(如 query 传入非自己 userId | AuthorizationGuard 比对 `x-user-id` 与查询参数 |
| B | JWT userId 与请求 body userId 不一致 | Mutation 入参校验,拒绝不一致请求 |
#### 1.7.2 AuthorizationGuard 接口
依据 §2.9 裁决:
```typescript
// services/student-bff/src/middleware/authorization.guard.ts
export interface AuthorizationGuard {
/**
* 校验学生是否有权访问指定资源
* @param currentUserId 从 x-user-id header 获取
* @param resourceUserId 查询参数中的 userId
* @returns true 允许访问false 拒绝
*/
canAccessSelfData(currentUserId: string, resourceUserId: string): Promise<boolean>;
}
```
#### 1.7.3 3 类错误码语义§2.7 裁决)
| 错误码 | HTTP | 场景 | 触发条件 |
| -------------------------------- | ---- | ---------------------------------------------------- | ----------------------------------------- |
| `BFF_STUDENT_UNAUTHORIZED` | 401 | x-user-id 缺失或无效 | header 无 x-user-id 或为空 |
| `BFF_STUDENT_FORBIDDEN_RESOURCE` | 403 | 学生越权访问他人数据(场景 A | canAccessSelfData 返回 false |
| `BFF_STUDENT_IDENTITY_MISMATCH` | 403 | JWT userId 与请求 body userId 不一致(场景 B | Mutation submitHomework 等 body userId 不匹配 |
#### 1.7.4 P3 实现方式§2.9 裁决)
依据 §2.9 裁决:
1. **P3 抽象 AuthorizationGuard 接口**`canAccessSelfData`,见 §1.7.2
2. **P3 内部实现为"DEV_MODE 放行 + 生产拒绝"**(保守策略):
- `DEV_MODE=true`:放行所有请求,仅记录 warn 日志
- `DEV_MODE=false`:严格校验,越权返回 `BFF_STUDENT_FORBIDDEN_RESOURCE`
3. **Redis 缓存**P3 后期接入):
- key: `authz:student:{userId}`
- value: 用户基础信息userId / roles / classIds
- TTL: 5min
- AuthorizationGuard 优先查缓存,缓存未命中调 iam gRPC
4. **此方案不违反"不分阶段"**Guard 接口即最终方案P3→P3 后期仅替换内部实现(属"跨阶段扩展例外",见 §3
**P3 阶段生产环境**Guard 全部拒绝时student-bff P3 端到端验证仅限 DEV_MODE。生产环境 P3 不接入流量(仅 dev 测试P3 后期接入真实校验后正式上线。
---
@@ -52,79 +312,288 @@ GraphQL schema 文件路径:`apps/student-bff/src/schema/*.graphql`(端口 :
### 2.1 gRPC 调用(同步)
| 被调用方 | Service.RPC | 用途 | mock 策略 |
| --------------- | ----------------------------------------- | ---------------- | ------------------------------------------- |
| iam (ai06) | IamService.GetUserInfo | 获取当前学生信息 | iam 就绪前返回固定 UserInfostudent 角色) |
| iam (ai06) | IamService.GetEffectivePermissions | 权限校验 | iam 就绪前返回学生权限集 |
| iam (ai06) | IamService.GetViewports | 学生导航菜单 | iam 就绪前返回固定视口列表 |
| core-edu (ai08) | ClassService.GetClass | 我的班级详情 | core-edu 就绪前返回固定 ClassInfo |
| core-edu (ai08) | ClassService.ListStudentsByClass | 班级同学名单 | core-edu 就绪前返回固定 30 个 StudentInfo |
| core-edu (ai08) | ExamService.ListExamsByClass | 我的考试 | core-edu 就绪前返回固定 2 个 Exam |
| core-edu (ai08) | HomeworkService.ListHomeworkByClass | 我的作业 | core-edu 就绪前返回固定 3 个 Homework |
| core-edu (ai08) | HomeworkService.SubmitHomework | 提交作业 | core-edu 就绪前返回 success=true |
| core-edu (ai08) | GradeService.ListGradesByStudent | 我的成绩 | core-edu 就绪前返回固定 5Grade |
| core-edu (ai08) | AttendanceService.ListAttendanceByStudent | 我的考勤 | core-edu 就绪前返回固定 10 条 Attendance |
| content (ai09) | TextbookService.ListTextbooks | 教材列表 | content 就绪前返回固定 5 个教材 |
| content (ai09) | ChapterService.ListChapters | 章节列表 | content 就绪前返回固定章节树 |
| content (ai09) | KnowledgeGraphService.GetLearningPath | 学习路径 | content 就绪前返回固定 8 个知识点推荐顺序 |
| data-ana (ai11) | AnalyticsService.GetStudentDashboard | 学生仪表盘 | data-ana 就绪前返回固定仪表盘 |
| data-ana (ai11) | AnalyticsService.GetStudentWeakness | 我的薄弱点 | data-ana 就绪前返回固定 3 个 weak_points |
| data-ana (ai11) | AnalyticsService.GetLearningTrend | 学习趋势 | data-ana 就绪前返回固定趋势数据 |
| msg (ai10) | NotificationService.ListNotifications | 学生通知 | msg 就绪前返回固定 10 条通知 |
| msg (ai10) | NotificationService.MarkAsRead | 标记已读 | msg 就绪前返回 success=true |
依据 B2 裁决student-bff 对下游全部走 gRPC禁止 HTTP fetch
| 被调用方 | Service.RPC | 用途 | 启用阶段 | mock 策略 |
| --------------- | ----------------------------------------- | ---------------- | -------- | ------------------------------------------- |
| iam (ai06) | IamService.GetUserInfo | 获取当前学生信息 | P3 | iam 就绪前返回固定 UserInfostudent 角色) |
| iam (ai06) | IamService.GetEffectivePermissions | 权限校验 | P3 | iam 就绪前返回学生权限集 |
| iam (ai06) | IamService.GetViewports | 学生导航菜单 | P3 | iam 就绪前返回固定视口列表 |
| iam (ai06) | IamService.GetEffectiveDataScope | DataScope 透传 | P3 | iam 就绪前返回 `SELF` |
| core-edu (ai08) | ClassService.GetClass | 我的班级详情 | P3 | core-edu 就绪前返回固定 ClassInfo |
| core-edu (ai08) | ClassService.ListStudentsByClass | 班级同学名单 | P3 | core-edu 就绪前返回固定 30 个 StudentInfo |
| core-edu (ai08) | ExamService.ListExamsByClass | 我的考试 | P3 | core-edu 就绪前返回固定 2Exam |
| core-edu (ai08) | HomeworkService.ListHomeworkByClass | 我的作业 | P3 | core-edu 就绪前返回固定 3 个 Homework |
| core-edu (ai08) | HomeworkService.SubmitHomework | 提交作业 | P3 | core-edu 就绪前返回 success=true |
| core-edu (ai08) | GradeService.ListGradesByStudent | 我的成绩 | P3 | core-edu 就绪前返回固定 5 个 Grade |
| core-edu (ai08) | AttendanceService.ListAttendanceByStudent | 我的考勤 | P3 | core-edu 就绪前返回固定 10 条 Attendance |
| content (ai09) | TextbookService.ListTextbooks | 教材列表 | P4 | content 就绪前返回固定 5 个教材 |
| content (ai09) | ChapterService.ListChapters | 章节列表 | P4 | content 就绪前返回固定章节树 |
| content (ai09) | KnowledgeGraphService.GetLearningPath | 学习路径 | P4 | content 就绪前返回固定 8 个知识点推荐顺序 |
| data-ana (ai11) | AnalyticsService.GetStudentDashboard | 学生仪表盘 | P4 | data-ana 就绪前返回固定仪表盘 |
| data-ana (ai11) | AnalyticsService.GetStudentWeakness | 我的薄弱点 | P4 | data-ana 就绪前返回固定 3 个 weak_points |
| data-ana (ai11) | AnalyticsService.GetLearningTrend | 学习趋势 | P4 | data-ana 就绪前返回固定趋势数据 |
| msg (ai10) | NotificationService.ListNotifications | 学生通知 | P5 | msg 就绪前返回固定 10 条通知 |
| msg (ai10) | NotificationService.GetUnreadCount | 通知未读数 | P5 | msg 就绪前返回固定 count=3 |
| msg (ai10) | NotificationService.MarkAsRead | 标记已读 | P5 | msg 就绪前返回 success=true |
> **命名对齐**coord §5.2
> - `AnalyticsService.GetStudentDashboard`(无 Stats 后缀,统一命名)
> - `KnowledgeGraphService.GetLearningPath`(禁用 ContentService 命名)
### 2.2 Kafka 事件订阅(异步)
无。student-bff 不订阅 Kafka 事件,仅做同步 gRPC 聚合。
依据 B7 裁决:
### 2.3 HTTP 调用(如有
- **P3-P4 阶段****不订阅 Kafka**(仅同步 gRPC 聚合
- **P5 阶段**push-gateway 落地后,评估是否订阅 Kafka`edu.identity.user.role_changed` 用于权限缓存失效)
无。
**P5 可选订阅的 topic**(待 P5 评估):
| Topic | 用途 | 触发动作 |
| ------------------------------- | -------------------------- | ------------------------------------- |
| `edu.identity.user.role_changed` | 学生角色变更,失效权限缓存 | iam 发布student-bff 清除 Redis 缓存 |
> **P3-P4 降级方案**:权限缓存用短 TTL5min兜底不订阅 Kafka 事件。
### 2.3 HTTP 调用
**无**。依据 B2 裁决student-bff 对下游全部走 gRPC禁止 HTTP fetch。
### 2.4 DownstreamClient 抽象层B8 裁决)
依据 B8 裁决:
1. **回写 teacher-bff**DownstreamClient 作为 BFF 模式 v2 标准抽象,回写到 teacher-bff3 个 BFFteacher-bff / student-bff / parent-bff统一使用
2. **抽象位置**`packages/shared-ts/src/bff/downstream-client.ts`coord 维护)
3. **核心能力**
```typescript
export class DownstreamClient {
/**
* gRPC 调用封装
* @param service 下游服务名(如 'iam' / 'core-edu'
* @param method RPC 方法名(如 'GetUserInfo'
* @param request 请求 message
* @param options 超时 / 重试 / traceId 透传
*/
call<TRequest, TResponse>(
service: string,
method: string,
request: TRequest,
options?: CallOptions,
): Promise<TResponse>;
}
interface CallOptions {
timeoutMs?: number; // 默认 5000ms
retryCount?: number; // 默认 2
retryBackoffMs?: number; // 默认 100ms指数退避
traceId?: string; // 从 x-request-id header 获取
metadata?: Record<string, string>; // gRPC metadata含 x-user-id / x-user-roles / x-dataScope
}
```
4. **student-bff 使用方式**
```typescript
// services/student-bff/src/auth/auth.service.ts
import { DownstreamClient } from '@edu/shared-ts/bff/downstream-client';
@Injectable()
export class AuthService {
constructor(private readonly downstream: DownstreamClient) {}
async getCurrentUser(userId: string): Promise<UserInfo> {
return this.downstream.call('iam', 'GetUserInfo', { userId }, {
metadata: { 'x-user-id': userId },
});
}
}
```
5. **回写义务**ai04 在 P3 实现时,将 DownstreamClient 抽象回写到 teacher-bff替换 teacher-bff 现有的散落 fetch 调用),保证 3 个 BFF 统一。
---
## §3 就绪信号
## §3 跨阶段扩展例外规则§2.3 裁决)
### 3.1 我依赖的上游就绪标志
依据 president-final-rulings §2.3
- [ ] iam gRPC 50052 启用ai06
- [ ] core-edu gRPC 50053 启用ai08
- [ ] content gRPC 50054 启用ai09
- [ ] data-ana gRPC 50055 启用ai11
- [ ] msg gRPC 50056 启用ai10
### 3.1 允许扩展(无需 coord 仲裁
### 3.2 我的就绪标志(供下游消费
1. 新增下游 gRPC 调用(新增 RPC 方法到 DownstreamClient
2. 新增下游配置gRPC endpoint 配置)
3. 新增 /readyz 探针(按阶段启用,见 §4
4. Dashboard Query Resolver 内部数据源扩展null 字段 → 真实数据)
5. AuthorizationGuard 内部实现替换DEV_MODE 放行 → 真实 gRPC 校验 + Redis 缓存)
- [ ] student-bff GraphQL :3009 启用(/healthz 返回 200
- [ ] /readyz 返回 200含 5 个下游 gRPC 连通性检查)
- [ ] GraphQL schema 可内省POST /graphql 返回 schema
- [ ] 核心 Query 可执行currentUser / myClasses / studentDashboard / myGrades
- [ ] 核心 Mutation 可执行submitHomework / markAsRead
### 3.2 禁止变更(需 coord 仲裁
1. 修改已有 RPC 调用的签名或返回类型
2. 删除已实现的 RPC 调用(除非下游服务下线)
3. 修改 GraphQL schema 已有字段的类型(新增字段允许,无需仲裁)
4. 修改 /readyz 已有探针的检查项(只能新增,不能修改)
### 3.3 验收标准
扩展时必须:
1. 更新 student-bff 02-architecture-design.md 下游调用矩阵§7
2. 更新 `packages/shared-ts/contracts/graphql/student-bff.schema.graphql`
3. 运行 `pnpm run arch:scan` 更新 arch.db
4. coord 在批次验收时检查上述 3 项
---
## §4 Mock 策略
## §4 /readyz 探针按阶段扩展规则§2.4 裁决)
### 4.1 我提供的 mock
依据 president-final-rulings §2.4 + G2 裁决:
在 student-bff 真实就绪前为下游student-portal提供以下 mock
### 4.1 探针列表(按阶段)
- **GraphQL mock**:使用 Apollo Server mockProviders 或 MSW 拦截 POST /graphql
- currentUser 返回固定学生id="student-001", name="李同学", roles=["student"]
- myClasses 返回固定 1 个班级
- studentDashboard 返回固定仪表盘avg_score=85.0, class_rank=5
- myGrades 返回固定 5 个成绩
- myHomework 返回固定 3 个作业1 个待提交)
- myNotifications 返回固定 10 条通知
student-bff 无 DB探针仅检查 Redis + 下游 gRPC 可达性:
### 4.2 我消费的 mock
| 阶段 | 探针列表 | 数量 |
| ---- | ----------------------------------------------------- | ---- |
| P3 | Redis PING + iam gRPC 50052 + core-edu gRPC 50053 | 3 |
| P4 | + content gRPC 50054 + data-ana gRPC 50055 | 5 |
| P5 | + ai gRPC 50058 + msg gRPC 50056 | 7 |
**实现方式**`DownstreamHealthCheck` 注册表模式,每个下游注册独立探针,按阶段启用(通过 ENV 过滤):
```typescript
const checks: HealthCheck[] = [
checkRedis(),
checkGrpc('iam', 50052),
checkGrpc('core-edu', 50053),
];
if (env.CONTENT_GRPC_ENABLED) checks.push(checkGrpc('content', 50054));
if (env.DATA_ANA_GRPC_ENABLED) checks.push(checkGrpc('data-ana', 50055));
if (env.AI_GRPC_ENABLED) checks.push(checkGrpc('ai', 50058));
if (env.MSG_GRPC_ENABLED) checks.push(checkGrpc('msg', 50056));
```
### 4.2 软失败规则
依据 §2.4.3
- **必需依赖**Redis + 已启用 gRPC 下游):失败返回 503触发 Pod 重启
- **可选依赖**(未启用 gRPC 下游 / Kafka 订阅):失败仅告警,返回 200 + body `degraded: true`
**student-bff 软失败场景**
| 依赖 | 类型 | 失败行为 |
| ------------------- | ------ | ------------------------------------------- |
| Redis | 必需 | 503缓存失效会影响性能但 BFF 仍可降级运行) |
| iam gRPC 50052 | 必需 | 503无 iam 无法做身份校验) |
| core-edu gRPC 50053 | 必需 | 503核心数据源 |
| content gRPC 50054 | 可选P4 启用前) | 200 + degraded=true |
| data-ana gRPC 50055 | 可选P4 启用前) | 200 + degraded=true |
| ai gRPC 50058 | 可选P5 启用前) | 200 + degraded=true |
| msg gRPC 50056 | 可选P5 启用前) | 200 + degraded=true |
> **P3 阶段**content / data-ana / ai / msg 均为可选P3 /readyz 仅检查 Redis + iam + core-edu3 项)。
---
## §5 就绪信号
### 5.1 我依赖的上游就绪标志
| 上游依赖 | 就绪标志 | 责任方 | 完成期限 |
| ---------------- | ------------------------------------------- | ------ | ------------- |
| iam gRPC 50052 | iam /readyz 返回 200 + GetUserInfo RPC 可调 | ai06 | 批次 1 完成 |
| core-edu gRPC 50053 | core-edu /readyz 返回 200 + ListExamsByClass RPC 可调 | ai08 | 批次 2 完成 |
| content gRPC 50054 | content /readyz 返回 200 + ListTextbooks RPC 可调 | ai09 | 批次 3 完成 |
| data-ana gRPC 50055 | data-ana /readyz 返回 200 + GetStudentDashboard RPC 可调 | ai11 | 批次 3 完成 |
| msg gRPC 50056 | msg /readyz 返回 200 + ListNotifications RPC 可调 | ai10 | 批次 4 完成 |
| GraphQL schema 仲裁 | coord 仲裁 student-bff schema 第一版 | coord | 批次 2 启动前 |
| DownstreamClient 抽象 | packages/shared-ts/src/bff/downstream-client.ts 就绪 | coord | 批次 1 启动前 |
| shared-go 包 | packages/shared-go/ 骨架就绪 | coord | 批次 0.14 |
### 5.2 我的就绪标志(供下游消费)
| 就绪标志 | 完成标准 | 完成阶段 |
| ----------------------------------------- | ----------------------------------------------------------- | -------- |
| student-bff GraphQL :3009 启用 | /healthz 返回 200 | P3 |
| /readyz 返回 200P3 探针 3 项) | Redis + iam gRPC + core-edu gRPC 全部通过 | P3 |
| GraphQL schema 可内省 | POST /graphql `{ query: "{ __schema { types { name } } }" }` 返回 schema | P3 |
| 核心 Query 可执行 | currentUser / myClasses / myExams / myHomework / myGrades 返回真实数据 | P3 |
| 核心 Mutation 可执行 | submitHomework 可执行 | P3 |
| AuthorizationGuard 接口就绪 | canAccessSelfData 接口已实现DEV_MODE 放行) | P3 |
| DownstreamClient 回写 teacher-bff 完成 | teacher-bff 已切换为 DownstreamClient 抽象 | P3 |
| /readyz 扩展 P4 探针5 项) | + content gRPC + data-ana gRPC | P4 |
| /readyz 扩展 P5 探针7 项) | + ai gRPC + msg gRPC | P5 |
---
## §6 Mock 策略
### 6.1 我提供的 mock供 student-portal 消费)
在 student-bff 真实就绪前,为 ai14student-portal提供以下 mock
**GraphQL mock 实现方式**GraphQL Yoga 内置 mock 模式(`graphql-yoga``mocking` 配置)或 MSW 拦截 POST /graphql
| Query/Mutation | mock 返回 |
| --------------------------- | ------------------------------------------------------------ |
| `currentUser` | 固定学生id="student-001", name="李同学", roles=["student"] |
| `myClasses` | 固定 1 个班级id="cls-001", name="高三1班" |
| `myExams` | 固定 2 个考试 |
| `myHomework` | 固定 3 个作业1 个待提交) |
| `myGrades` | 固定 5 个成绩avg_score=85.0 |
| `myAttendance` | 固定 10 条考勤 |
| `studentDashboard` | 固定仪表盘avg_score=85.0, class_rank=5 |
| `myNotifications` | 固定 10 条通知3 条未读) |
| `myNotificationUnreadCount` | 固定 count=3 |
| `submitHomework` | 返回 success=true |
| `markNotificationAsRead` | 返回 success=true |
> **P4 字段降级**P3 阶段 `studentDashboard.weakness` / `myTrend` / `textbooks` / `chapters` / `learningPath` 返回 null + `extensions.warning = "field_unavailable_in_p3"`
### 6.2 我消费的 mock上游未就绪时
在真实上游就绪前student-bff 使用以下 mock详见 §2.1 mock 策略列):
- **iam mock**:固定 UserInfo + 学生权限 + 固定视口
- **core-edu mock**:固定班级/同学/考试/作业/成绩/考勤
- **content mock**:固定教材/章节/学习路径
- **data-ana mock**:固定仪表盘/薄弱点/趋势
- **msg mock**:固定通知列表 + MarkAsRead success
| 上游 | mock 实现 |
| -------- | ------------------------------------------------------------ |
| iam | 固定 UserInfo + 学生权限集 + 固定视口 + DataScope=SELF |
| core-edu | 固定班级/同学/考试/作业/成绩/考勤 |
| content | 固定教材/章节/学习路径P4 启用前) |
| data-ana | 固定仪表盘/薄弱点/趋势P4 启用前) |
| msg | 固定通知列表 + GetUnreadCount + MarkAsRead successP5 启用前) |
> 所有上游 mock 通过 gRPC client 拦截器实现,上游就绪后移除拦截器切换真实调用
> **mock 实现方式**:通过 DownstreamClient 的 gRPC client 拦截器实现,上游就绪后移除拦截器切换真实调用。具体:在 `DownstreamClient.call()` 内部检查 `env.MOCK_UPSTREAM=true`,若为 true 则返回 mock 数据,否则走真实 gRPC
---
## §7 缓存策略B6 裁决)
依据 B6 裁决 + 004 §6.2 BFF 混合读策略:
### 7.1 Redis 短缓存
| 缓存对象 | key 模式 | TTL | 失效策略 |
| ----------------------- | ----------------------------------- | ----- | ----------------------- |
| currentUser 聚合结果 | `bff:student:user:{userId}` | 30s | TTL 过期 |
| myClasses 聚合结果 | `bff:student:classes:{userId}` | 30s | TTL 过期 |
| studentDashboard 聚合结果 | `bff:student:dashboard:{userId}` | 5s | TTL 过期(实时性要求高)|
| 权限列表 | `authz:student:{userId}` | 5min | P5 订阅 Kafka 事件失效 |
| 视口配置 | `bff:student:viewports:{userId}` | 5min | TTL 过期 |
### 7.2 缓存规则
1. **仅缓存 Query**Mutation 不缓存
2. **缓存粒度**:按 Query + userId 维度缓存(学生数据隔离 SELF
3. **降级策略**Redis 不可用时,直接走 gRPC 调用(不缓存),返回数据 + `degraded: true`(标记缓存降级)
4. **缓存击穿防护**:使用 `ioredis``GETSET` 或单飞模式(同一 key 并发请求只发一个 gRPC 调用)
---
## §8 变更记录
| 日期 | 版本 | 变更 | 负责人 |
| ---------- | ---- | -------------------------------------------------------------------- | ------ |
| 2026-07-09 | v1 | 初始创建 | ai04 |
| 2026-07-10 | v2 | 对齐 coord B1-B8 + president §2.2-2.9 裁决schema 路径/错误响应/越权防御/DownstreamClient/跨阶段扩展/readyz 探针/降级模式/缓存策略) | ai04 |

View File

@@ -1,24 +1,221 @@
# student-bff 问题记录
> 负责人ai04
> 关联:[coord.md](../coord.md)、[contracts/student-bff_contract.md](../contracts/student-bff_contract.md)
> 关联:[coord.md](../coord.md)、[contracts/student-bff_contract.md](../contracts/student-bff_contract.md)、[matrix.md](../matrix.md)
> 规则AI 遇到问题时在此追加条目coord 仲裁后更新状态
> 仲裁依据:[coord-final-decisions.md](../../coord-final-decisions.md) §2 BFF 专项裁决B1-B8、[president-final-rulings.md](../../president-final-rulings.md)
---
## 问题列表
## §0 已有仲裁核查总结2026-07-10 审查)
<!--
追加条目格式:
> 本次审查对历史 issues.md 中 ai04 提请的 4 项问题ISSUE-028/029/030/031-ai04逐一核查总裁裁决落地情况。
### ISSUE-[编号]-[AI标识][标题]
| 编号 | 主题 | 总裁裁决章节 | 裁决要点 | 核查结果 |
| ---- | ---- | ------------ | -------- | -------- |
| ISSUE-028-ai04 | 02 文档与 B1+B2 裁决冲突需回写 | president §3.4 | ai04 须在批次 2 启动前回写 student-bff 02B1 GraphQL + B2 gRPC + B8 DownstreamClient | ⚠️ **未执行**02-architecture-design.md 仍为 REST 设计§4 21 个 REST 端点 / §9.2 REST→GraphQL 演进 / §9.3 HTTP→gRPC 演进),违反 B1、B2 |
| ISSUE-029-ai04 | P3 启动前置依赖确认4 项强阻塞) | president §4.1 / §6.1 | 批次 0 完成信号机制 + 批次 2 启动条件:批次 1 P2.1 完成 + ai03 DownstreamClient 抽象就绪 | ✅ **已裁决**:批次时间线 §6.1 明确批次 2 启动条件;前置依赖检查清单机制已建立 |
| ISSUE-030-ai04 | student-bff GraphQL schema 第一版仲裁时机 | president §2.2 | ai04 起草 schema批次 1 等待期coord 在批次 2 启动前仲裁第一版;存放 `packages/shared-ts/contracts/graphql/student-bff.schema.graphql` | ✅ **已裁决**schema 仲裁机制已建立§2.2);但 schema 第一版尚未起草,需 ai04 在批次 1 等待期产出 |
| ISSUE-031-ai04 | issues.md 编号冲突 | president §0.4 | 保留原始内容不删除,用 `ISSUE-XXX-<提请AI>` 格式唯一定位,不重新编号 | ✅ **已裁决**:编号规则已生效,本文件即按新流程在 objections/ 下维护 |
- **提请方**aiXX
- **日期**YYYY-MM-DD
- **类型**:契约不明确 / 工作量超批 / 前置依赖缺失 / 编号冲突 / 其他
- **描述**[详细描述问题]
- **建议方案**[AI 的建议]
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
### 0.1 核查结论
(暂无问题)
- **唯一未落地项**ISSUE-028-ai0402 文档回写。02-architecture-design.md 当前内容与 B1/B2/B8 裁决严重冲突,须在批次 2 启动前完成回写。
- **schema 第一版未起草**ISSUE-030-ai04 虽已建立仲裁机制,但 ai04 尚未产出 student-bff GraphQL schema 草案,需在批次 1 等待期完成。
- **01-understanding.md 同步问题**:阶段 1 文档同样存在 REST 假设与错误码前缀错误(详见 §2需与 02 文档一并修正。
---
## §1 待 coord 仲裁的新问题(本次审查发现)
### ISSUE-STU-001-ai0401-understanding.md 与 B1/B2/B5 裁决冲突
- **提请方**ai04
- **日期**2026-07-10
- **类型**:裁决冲突(文档未回写)
- **描述**:阶段 1 文档 `services/student-bff/docs/01-understanding.md` 存在 3 项与已裁决规则的冲突:
1. **B1 冲突API 风格)**§3.2 暴露 14 个 REST 端点(`/student/dashboard`§4 / §4.1 明确"先对齐 teacher-bff 现状REST + fetch",建议"P3 阶段先 REST后续统一升级 GraphQL"。与 B1"P2 起直接 GraphQL"冲突,属禁止的"中间过渡方案"。
2. **B2 冲突(下游通信)**§3.1 表述"BFF→Service 走 HTTP fetch当前阶段"§4 技术栈"HTTP fetch当前阶段对齐 teacher-bff 模式)"。与 B2"首次实现即 gRPC 调用下游"冲突。
3. **B5 冲突(错误码前缀)**§3.3 / §6 表格用 `STUDENT_BFF_` 前缀。与 B5"统一 BFF_ 前缀BFF_TEACHER_ / BFF_STUDENT_ / BFF_PARENT_"冲突。
- **建议方案**:与 ISSUE-028-ai04 合并处理01 文档随 02 文档一并回写:
- 删除 REST 端点清单,改为 GraphQL Query/Mutation 清单
- 删除"HTTP fetch 对齐 teacher-bff 现状"表述,改为"gRPC 调用下游(@grpc/grpc-js + @bufbuild/protobuf"
- 错误码前缀统一为 `BFF_STUDENT_`
- §7.2"待 coord 仲裁"项中B1/B2/B3/B5/B6/B7 已裁决,删除重复提请
- **状态**:待 coord 确认回写范围(是否 01 文档也纳入回写义务)
### ISSUE-STU-002-ai0402-architecture-design.md 引用不存在的 004 章节
- **提请方**ai04
- **日期**2026-07-10
- **类型**:契约不明确(文档引用错误)
- **描述**02-architecture-design.md 多处引用"004 §11.4 错误码前缀矩阵"和"004 §11.5 统一响应信封 ActionState",但实际 004_architecture_impact_map.md §11 仅包含:
- §11.1 Protobuf 契约体系
- §11.2 契约规则
- §11.3 BFF 聚合模式
- **不存在 §11.4 和 §11.5**
- **影响**:错误码前缀 BFF_STUDENT_ 的权威来源应改为 [coord-final-decisions.md](../../coord-final-decisions.md) G14 + B5ActionState 信封规范应改为 [coord-final-decisions.md](../../coord-final-decisions.md) G8 + F9
- **建议方案**02 文档回写时修正引用源coord 确认是否需要在 004 补充 §11.4/§11.5 章节,或统一指向 coord-final-decisions
- **状态**:待 coord 仲裁
### ISSUE-STU-003-ai0402 文档 §8.3 列 12 项未决决策,其中 8 项已裁决
- **提请方**ai04
- **日期**2026-07-10
- **类型**:裁决冲突(文档未回写)
- **描述**02-architecture-design.md §8.3"未决设计决策(待 coord 仲裁)"列出 12 项,但其中 8 项已被 coord-final-decisions §2 裁决:
| # | 决策点 | 02 文档建议 | 已裁决结论 | 裁决章节 |
| --- | ------ | ----------- | ---------- | -------- |
| 1 | BFF API 风格 | AP3 REST | **B1P2 起直接 GraphQL** | coord-final-decisions §2 B1 |
| 2 | BFF 是否做权限校验 | A不校验 | **B3BFF 豁免 @RequirePermission** | coord-final-decisions §2 B3 |
| 3 | 自我越权防御 | B | **B4全部 BFF 强制自我越权防御** | coord-final-decisions §2 B4 |
| 4 | /readyz 检查逻辑 | AP3 直接 ok | **G2 + §2.4:按阶段扩展探针,必需依赖失败 503可选依赖软失败** | president §2.4 |
| 5 | Kafka 事件订阅时机 | AP3 不订阅) | **B7P2-P4 不订阅 KafkaP5 后订阅** | coord-final-decisions §2 B7 |
| 6 | 缓存策略 | BRedis 5-30s | **B6Redis 5-30s 短缓存** | coord-final-decisions §2 B6 |
| 8 | 错误码前缀 | BFF_STUDENT_ | **B5BFF_STUDENT_** | coord-final-decisions §2 B5 |
| 9 | DownstreamClient 回写 | B回写 | **B8回写 teacher-bff3 个 BFF 统一** | coord-final-decisions §2 B8 |
仅剩 #7(端口 3009#10CQRS 不引入)、#11SSE 实现)、#12(熔断器引入)属合理的设计决策,但 #12 熔断器 president §2.4 已暗示按阶段评估。
- **建议方案**02 文档回写时删除已裁决项的"待仲裁"标注,改为"已裁决(见 coord-final-decisions §2 BX"
- **状态**:待 coord 确认(与 ISSUE-028-ai04 合并处理)
### ISSUE-STU-004-ai04student-bff GraphQL schema 第一版尚未起草
- **提请方**ai04
- **日期**2026-07-10
- **类型**:前置依赖缺失
- **描述**president §2.2 裁决"ai04 起草 student-bff schema批次 1 等待期coord 在批次 2 启动前仲裁第一版"。当前批次 1 已启动2026-07-10ai04 处于批次 1 等待期,但 schema 草案尚未产出。02-architecture-design.md 仍是 REST 设计,未定义 GraphQL Query/Mutation/Type。
- **影响**:阻塞 ai14student-portalP3 启动(依赖 schema 契约);阻塞 coord 批次 2 启动前仲裁
- **建议方案**ai04 在批次 1 等待期优先产出 student-bff GraphQL schema 草案,存放 `packages/shared-ts/contracts/graphql/student-bff.schema.graphql`,提交 coord 仲裁
- **状态**ai04 自行执行(非 coord 仲裁项),记录待办
---
## §2 01-understanding.md 审查详情
### 2.1 准确性问题
| 位置 | 问题 | 严重度 |
| ---- | ---- | ------ |
| §1 通信方式(入/出) | 表述"HTTP REST当前阶段/ HTTP fetch当前阶段",实际 B1/B2 裁决为 GraphQL + gRPC | 高 |
| §3.1 表格 | 列"当前 REST 端点(实际可用)"列,暗示走 REST但 BFF 是新服务不存在"当前 REST 现状" | 高 |
| §3.2 端点表 | 14 个 REST 端点,违反 B1 GraphQL | 高 |
| §3.3 错误码前缀 | `STUDENT_BFF_` 违反 B5 `BFF_STUDENT_` | 中 |
| §4 技术栈 API 风格 | "HTTP REST当前阶段",违反 B1 | 高 |
| §6 权限装饰器 | "⚠️ 不对齐"结论正确B3 豁免),但理由应补充"已裁决 B3" | 低 |
| §7.2 决策点 1-5 | 列为"待 coord 仲裁",但 B1/B2/B3/B5/B6/B7 均已裁决 | 高 |
### 2.2 遗漏项
| 遗漏内容 | 应补充位置 | 依据 |
| -------- | ---------- | ---- |
| GraphQL schema 设计意图Query/Mutation/Type | §3.2 | B1 + president §2.2 |
| gRPC 下游调用设计(@grpc/grpc-js + @bufbuild/protobuf | §3.1 / §4 | B2 |
| DataLoader 防 N+1 策略 | §4 | 004 §11.3 + B1 |
| B4 自我越权防御userId 强制比对) | §2.3 / §3 | B4 |
| B8 DownstreamClient 抽象(复用 teacher-bff | §4 / §6 | B8 |
| GraphQL schema 存放路径 `packages/shared-ts/contracts/graphql/student-bff.schema.graphql` | §3.2 | president §2.2 |
| GraphQL errors 数组 + extensions.code + extensions.traceId 错误格式 | §3.3 | president §2.2 #5 |
| Relay Cursor Connections 分页规范 | §3.2 | president §2.2 #5 |
---
## §3 02-architecture-design.md 审查详情
### 3.1 架构合理性评估
| 维度 | 评估 | 说明 |
| ---- | ---- | ---- |
| 分层设计§1 | ✅ 合理 | Controller → Service → Aggregator → Cache → DownstreamClient 五层清晰DownstreamClient/Aggregator/Transformer 抽象优于 teacher-bff 现状,符合 B8 |
| 领域模型§2 | ✅ 合理 | "场景聚合视图"概念恰当BFF 无领域模型DataScope=SELF 强制实现§2.3)符合 B4 |
| 缓存设计§3 | ✅ 合理 | Redis Key 规范、TTL 分档、失效策略完整,符合 B6 |
| API 设计§4 | ❌ 严重冲突 | 21 个 REST 端点违反 B1 GraphQL须改为 GraphQL Query/Mutation |
| 事件设计§5 | ⚠️ 部分冲突 | 订阅清单合理,但 B7 裁决 P2-P4 不订阅 Kafka§5 应明确"P5 才落地"且标注 B7 |
| 横切关注点§6 | ✅ 合理 | logger/metrics/tracer/health/优雅关闭对齐黄金模板;错误码 BFF_STUDENT_ 正确§0.2 已修正) |
| 契约矩阵§7 | ⚠️ 需更新 | 下游通信"HTTP→gRPC P3+"表述违反 B2应改为"gRPC 首次实现即用" |
| 风险与假设§8 | ⚠️ 需更新 | §8.3 12 项未决决策中 8 项已裁决(见 ISSUE-STU-003 |
| 演进路线§9 | ❌ 严重冲突 | §9.2 REST→GraphQL、§9.3 HTTP→gRPC 违反"不分阶段"原则president §0.3 |
| 扩展点§10 | ✅ 优秀 | 多端适配/国际化/多角色复用/离线模式/AI 增强/学习路径预留设计前瞻 |
| 性能容量§11 | ✅ 合理 | SLO 分级、容量规划、限流策略完整 |
| 安全合规§12 | ✅ 合理 | 身份认证/授权隔离/输入安全/数据合规/审计完整 |
| 可观测性§13 | ✅ 合理 | 日志规范/span/告警/Grafana 面板完整 |
### 3.2 长远性评估
| 长远性维度 | 评估 | 说明 |
| ---------- | ---- | ---- |
| 多角色复用§9.5 | ✅ | 学习委员/课代表/走读生差异化通过视口扩展,无需改代码 |
| 多端适配§10.1 | ✅ | Transformer 层按 x-client-type 裁剪H5/小程序可扩展 |
| GraphQL 演进§9.2 | ❌ | 规划为"P6+ 可选",但 B1 已裁决 GraphQL 是起点非终点,须移除"可选" |
| 通信协议演进§9.3 | ❌ | 规划"P3 HTTP → P4 gRPC 混合 → P6 Service Mesh",违反 B2"首次实现即 gRPC" |
| 推送通道演进§9.4 | ✅ | P3 无推送 → P5 SSE → P5+ WebSocket → P6+ 移动端推送,渐进合理 |
| AI 答疑增强§10.5 | ✅ | 预留 context 参数,支持多步编排 |
| 国际化§10.2 | ✅ | 预留 I18nContext 接入点 |
### 3.3 业界架构文档规范符合度
| 规范项 | 符合度 | 说明 |
| ------ | ------ | ---- |
| 文档导航/导读 | ✅ | §0.3 文档结构清晰16 章覆盖完整 |
| 设计原则 | ✅ | §0.1 列 P1-P9 九项原则 |
| 架构图C4 模型) | ✅ | §1 物理分层图 + 调用链时序图,符合 C4 Level 2/3 |
| ADR 决策记录 | ⚠️ | §8.3 列决策但未用 ADR 格式,且已裁决项未更新 |
| 非功能性需求 | ✅ | §11 性能 SLO + §12 安全 + §13 可观测性 |
| 演进路线 | ⚠️ | 有 §9 但违反"不分阶段"原则 |
| 实施清单 | ✅ | §14 P3-P6 分阶段清单完整 |
| 风险登记 | ✅ | §8.1 技术风险 + §8.2 外部依赖假设 |
### 3.4 关键遗漏
| 遗漏内容 | 应补充位置 | 依据 |
| -------- | ---------- | ---- |
| GraphQL Schema 完整定义Query/Mutation/Type/Enum | 新增 §4.2 或独立 §5 | B1 + president §2.2 |
| DataLoader 批量策略(哪些 Query 需要 DataLoader | §1.2 或 §6 | 004 §11.3 + B1 |
| gRPC client 设计channel 复用、interceptor、metadata 透传) | §1.2 或 §7 | B2 |
| GraphQL errors 数组扩展 ActionState 字段规范 | §6.2 错误码清单 | president §2.2 #3 |
| Relay Cursor Connections 分页规范 | §4 API 设计 | president §2.2 #5 |
| GraphQL schema 存放路径与 codegen 配置 | §14 实施清单 | president §2.2 #4 |
| AuthorizationGuard 接口设计B4 越权防御 P3 实现方式) | §2.3 或 §6 | president §2.9(参照 teacher-bff ISSUE-033-ai03 |
---
## §4 历史问题归档
> 以下问题原记录在 `docs/issues.md`(旧流程),现按新流程归档至此。总裁裁决详见 [president-final-rulings.md](../../president-final-rulings.md) §10 问题索引表。
### ISSUE-028-ai04ai04 02-architecture-design.md 与 coord B1+B2 裁决冲突需回写
- **提请方**ai04student-bff + parent-bff
- **日期**2026-07-09
- **类型**:裁决冲突(文档回写义务)
- **描述**02-architecture-design.md 与 coord-final-decisions B1P2 起直接 GraphQL+ B2首次实现即 gRPC存在 2 项重大冲突§4 API 设计决策为 REST§9.2 演进路线 REST→GraphQL
- **裁决**president §3.4 — ai04 须在批次 2 启动前回写 student-bff 02B1 GraphQL + B2 gRPC + B8 DownstreamClient
- **状态**:⚠️ 未执行(详见 §0 核查)
### ISSUE-029-ai04ai04 P3 启动前置依赖确认4 项强阻塞)
- **提请方**ai04student-bff + parent-bff
- **日期**2026-07-09
- **类型**:前置依赖未就绪
- **描述**P3 启动依赖 4 项强阻塞前置core_edu.proto 补全 / buf.gen.yaml gRPC 插件 / ai03 DownstreamClient 抽象 / ai08 core-edu gRPC server
- **裁决**president §4.1 / §6.1 — 批次 0 完成信号机制 + 批次 2 启动条件明确
- **状态**:✅ 已裁决
### ISSUE-030-ai04ai04⟷ai14 student-bff GraphQL schema 第一版仲裁时机
- **提请方**ai04student-bff + parent-bff
- **日期**2026-07-09
- **类型**:契约不明确
- **描述**student-bff GraphQL schema 第一版仲裁时机未明确
- **裁决**president §2.2 — ai04 起草 schema批次 1 等待期coord 在批次 2 启动前仲裁第一版
- **状态**:✅ 已裁决schema 第一版待 ai04 起草,见 ISSUE-STU-004
### ISSUE-031-ai04issues.md 编号冲突
- **提请方**ai04student-bff + parent-bff
- **日期**2026-07-09
- **类型**:工作归属不明(文档规范)
- **描述**issues.md ISSUE-024/025 编号冲突ai11 与 ai09 重复)
- **裁决**president §0.4 — 保留原始内容,用 `ISSUE-XXX-<提请AI>` 格式定位,不重新编号
- **状态**:✅ 已裁决

View File

@@ -1,45 +1,362 @@
# student-bff 工作排期
> 负责人ai04
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/student-bff_contract.md](../contracts/student-bff_contract.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/student-bff_contract.md](../contracts/student-bff_contract.md)、[objections/student-bff_issue.md](../objections/student-bff_issue.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,开发期间用 mock最后统一集成测试)
> 裁决依据:[coord-final-decisions.md](../../coord-final-decisions.md) §2 B1-B8、[president-final-rulings.md](../../president-final-rulings.md) §2.2/§3.4/§6.1
---
## §1 总览
student-bff 为学生端提供 GraphQL 聚合 API,覆盖 Dashboard、考试作答、作业提交等场景。全阶段目标P2 GraphQL schema 骨架 → P3 Dashboard+考试+作业聚合 → P4-P6 持续优化
student-bff 为学生端提供 **GraphQL 聚合 API**B1 裁决P2 起直接 GraphQL + DataLoader下游通过 **gRPC** 调用业务服务B2 裁决:首次实现即 gRPC复用 teacher-bff 产出的 **DownstreamClient 抽象**B8 裁决)
- **阶段归属**P3 核心教学阶段(批次 2
- **端口**3009HTTP GraphQL endpoint
- **路由前缀**`/student`api-gateway 代理 `/api/v1/student/*` → student-bff:3009
- **schema 存放**`packages/shared-ts/contracts/graphql/student-bff.schema.graphql`president §2.2
- **核心场景**:学生 Dashboard 聚合 + 考试作答 + 作业提交 + 成绩查看
### 1.1 关键裁决对齐
| 裁决 | 结论 | 对齐方式 |
| ---- | ---- | -------- |
| B1 API 风格 | P2 起直接 GraphQLYoga + DataLoader | P3 首次实现即 GraphQL禁止 REST |
| B2 下游通信 | 首次实现即 gRPC | @grpc/grpc-js + @bufbuild/protobuf,禁止 HTTP fetch |
| B3 权限装饰器 | BFF 豁免 @RequirePermission | 仅校验 x-user-id 存在,权限交下游 |
| B4 越权防御 | 全部 BFF 强制 | AuthorizationGuard 强制 userId 比对 |
| B5 错误码前缀 | BFF_STUDENT_ | 统一 BFF_ 前缀 |
| B6 缓存策略 | Redis 5-30s 短缓存 | CacheInterceptor + Redis |
| B7 Kafka 订阅 | P2-P4 不订阅P5 后订阅 | P3/P4 纯同步聚合P5 引入 EventSubscriber |
| B8 DownstreamClient | 回写 teacher-bff3 BFF 统一 | 复用 ai03 P2 产出的抽象 |
---
## §2 全阶段甘特图(P2-P6各 AI 自行细化
## §2 全阶段甘特图(批次 1 等待期 + 批次 2-5
```mermaid
gantt
title ai04 student-bff 全阶段排期
title ai04 student-bff 全阶段排期(对齐总裁 §6.1 批次时间线)
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2-P6
[阶段任务] :a4a, 2026-07-10, Xd
section 批次1等待期
W0.1 GraphQL schema 第一版起草 :crit, w1, 2026-07-10, 3d
W0.2 01/02 文档回写(B1/B2/B5/B8) :crit, w2, after w1, 3d
W0.3 schema 提交 coord 仲裁 :milestone, w3, after w2, 0d
section 批次2 P3 核心
P3.1 NestJS+GraphQL Yoga 骨架 :crit, p1, after w3, 2d
P3.2 DownstreamClient+gRPC client(iam+core-edu) :crit, p2, after p1, 3d
P3.3 核心 Query Resolver(dashboard/homework/grades/exams) :crit, p3, after p2, 3d
P3.4 Mutation(submitHomework)+AuthorizationGuard(B4) :crit, p4, after p3, 2d
P3.5 DataLoader+N+1防御 :p5, after p4, 1d
P3.6 Redis缓存(B6)+ActionState信封 :p6, after p4, 1d
P3.7 /healthz+/readyz探针(iam+core-edu) :p7, after p6, 1d
P3.8 横切关注点(logger/metrics/tracer/error filter) :p8, after p6, 2d
P3.9 单元测试(覆盖率≥80%) :p9, after p8, 2d
section 批次3 P4 扩展
P4.1 content gRPC client(textbooks/chapters/questions) :p10, after p9, 3d
P4.2 data-ana gRPC client(weakness/trend) :p11, after p10, 2d
P4.3 Query 扩展(myTextbooks/myWeakness/myTrend) :p12, after p11, 2d
P4.4 /readyz 扩展探针(content+data-ana) :p13, after p12, 1d
P4.5 Dashboard Resolver 字段扩展 :p14, after p12, 1d
section 批次4 P5 扩展
P5.1 msg gRPC client(notifications) :p15, after p14, 2d
P5.2 ai gRPC client(chat/streamChat SSE) :p16, after p15, 3d
P5.3 Query/Mutation 扩展(myNotifications/markAsRead/aiChat) :p17, after p16, 2d
P5.4 Kafka EventSubscriber(B7 P5订阅) :p18, after p17, 2d
P5.5 push-gateway 推送通道 :p19, after p18, 2d
P5.6 /readyz 扩展探针(msg+ai) :p20, after p19, 1d
section 批次5 P6 硬化
P6.1 熔断器(opossum)完善 :p21, after p20, 2d
P6.2 HPA+全链路可观测 :p22, after p21, 2d
P6.3 灾备演练+99.9%可用性 :p23, after p22, 3d
```
> **注意**:以上为 coord 初始规划ai04 接管后必须自行细化为完整 P2-P6 排期。
> **关键路径**critschema 起草 → 文档回写 → P3 骨架 → gRPC client → Query Resolver → Mutation+Guard
> **总时间线**:批次 1 等待期 6 天 + 批次 2 P3 约 17 天 + 批次 3 P4 约 9 天 + 批次 4 P5 约 12 天 + 批次 5 P6 约 7 天
---
## §3 详细任务
### 全阶段任务
### 3.1 批次 1 等待期2026-07-10 起,约 6 天)
#### W0.1 GraphQL schema 第一版起草
- **负责人**ai04
- **交付物**:⚠️ 由 ai04 自行补充
- **依赖**:见 [contracts/student-bff_contract.md](../contracts/student-bff_contract.md)
- **验收标准**:⚠️ 由 ai04 自行补充
- **依赖**president §2.2 裁决 ai04 在批次 1 等待期起草)
- **交付物**
- `packages/shared-ts/contracts/graphql/student-bff.schema.graphql` 第一版
- 包含 Query/Mutation 清单 + 类型定义 + 权限点标注(`# @permission:`+ DataScope 标注(`# @dataScope: SELF`
- 分页采用 Relay Cursor Connections 规范president §2.2 #5
- 错误格式GraphQL errors 数组 + `extensions.code` + `extensions.traceId`president §2.2 #3
- **验收标准**
- P3 核心 QuerystudentDashboard / myHomework / myGrades / myExams / myClasses / currentUser
- P3 核心 MutationsubmitHomework
- 提交 coord 仲裁president §2.2coord 在批次 2 启动前仲裁第一版)
- **状态**:⏳ 待办(见 objections ISSUE-STU-004
#### W0.2 01/02 文档回写
- **负责人**ai04
- **依赖**:无
- **交付物**
- `services/student-bff/docs/01-understanding.md` 回写ISSUE-STU-001
- §3.2 REST 端点 → GraphQL Query/Mutation 清单
- §3.1/§4 HTTP fetch → gRPC 下游调用
- §3.3/§6 错误码 `STUDENT_BFF_``BFF_STUDENT_`
- §7.2 删除已裁决的"待仲裁"项
- `services/student-bff/docs/02-architecture-design.md` 回写ISSUE-028-ai04
- §4 21 个 REST 端点 → GraphQL Schema 设计
- §9.2 删除 REST→GraphQL 演进,改为 GraphQL 即起点
- §9.3 删除 HTTP→gRPC 演进,改为 gRPC 首次实现即用
- §8.3 删除 8 项已裁决的"待仲裁"标注ISSUE-STU-003
- 修正 §11.4/§11.5 错误引用ISSUE-STU-002
- 补充 GraphQL Schema / DataLoader / gRPC client / AuthorizationGuard 设计
- **验收标准**:与 coord-final-decisions §2 B1-B8 + president §2.2 完全一致
- **状态**:⏳ 待办
### 3.2 批次 2 P3 核心教学(约 17 天)
#### P3.1 NestJS + GraphQL Yoga 骨架
- **负责人**ai04
- **依赖**:批次 1 完成iam gRPC 50052 + ai03 DownstreamClient 抽象就绪president §6.1
- **交付物**
- `services/student-bff/` 服务骨架(克隆 teacher-bff 结构B8 复用 shared/
- `src/app.module.ts` + `src/main.ts`(端口 3009
- GraphQL Yoga endpoint`POST /graphql`+ Playground开发环境
- `package.json`@edu/student-bff+ `tsconfig.json`NodeNext ESM+ `nest-cli.json`
- `Dockerfile`多阶段构建EXPOSE 3009
- **验收标准**`POST /graphql` 返回 200 + schema 内省可用
- **状态**:⏳ 待办
#### P3.2 DownstreamClient + gRPC clientiam + core-edu
- **负责人**ai04
- **依赖**P3.1 + ai03 teacher-bff P2 产出的 DownstreamClient 抽象B8
- **交付物**
- `src/shared/downstream/downstream-client.ts`(复用 teacher-bff 抽象B8
- gRPC client 配置iam:50052 + core-edu:50053
- `src/config/env.ts`IamGrpcUrl + CoreEduGrpcUrl + 超时/重试参数
- gRPC interceptortraceId 透传 + 错误归一化
- **验收标准**:可调用 iam.GetUserInfo + core-edu.HomeworkService.ListHomeworkByClass
- **状态**:⏳ 待办
#### P3.3 核心 Query Resolver
- **负责人**ai04
- **依赖**P3.2 + coord 仲裁的 schema 第一版
- **交付物**
- `src/student/resolvers/dashboard.resolver.ts`studentDashboard聚合 iam + core-edu
- `src/student/resolvers/homework.resolver.ts`myHomeworkcore-edu
- `src/student/resolvers/grades.resolver.ts`myGradescore-eduB4 强制 userId 比对)
- `src/student/resolvers/exams.resolver.ts`myExamscore-edu
- `src/student/resolvers/classes.resolver.ts`myClassescore-edu
- `src/student/resolvers/auth.resolver.ts`currentUser聚合 iam.GetUserInfo + GetEffectivePermissions + GetViewports
- 并行编排Promise.allSettled + 部分降级president §2.6 方案 Bdata 内 degraded 字段)
- **验收标准**5 个核心 Query 可执行,返回 ActionState 信封
- **状态**:⏳ 待办
#### P3.4 MutationsubmitHomework+ AuthorizationGuardB4
- **负责人**ai04
- **依赖**P3.3
- **交付物**
- `src/student/resolvers/homework.mutation.resolver.ts`submitHomework Mutation
- `src/student/guards/authorization.guard.ts`B4 自我越权防御
- 接口:`canAccessOwnData(userId, requestedStudentId): Promise<boolean>`
- P3 实现:强制 `studentId === userId`(学生只能操作自己数据)
- 参照 teacher-bff ISSUE-033-ai03 的 AuthorizationGuard 模式president §2.9
- Zod 输入校验SubmitHomeworkInput schema
- **验收标准**submitHomework 可提交越权请求studentId ≠ userId返回 BFF_STUDENT_FORBIDDEN
- **状态**:⏳ 待办
#### P3.5 DataLoader + N+1 防御
- **负责人**ai04
- **依赖**P3.3
- **交付物**
- `src/student/dataloaders/homework.loader.ts`:批量加载作业
- `src/student/dataloaders/grades.loader.ts`:批量加载成绩
- Dashboard 内多学生场景用 DataLoader 批量去重004 §11.3
- **验收标准**N+1 查询场景下下游 gRPC 调用数 ≤ 2
- **状态**:⏳ 待办
#### P3.6 Redis 缓存B6+ ActionState 信封
- **负责人**ai04
- **依赖**P3.3
- **交付物**
- `src/shared/cache/cache.module.ts`Redis CacheInterceptor
- 缓存 Key 规范:`student:dashboard:{userId}`TTL 5-30sB6
- ActionState 信封:`{success, data, meta?}` / `{success: false, error: {code, message, details?, traceId?}}`
- 降级模式:`data.degraded = true` + `data.degradedReason`president §2.6 方案 B
- **验收标准**:缓存命中时 P50 < 100ms降级响应符合方案 B
- **状态**:⏳ 待办
#### P3.7 /healthz + /readyz 探针
- **负责人**ai04
- **依赖**P3.2
- **交付物**
- `src/shared/health/health.controller.ts`/healthzliveness+ /readyzreadiness
- P3 /readyz 探针iam gRPC 50052 + core-edu gRPC 500532 项president §2.4
- 必需依赖失败返回 503可选依赖软失败返回 200 + degraded
- **验收标准**/readyz 返回 2 项探针状态
- **状态**:⏳ 待办
#### P3.8 横切关注点
- **负责人**ai04
- **依赖**P3.1
- **交付物**
- `src/shared/observability/logger.ts`pinoservice: 'student-bff'
- `src/shared/observability/metrics.ts`prom-client11 个 student_bff_* 指标)
- `src/shared/observability/tracer.ts`OTelserviceName: 'student-bff'
- `src/shared/errors/global-error.filter.ts`@Catch()BFF_STUDENT_* 错误码)
- `src/shared/errors/application-error.ts`(错误类层次)
- 优雅关闭SIGTERM → app.close() → shutdownTracer()
- **验收标准**/metrics 可访问GlobalErrorFilter 捕获所有异常
- **状态**:⏳ 待办
#### P3.9 单元测试
- **负责人**ai04
- **依赖**P3.3-P3.8
- **交付物**
- `test/unit/resolvers/*.test.ts`Resolver 聚合逻辑mock gRPC 下游)
- `test/unit/guards/*.test.ts`AuthorizationGuard 越权防御
- `test/unit/dataloaders/*.test.ts`DataLoader 批量逻辑
- `vitest.config.ts`(对齐 classes 测试框架)
- **验收标准**:覆盖率 ≥ 80%
- **状态**:⏳ 待办
### 3.3 批次 3 P4 内容分析扩展(约 9 天)
#### P4.1-P4.2 content + data-ana gRPC client
- **负责人**ai04
- **依赖**:批次 3 启动content gRPC 50054 + data-ana gRPC 50055 就绪)
- **交付物**
- content gRPC clientTextbookService + ChapterService + QuestionService + KnowledgeGraphService
- data-ana gRPC clientAnalyticsService.GetStudentWeakness + GetLearningTrend
- **验收标准**:可调用 content + data-ana gRPC RPC
- **状态**:⏳ 待办(属"跨阶段扩展例外"president §2.3 允许新增下游 gRPC 调用)
#### P4.3-P4.5 Query 扩展 + 探针扩展 + Dashboard 字段扩展
- **交付物**
- Query 扩展myTextbooks / myChapters / myQuestions / myLearningPath / myWeakness / myTrend
- /readyz 扩展探针:+ content 50054 + data-ana 50055共 4 项)
- Dashboard Resolver 字段扩展null 字段 → 真实 data-ana 数据president §2.3 #4 允许)
- **状态**:⏳ 待办
### 3.4 批次 4 P5 沟通 AI 扩展(约 12 天)
#### P5.1-P5.3 msg + ai gRPC client + Query/Mutation 扩展
- **交付物**
- msg gRPC clientNotificationService.ListNotifications + MarkAsRead
- ai gRPC clientAiService.Chat + StreamChatSSE 流式透传)
- Query/Mutation 扩展myNotifications / markAsRead Mutation / aiChat / aiStreamChat
- **状态**:⏳ 待办
#### P5.4-P5.6 Kafka EventSubscriber + push-gateway + 探针扩展
- **交付物**
- `src/student/events/event-subscriber.ts`Kafka 消费者组B7 P5 才订阅)
- 订阅 topicedu.homework.events / edu.exam.events / edu.grade.events / edu.identity.user.role_changed
- 幂等性Redis SETNX event_id 去重
- push-gateway 推送通道POST /push/user/:userId
- /readyz 扩展探针:+ msg 50056 + ai 50058共 6 项)
- **状态**:⏳ 待办
### 3.5 批次 5 P6 硬化(约 7 天)
#### P6.1-P6.3 熔断器 + HPA + 灾备
- **交付物**
- 熔断器opossum完善每个下游 gRPC client 独立熔断器
- HPA 自动扩缩容配置
- 全链路 trace + Grafana 仪表盘
- 灾备演练 + 99.9% 可用性压测
- **状态**:⏳ 待办
---
## §4 依赖与就绪信号
- **我依赖**:⚠️ 由 ai04 自行补充(见 contract.md
- **我的就绪信号**:⚠️ 由 ai04 自行补充
### 4.1 我依赖的上游就绪标志
| 依赖项 | 提供 AI | 就绪信号 | 阻塞阶段 | 状态 |
| ------ | ------- | -------- | -------- | ---- |
| core_edu.proto 补全AttendanceService / GetClassesByTeacher | coordpresident §2.5 | proto message + RPC 签名定义 | 批次 2 P3 | ⏳ |
| buf.gen.yaml gRPC 插件 | coord批次 0.9 | grpc/node 插件配置 | 批次 2 P3 | ⏳ |
| ai03 DownstreamClient 抽象 | ai03B8 裁决) | teacher-bff P2 产出可复用抽象 | 批次 2 P3 | ⏳ |
| iam gRPC 50052 + 12 RPC | ai06I1 裁决) | HealthService.Check = SERVING | 批次 2 P3 | ⏳ |
| core-edu gRPC 50053 + 22 RPC | ai08C2 裁决) | HealthService.Check = SERVING | 批次 2 P3 | ⏳ |
| content gRPC 50054 + 18 RPC | ai09 | HealthService.Check = SERVING | 批次 3 P4 | ⏳ |
| data-ana gRPC 50055 + 12 RPC | ai11 | HealthService.Check = SERVING | 批次 3 P4 | ⏳ |
| msg gRPC 50056 + 13 RPC | ai10 | HealthService.Check = SERVING | 批次 4 P5 | ⏳ |
| ai gRPC 50058 + 6 RPC | ai12 | HealthService.Check = SERVING | 批次 4 P5 | ⏳ |
| coord 仲裁 student-bff schema 第一版 | coordpresident §2.2 | schema 第一版裁定 | 批次 2 P3 | ⏳ |
| api-gateway `/student` 路由 | ai01 | /api/v1/student/* 可代理 | 批次 2 P3 | ⏳ |
### 4.2 我的就绪标志(供下游消费)
| 就绪标志 | 验证方式 | 消费方 |
| -------- | -------- | ------ |
| student-bff GraphQL :3009 启用 | GET /healthz 返回 200 | k8s / 监控 |
| /readyz 返回 200含下游 gRPC 连通性) | GET /readyz 返回 200 + checks | k8s / 监控 |
| GraphQL schema 可内省 | POST /graphql 返回 schema | ai14student-portal |
| 核心 Query 可执行 | studentDashboard / myHomework / myGrades / myClasses / currentUser | ai14 |
| 核心 Mutation 可执行 | submitHomework | ai14 |
| /metrics 可访问 | GET /metrics 返回 prometheus 格式 | Prometheus |
---
## §5 Mock 策略(全并行开发期间)
### 5.1 我提供的 mock供 ai14 student-portal
在 student-bff 真实就绪前,为 ai14 提供 GraphQL mock
- **方式**MSW 拦截 POST /graphql + 固定 response
- **mock 数据**
- currentUser 返回固定学生id="student-001", name="李同学", roles=["student"]
- studentDashboard 返回固定仪表盘pendingHomework=3, upcomingExams=2, unreadNotifications=5
- myHomework 返回固定 3 个作业1 个待提交)
- myGrades 返回固定 5 个成绩
- myExams 返回固定 2 个考试
- myClasses 返回固定 1 个班级
### 5.2 我消费的 mock上游未就绪前
| 上游 | mock 方式 | 切换真实时机 |
| ---- | --------- | ------------ |
| iam gRPC | grpc-mock 拦截 + 固定 UserInfo/Permissions/Viewports | iam 就绪信号 ✅ |
| core-edu gRPC | grpc-mock 拦截 + 固定 Homework/Exam/Grade/Class | core-edu 就绪信号 ✅ |
| content gRPC | grpc-mock 拦截 + 固定 Textbook/Chapter/Question | content 就绪信号 ✅ |
| data-ana gRPC | grpc-mock 拦截 + 固定 Weakness/Trend | data-ana 就绪信号 ✅ |
| msg gRPC | grpc-mock 拦截 + 固定 Notification | msg 就绪信号 ✅ |
| ai gRPC | grpc-mock 拦截 + 固定 Chat response | ai 就绪信号 ✅ |
> 所有上游 mock 通过 gRPC client 拦截器实现,上游就绪后移除拦截器切换真实调用(对齐 matrix.md §7 全并行 Mock 策略)。
---
## §6 风险与缓解
| 风险 | 概率 | 影响 | 缓解措施 |
| ---- | ---- | ---- | -------- |
| schema 仲裁延迟阻塞 P3 启动 | 中 | 高 | ai04 批次 1 等待期优先产出 schema 草案W0.1 |
| ai03 DownstreamClient 抽象未就绪 | 中 | 高 | ISSUE-007 已识别coord 验收批次 1 时检查 |
| core_edu.proto 补全延迟 | 低 | 高 | president §2.5 已明确 coord 负责 proto 定义 |
| GraphQL + gRPC 首次实现复杂度高 | 中 | 中 | 复用 teacher-bff P2 模式B8 DownstreamClient + Yoga endpoint |
| 下游 gRPC mock 与真实行为不一致 | 中 | 低 | 集成测试阶段统一验证matrix.md §9 |