add verification of ARB-001 and ARB-002 impacts, and submit seven new disputed issues for coord arbitration
255 lines
18 KiB
Markdown
255 lines
18 KiB
Markdown
# student-portal 对接契约
|
||
|
||
> 负责人:ai14
|
||
> 关联:[matrix.md](./matrix.md)、[coord.md §1 ARB-001](../coord.md)、[coord.md §2 ARB-002](../coord.md)、[student-bff_contract.md](./student-bff_contract.md)、[teacher-portal_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`(待 ISSUE-014-02 仲裁后由 ai04 创建,对齐 ARB-001 §1.3 集中管理原则)
|
||
|
||
### 1.4 Kafka 事件发布(如有)
|
||
|
||
无。
|
||
|
||
### 1.5 错误码前缀
|
||
|
||
无(前端不定义错误码前缀,透传 BFF 错误码 `BFF_STUDENT_*`,见 [matrix.md §6](../matrix.md))。
|
||
|
||
### 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 | 作业通知 / 考试通知 / 成绩通知 / 系统通知 / 考试延长(待 ISSUE-014-06 仲裁) / 考试强制提交(待 ISSUE-014-06 仲裁) |
|
||
|
||
---
|
||
|
||
## §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 | 作业附件上传(待 ISSUE-014-05 仲裁) | 待仲裁后实现 |
|
||
| push-gateway (ai02) | GET /ws(WebSocket) | WebSocket 实时通知 | push-gateway 就绪前使用 mock-socket 模拟 WS 推送 |
|
||
|
||
> **路径说明**(ISSUE-014-01):
|
||
> - `POST /api/v1/student/graphql` 经 api-gateway 反向代理到 student-bff `POST /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](../../../apps/student-portal/docs/02-architecture-design.md) 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!) | 记录防作弊违规(待 ISSUE-014-03 仲裁) | MSW 返回 success=true |
|
||
| recordPasteEvent(input: RecordPasteEventInput!) | 记录粘贴事件(待 ISSUE-014-04 仲裁) | MSW 返回 success=true |
|
||
| updateNotificationPreference(input: UpdateNotificationPreferenceInput!) | 更新通知偏好(P5) | MSW 返回 success=true |
|
||
|
||
> **DataScope L0 强制执行**(ISSUE-014-07):
|
||
> - 所有学生端 Query 不传 `studentId` 参数,由 student-bff 在 Resolver 层从 JWT `x-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` 创建(待 ISSUE-014-02 仲裁) | 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 50057(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(待 ISSUE-014-05 仲裁后实现)
|
||
- 所有 mock 响应定义在 `apps/student-portal/src/mocks/fixtures/*.json`
|
||
|
||
#### 4.2.2 WebSocket mock(mock-socket)
|
||
|
||
- 连接 `ws://localhost:8081/ws` 后每 30 秒推送 1 条 mock 通知
|
||
- 通知类型轮询:作业通知 / 考试通知 / 成绩通知 / 系统通知
|
||
- 支持模拟考试延长事件(待 ISSUE-014-06 仲裁后实现)
|
||
|
||
#### 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](./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`(待 ISSUE-014-02 仲裁) | ⏳ 待仲裁 |
|
||
| 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 | 待 ISSUE-014-07 仲裁 | ⏳ 待仲裁 |
|
||
|
||
---
|
||
|
||
## §6 异议引用
|
||
|
||
> 详见 [objections/student-portal_issue.md](../objections/student-portal_issue.md)
|
||
|
||
| 编号 | 标题 | 影响 |
|
||
| ------------ | -------------------------------------------------------- | --------------------------------------------- |
|
||
| 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
|