Merge worktree branch merge-15-modules-to-main-5ug5xJ

This commit is contained in:
SpecialX
2026-07-10 15:28:20 +08:00
parent 60d7173545
commit df62ffc176
51 changed files with 11559 additions and 1908 deletions

View File

@@ -1,7 +1,9 @@
# student-portal 对接契约
> 负责人ai14
> 关联:[matrix.md](./matrix.md)
> 关联:[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)
> 版本v2ai14 接管审计与补全版2026-07-10
> 修订摘要GraphQL endpoint 路径修正为 `/api/v1/student/graphql`(对齐 matrix.md §5+ 补全 24 个 GraphQL query/mutation对齐 02 §4.2+ 补充 ARB-002 MF Shell 暴露清单 + 补充就绪信号明细
---
@@ -18,32 +20,54 @@
| 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 错误码 `BFF_STUDENT_*`,见 [matrix.md §6](../matrix.md))。
### 1.6 微前端架构(补充
### 1.6 微前端架构(MF RemoteARB-002 对齐
| 角色 | 说明 |
| ---------------------- | ----------------------------------------------------------------- |
| MF Remote | 学生门户是微前端远程模块,由 teacher-portal AppShell 或独立壳加载 |
| 暴露的 remote 模块 | StudentApp学生端完整应用、shared 学生端组件 |
| module federation 配置 | `apps/student-portal/module-federation.config.ts` |
| 角色 | 说明 |
| ---------------------- | --------------------------------------------------------------------------------------------------------- |
| MF Remote | student-portal 是微前端远程模块P3 首个 RemoteARB-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 / RequirePermissionARB-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-tsARB-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 仲裁) |
---
@@ -59,27 +83,59 @@
### 2.3 HTTP 调用(如有)
| 被调用方 | Method.Path | 用途 | mock 策略 |
| ------------------- | ------------------------- | --------------------------------------------- | ----------------------------------------------------------------- |
| api-gateway (ai01) | POST /api/student/graphql | 学生 GraphQL 查询(经网关代理到 student-bff | api-gateway/student-bff 就绪前使用 MSW 拦截返回 mock GraphQL 响应 |
| api-gateway (ai01) | POST /api/auth/login | 学生登录 | api-gateway 就绪前使用 MSW 返回固定 JWT |
| push-gateway (ai02) | GET /ws | WebSocket 实时通知 | push-gateway 就绪前使用 mock-socket 模拟 WS 推送 |
| 被调用方 | 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 /wsWebSocket | 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/*` 保持命名一致性
> - 由 ai01api-gateway确认路由配置`/api/v1/student/*` → `student-bff:3009/*`
### 2.4 GraphQL 查询域(经 api-gateway 代理到 student-bff
| Query/Mutation | 用途 | mock 策略 |
| ----------------------------------- | --------------- | -------------------------------------------------- |
| currentUser | 当前学生信息 | MSW 返回固定学生 |
| myClasses | 我的班级 | MSW 返回固定 1 个班级 |
| myExams | 我的考试 | MSW 返回固定 2 个考试 |
| myHomework / submitHomework | 我的作业 + 提交 | MSW 返回固定作业 + submitHomework success |
| myGrades | 我的成绩 | MSW 返回固定 5 个成绩 |
| myAttendance | 我的考勤 | MSW 返回固定 10 条考勤 |
| textbooks / chapters / learningPath | 学习内容 | MSW 返回固定内容 + 学习路径 |
| studentDashboard | 学生仪表盘 | MSW 返回固定仪表盘avg_score=85.0, class_rank=5 |
| myWeakness | 我的薄弱点 | MSW 返回固定 3 个 weak_points |
| myTrend | 学习趋势 | MSW 返回固定趋势数据 |
| myNotifications / markAsRead | 通知中心 | MSW 返回固定通知 |
> 对齐 [02-architecture-design.md v2 §4.2](../../../apps/student-portal/docs/02-architecture-design.md) GraphQL 操作清单(共 24 个)
#### 2.4.1 Query16 个)
| 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 Mutation8 个)
| 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 层会重新校验)
---
@@ -87,18 +143,31 @@
### 3.1 我依赖的上游就绪标志
- [ ] api-gateway HTTP :8080 启用ai01—— 前端请求入口
- [ ] student-bff GraphQL :3009 启用ai04—— 数据来源
- [ ] push-gateway WebSocket :8081/ws 启用ai02—— 实时通知
| 上游 | 就绪标志 | 阻塞阶段 | 状态 |
| --------------------------------- | ------------------------------------------------------------------------- | -------- | ------------ |
| packages 骨架ai13 批次 0.15 | ui-tokens / ui-components / hooks 可 import | P2 启动 | ✅ 已就绪 |
| teacher-portal MF Shellai13 P2| exposes AppShell/GraphQLProvider/useGraphQLClient/useAuth/usePermission + shared singleton | P2 启动 | ⏳ 待 ai13 P2 |
| api-gateway HTTP :8080ai01 P3 | `/api/v1/student/*` 反向代理 student-bff 可用 | P3 启动 | ⏳ 待 ai01 P3 |
| student-bff GraphQLai04 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 50053ai08 P3 | ExamService/HomeworkService/GradeService/AttendanceService/ClassService | P3 启动 | ⏳ 待 ai08 P3 |
| iam gRPC 50052ai06 P2 | GetUserInfo + GetEffectivePermissions + GetViewports | P3 启动 | ⏳ 待 ai06 P2 |
| content gRPC 50054ai09 P4 | TextbookService + ChapterService + KnowledgeGraphService | P4 启动 | ⏳ 待 ai09 P4 |
| data-ana gRPC 50055ai11 P4 | AnalyticsService.GetStudentWeakness + GetLearningTrend | P4 启动 | ⏳ 待 ai11 P4 |
| push-gateway WebSocket :8081/wsai02 P5 | WS 连接可建立 + 推送可接收 | P5 启动 | ⏳ 待 ai02 P5 |
| msg gRPC 50056ai10 P5 | NotificationService.ListNotifications + MarkAsRead | P5 启动 | ⏳ 待 ai10 P5 |
| ai 服务 gRPC 50057ai12 P5可选 | AiService.ChatSSE 流式) | P5 启动 | ⏳ 待 ai12 P5 |
### 3.2 我的就绪标志(供下游消费)
- [ ] student-portal dev server :4001 启用
- [ ] MF Remote 可被 AppShell 加载(暴露 StudentApp 模块)
- [ ] 独立壳渲染(首页 + 导航 + 路由守卫)
- [ ] 登录流程可用(POST /api/auth/login 获取 JWT 存入 cookie
- [ ] GraphQL 查询可执行currentUser / myClasses / studentDashboard 返回数据)
- [ ] WebSocket 通知可接收
- [ ] 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 零错误
---
@@ -108,18 +177,78 @@
student-portal 是前端,无下游消费方。但对开发体验提供:
- **Storybook**:各组件独立 story
- **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
- **HTTP/GraphQL mock**:使用 MSW 拦截所有请求
- POST /api/auth/login → 返回固定 JWT + UserInfostudent 角色)
- POST /api/student/graphql → 根据 operationName 返回对应 mock 响应(与 student-bff mock 数据一致
- 所有 mock 响应定义在 `apps/student-portal/src/mocks/fixtures/*.json`
- **WebSocket mock**:使用 mock-socket 库
- 连接后每 30 秒推送 1 条 mock 通知
- **JWT mock**:使用固定 mock JWT存入 httpOnly cookie
- **环境切换**:通过 `NEXT_PUBLIC_API_MOCKING=enabled` 环境变量控制,上游就绪后设为 `disabled`
#### 4.2.1 HTTP / GraphQL mockMSW
- `POST /api/v1/student/graphql` operationName 返回对应 mock 响应(见 §2.4
- `POST /api/auth/login` → 返回固定 JWT + UserInfostudent 角色)由 Shell 处理
- `POST /api/v1/student/upload` → 返回固定 signed URL待 ISSUE-014-05 仲裁后实现)
- 所有 mock 响应定义在 `apps/student-portal/src/mocks/fixtures/*.json`
#### 4.2.2 WebSocket mockmock-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` 存储(前端可独立测试断网恢复逻辑)
- 不需要 mockIDB 在浏览器原生支持
---
## §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.3auth/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**: ai14student-portal
**Branch**: feat-review-student-portal-docs-9yN6Av
**Coordinator**: coord-ai