19 KiB
student-portal 对接契约
负责人:ai14 关联:matrix.md、coord.md §1 ARB-001、coord.md §2 ARB-002、student-bff_contract.md、teacher-portal_contract.md 版本:v2(ai14 接管审计与补全版,2026-07-10) 修订摘要:GraphQL endpoint 路径修正为
/api/v1/student/graphql(对齐 matrix.md §5)+ 补全 24 个 GraphQL query/mutation(对齐 02 §4.2)+ 补充 ARB-002 MF Shell 暴露清单 + 补充就绪信号明细
§1 我提供什么(对外接口)
1.1 gRPC 接口(如有)
无。student-portal 是前端微前端 Remote。
1.2 HTTP 端点(如有)
| Method | Path | 用途 | 认证 |
|---|---|---|---|
| GET | / | 学生门户首页 | JWT 必需(前端路由守卫) |
| GET | /my-classes | 我的班级 | JWT 必需 |
| GET | /my-exams | 我的考试 | JWT 必需 |
| GET | /my-exams/[id]/take | 考试作答页 | JWT 必需 |
| GET | /my-exams/[id]/result | 考试结果页 | JWT 必需 |
| GET | /my-homework | 我的作业 | JWT 必需 |
| GET | /my-homework/[id]/submit | 作业提交页 | JWT 必需 |
| GET | /my-grades | 我的成绩 | JWT 必需 |
| GET | /my-attendance | 我的考勤 | JWT 必需 |
| GET | /learning-path | 学习路径 | JWT 必需 |
| GET | /dashboard | 学生仪表盘 | JWT 必需 |
| GET | /dashboard/weakness | 学情诊断 | JWT 必需 |
| GET | /dashboard/trend | 学习趋势 | JWT 必需 |
| GET | /textbooks | 教材列表 | JWT 必需 |
| GET | /textbooks/[id]/chapters | 章节列表 | JWT 必需 |
| GET | /notifications | 通知中心 | JWT 必需 |
| GET | /ai-tutor | AI 辅助答疑(P5 可选) | JWT 必需 |
路由前缀:无
/student/前缀(student-portal 作为 MF Remote,由 Shell 路由/student/*加载,内部路由无前缀)。
1.3 GraphQL schema(如 BFF)
不适用。student-portal 消费 student-bff GraphQL,自身不提供 schema。
消费的 schema 文件:
packages/shared-ts/contracts/graphql/student-bff.graphql(已裁决 ARB-019 §21,由 ai04 创建,对齐 ARB-001 §1.3 集中管理原则)
1.4 Kafka 事件发布(如有)
无。
1.5 错误码前缀
无(前端不定义错误码前缀,透传 BFF 错误码 BFF_STUDENT_*,见 matrix.md §6)。
1.6 微前端架构(MF Remote,ARB-002 对齐)
| 角色 | 说明 |
|---|---|
| MF Remote | student-portal 是微前端远程模块(P3 首个 Remote,ARB-002 §2.3),由 teacher-portal AppShell 加载 |
| 暴露的 remote 模块 | ./StudentApp(学生端完整应用) |
| module federation 配置 | apps/student-portal/next.config.js(NextFederationPlugin) |
| Shell 暴露清单(复用) | AppShell / GraphQLProvider / useAuth / usePermission / useGraphQLClient / ErrorBoundary / Loading / Empty / RequirePermission(ARB-002 §2.2) |
| shared singleton | react / react-dom / urql / graphql / @tanstack/react-query / zustand / nuqs / @edu/ui-tokens / @edu/ui-components / @edu/hooks / @edu/contracts / @edu/shared-ts(ARB-002 §2.2) |
| feature flag | NEXT_PUBLIC_MF_ENABLED(P2=false 独立壳,P3=true 接入 Shell) |
| 登录页 | 不实现,未登录跳转 http://localhost:4000/login?redirect=student(ARB-002 §2.3 登录由 Shell 独占) |
1.7 WebSocket 消费(push-gateway)
| 端点 | 用途 | 认证 | 事件类型 |
|---|---|---|---|
ws://push-gateway:8081/ws |
实时通知推送 | JWT | 作业通知 / 考试通知 / 成绩通知 / 系统通知 / 考试延长(ExamExtended) / 考试强制提交(ExamForceSubmitted)(ARB-019 §21.6 已裁决) |
§2 我消费什么(依赖上游)
2.1 gRPC 调用(同步)
无。前端不直接调 gRPC。
2.2 Kafka 事件订阅(异步)
无。前端不直接订阅 Kafka。
2.3 HTTP 调用(如有)
| 被调用方 | Method.Path | 用途 | mock 策略 |
|---|---|---|---|
| api-gateway (ai01) | POST /api/v1/student/graphql | 学生 GraphQL 查询(经网关代理到 student-bff :3009/graphql) | api-gateway/student-bff 就绪前使用 MSW 拦截返回 mock GraphQL 响应 |
| api-gateway (ai01) | POST /api/auth/login | 学生登录(Shell 独占,student-portal 不直接调用,仅跳转) | api-gateway 就绪前由 Shell 处理 |
| api-gateway (ai01) | POST /api/v1/student/upload | 作业附件上传(ARB-019 §21.5 方案 A) | 按方案 A 实现:REST + 对象存储 + GraphQL 提交 URL |
| push-gateway (ai02) | GET /ws(WebSocket) | WebSocket 实时通知 | push-gateway 就绪前使用 mock-socket 模拟 WS 推送 |
路径说明(ISSUE-014-01):
POST /api/v1/student/graphql经 api-gateway 反向代理到 student-bffPOST /graphql(:3009)- 路径前缀
/api/v1/student/*与 matrix.md §5、teacher-portal/api/v1/teacher/*保持命名一致性- 由 ai01(api-gateway)确认路由配置:
/api/v1/student/*→student-bff:3009/*
2.4 GraphQL 查询域(经 api-gateway 代理到 student-bff)
对齐 02-architecture-design.md v2 §4.2 GraphQL 操作清单(共 24 个)
2.4.1 Query(读,16 个)
| Query | 用途 | mock 策略 |
|---|---|---|
| currentUser | 当前学生信息 | MSW 返回固定学生(id=student-001, roles=[student]) |
| myClasses | 我的班级 | MSW 返回固定 1 个班级 |
| myExams | 我的考试列表 | MSW 返回固定 2 个考试 |
| examDetail(id: ID!) | 考试详情(含题目) | MSW 返回固定考试 + 5 道题 |
| myHomework | 我的作业列表 | MSW 返回固定 3 个作业(1 个待提交) |
| homeworkDetail(id: ID!) | 作业详情 | MSW 返回固定作业 + 题目 |
| myGrades | 我的成绩 | MSW 返回固定 5 个成绩 |
| myAttendance | 我的考勤 | MSW 返回固定 10 条考勤 |
| textbooks | 教材列表 | MSW 返回固定 5 个教材 |
| chapters(textbookId: ID!) | 章节列表 | MSW 返回固定章节树 |
| learningPath | 学习路径 | MSW 返回固定 8 个知识点推荐顺序 |
| studentDashboard | 学生仪表盘 | MSW 返回固定仪表盘(avg_score=85.0, class_rank=5) |
| myWeakness | 我的薄弱点 | MSW 返回固定 3 个 weak_points |
| myTrend | 学习趋势 | MSW 返回固定趋势数据 |
| myNotifications(first: Int, after: String) | 通知列表 | MSW 返回固定 10 条通知 |
| serverTime | 服务器时间(考试倒计时对齐) | MSW 返回当前时间 + 100ms 延迟 |
2.4.2 Mutation(写,8 个)
| Mutation | 用途 | mock 策略 |
|---|---|---|
| submitHomework(input: SubmitHomeworkInput!) | 提交作业 | MSW 返回 success=true |
| submitExam(input: SubmitExamInput!) | 提交考试作答 | MSW 返回 success=true + submittedAt |
| saveExamDraft(input: SaveExamDraftInput!) | 保存考试草稿 | MSW 返回 success=true |
| markAsRead(notificationId: ID!) | 标记通知已读 | MSW 返回 success=true |
| markAllAsRead | 全部标记已读 | MSW 返回 success=true |
| recordExamViolation(input: RecordExamViolationInput!) | 记录防作弊违规(ARB-019 §21.4) | MSW 返回 success=true |
| recordPasteEvent(input: RecordPasteEventInput!) | 记录粘贴事件(ARB-019 §21.4) | MSW 返回 success=true |
| updateNotificationPreference(input: UpdateNotificationPreferenceInput!) | 更新通知偏好(P5) | MSW 返回 success=true |
DataScope L0 强制执行(ISSUE-014-07):
- 所有学生端 Query 不传
studentId参数,由 student-bff 在 Resolver 层从 JWTx-user-id提取并强制过滤- 前端无法绕过 L0 边界(前端篡改 JWT 无效,gRPC 层会重新校验)
§3 就绪信号
3.1 我依赖的上游就绪标志
| 上游 | 就绪标志 | 阻塞阶段 | 状态 |
|---|---|---|---|
| packages 骨架(ai13 批次 0.15) | ui-tokens / ui-components / hooks 可 import | P2 启动 | ✅ 已就绪 |
| teacher-portal MF Shell(ai13 P2) | exposes AppShell/GraphQLProvider/useGraphQLClient/useAuth/usePermission + shared singleton | P2 启动 | ⏳ 待 ai13 P2 |
| api-gateway HTTP :8080(ai01 P3) | /api/v1/student/* 反向代理 student-bff 可用 |
P3 启动 | ⏳ 待 ai01 P3 |
| student-bff GraphQL(ai04 P3) | POST /graphql :3009 + 核心 Query/Mutation 可执行 |
P3 启动 | ⏳ 待 ai04 P3 |
| student-bff GraphQL schema | packages/shared-ts/contracts/graphql/student-bff.graphql 创建(ARB-019 §21 已裁决) |
P3 启动 | ✅ 已裁决 |
| core-edu gRPC 50053(ai08 P3) | ExamService/HomeworkService/GradeService/AttendanceService/ClassService | P3 启动 | ⏳ 待 ai08 P3 |
| iam gRPC 50052(ai06 P2) | GetUserInfo + GetEffectivePermissions + GetViewports | P3 启动 | ⏳ 待 ai06 P2 |
| content gRPC 50054(ai09 P4) | TextbookService + ChapterService + KnowledgeGraphService | P4 启动 | ⏳ 待 ai09 P4 |
| data-ana gRPC 50055(ai11 P4) | AnalyticsService.GetStudentWeakness + GetLearningTrend | P4 启动 | ⏳ 待 ai11 P4 |
| push-gateway WebSocket :8081/ws(ai02 P5) | WS 连接可建立 + 推送可接收 | P5 启动 | ⏳ 待 ai02 P5 |
| msg gRPC 50056(ai10 P5) | NotificationService.ListNotifications + MarkAsRead | P5 启动 | ⏳ 待 ai10 P5 |
| ai 服务 gRPC 50058(ai12 P5,可选) | AiService.Chat(SSE 流式) | P5 启动 | ⏳ 待 ai12 P5 |
3.2 我的就绪标志(供下游消费)
- student-portal dev server :4001 启用(
pnpm dev可访问) - MF Remote 可被 AppShell 加载(暴露
./StudentApp模块,teacher-portal Shell 可加载) - 独立壳渲染(
NEXT_PUBLIC_MF_ENABLED=false时首页 + 导航 + 路由守卫独立可用) - 登录流程可用(未登录跳转 Shell
/login,登录后回跳 student) - GraphQL 查询可执行(currentUser / studentDashboard / myClasses 返回数据)
- 考试作答链路通(进入作答 → 自动保存 → 提交 → 跳转结果页)
- WebSocket 通知可接收(通知中心实时更新)
- lint + typecheck 零错误
§4 Mock 策略
4.1 我提供的 mock
student-portal 是前端,无下游消费方。但对开发体验提供:
- Storybook:各组件独立 story(
apps/student-portal/.storybook/) - MSW handlers:
apps/student-portal/src/mocks/handlers.ts,拦截所有 GraphQL/HTTP 请求 - Mock fixtures:
apps/student-portal/src/mocks/fixtures/*.json,与 student-bff mock 数据一致
4.2 我消费的 mock
在真实上游就绪前,student-portal 使用以下 mock:
4.2.1 HTTP / GraphQL mock(MSW)
POST /api/v1/student/graphql→ 按 operationName 返回对应 mock 响应(见 §2.4)POST /api/auth/login→ 返回固定 JWT + UserInfo(student 角色)由 Shell 处理POST /api/v1/student/upload→ 返回固定 signed URL(ARB-019 §21.5 方案 A 已裁决)- 所有 mock 响应定义在
apps/student-portal/src/mocks/fixtures/*.json
4.2.2 WebSocket mock(mock-socket)
- 连接
ws://localhost:8081/ws后每 30 秒推送 1 条 mock 通知 - 通知类型轮询:作业通知 / 考试通知 / 成绩通知 / 系统通知
- 支持模拟考试延长事件 ExamExtended + 考试强制提交 ExamForceSubmitted(ARB-019 §21.6 已裁决)
4.2.3 JWT mock
- 使用固定 mock JWT(
eyJhbGciOiJSUzI1NiIs...),payload 含sub=student-001, roles=[student], dataScope=SELF - 存入 httpOnly cookie(由 Shell 登录流程设置)
4.2.4 环境切换
- 通过
NEXT_PUBLIC_API_MOCKING=enabled环境变量控制 - 上游就绪后设为
disabled,切换到真实请求 - MF 切换:
NEXT_PUBLIC_MF_ENABLED=false(P2 独立壳)→true(P3 接入 Shell)
4.2.5 IDB 草稿恢复(真实 idb-keyval)
- 考试作答草稿使用真实
idb-keyval存储(前端可独立测试断网恢复逻辑) - 不需要 mock,IDB 在浏览器原生支持
§5 与 student-bff 契约对齐核查
参考 student-bff_contract.md(ai04 维护)
| 对齐项 | student-portal 期望 | student-bff 提供 | 状态 |
|---|---|---|---|
| GraphQL endpoint | POST /api/v1/student/graphql(经 api-gateway 代理) |
POST /graphql :3009 |
✅ 对齐(api-gateway 代理) |
| GraphQL schema 文件 | packages/shared-ts/contracts/graphql/student-bff.graphql |
apps/student-bff/src/schema/*.graphql(ARB-019 §21 已裁决,统一为集中管理) |
✅ 已裁决 |
| Query 域(16 个) | 见 §2.4.1 | 见 student-bff §1.3(auth/myClasses/myExams/myHomework/myGrades/myAttendance/content/dashboard/weakness/trend/notifications) | ⏳ 待 ai04 确认 serverTime/examDetail/homeworkDetail |
| Mutation 域(8 个) | 见 §2.4.2 | student-bff §1.3 仅列 submitHomework + markAsRead | ⏳ 待 ai04 补全 submitExam/saveExamDraft/recordExamViolation/recordPasteEvent/updateNotificationPreference |
| 错误码前缀 | 透传 BFF_STUDENT_* |
BFF_STUDENT_(student-bff §1.5) |
✅ 对齐 |
| ActionState 信封 | success/errors/data + extensions.degraded | 待 ai04 实现(ARB-001 §1.3 原则) | ⏳ 待 ai04 |
| DataLoader 防 N+1 | 依赖 student-bff 实现 | 待 ai04 实现(ARB-001 §1.3 原则) | ⏳ 待 ai04 |
| DataScope L0 强制执行 | student-bff Resolver 层从 JWT 提取 studentId | 已裁决 ARB-019 §21.7(方案 A:Resolver 从 JWT 提取) | ✅ 已裁决 |
§6 异议引用
| 编号 | 标题 | 影响 |
|---|---|---|
| ISSUE-014-01 | GraphQL endpoint 路径不一致 | 影响 §2.3 路径配置 |
| ISSUE-014-02 | student-bff GraphQL schema 存放位置不一致 | 影响 §1.3 schema 文件路径 |
| ISSUE-014-03 | 考试作答页全屏策略与防作弊检测边界 | 影响 §2.4.2 recordExamViolation mutation 实现 |
| ISSUE-014-04 | 主观题粘贴策略(防作弊 vs 学生体验) | 影响 §2.4.2 recordPasteEvent mutation 实现 |
| ISSUE-014-05 | 作业附件上传协议(GraphQL mutation vs REST multipart) | 影响 §2.3 /api/v1/student/upload 端点 |
| ISSUE-014-06 | 考试延长/题目重排等实时事件命名未确认 | 影响 §1.7 WebSocket 事件类型 |
| ISSUE-014-07 | 学生端 DataScope L0 边界的强制执行层 | 影响 §2.4 GraphQL 查询域参数设计 |
AI Agent: ai14(student-portal) Branch: feat-review-student-portal-docs-9yN6Av Coordinator: coord-ai