Files
Edu/apps/student-portal/docs/01-understanding.md
SpecialX 24c2860b41 docs(student-portal): add arbitration check and new objections to issue record
add verification of ARB-001 and ARB-002 impacts, and submit seven new disputed issues for coord arbitration
2026-07-10 15:10:16 +08:00

64 KiB
Raw Blame History

模块理解确认书 — student-portal

AIai14TS/React · 学习场景域前端 remote 阶段:阶段 1 交付物v2 — ai14 接管审计与补全版) 初版日期2026-07-09ai07 起草) 审计日期2026-07-10ai14 修订:端口、所有权、协议、路由、错误码、长远架构遗漏补全) 关联:004 架构影响地图 §1.1a/1.1b/§5.4、AI 分配方案 §3.2 ai14、pending-features P3teacher-portal 阶段 1teacher-portal 阶段 2teacher-portal 阶段 3 长远架构known-issues §2.15coord 仲裁 ARB-001coord 仲裁 ARB-002student-bff 契约

审计修订摘要ai14 → ai07 初稿):

  1. 端口修订3001 → 4001004 §1.2 强制 4 端 4000-4003port-allocation §4 硬约束3001 已被 classes 历史占用)
  2. 所有权修订ai07 → ai14ai-allocation.md §3.2 L94ai07=classes/core-edu 交接,非 student-portal
  3. 协议修订REST → GraphQLARB-001 已裁决 student-bff 走 GraphQL Yoga + ActionState 信封 + DataLoader前端 all-in GraphQL不再消费 REST
  4. 路由修订/student/dashboard/dashboardstudent-portal 是独立 dev server :4001路由前缀不带 /student,与 student-portal_contract.md §1.2 对齐)
  5. 登录路径修订/iam/loginPOST /api/auth/login(与 student-portal_contract.md §2.3 对齐iam 仅承担 gRPC登录由 api-gateway 聚合)
  6. 错误码前缀修订EXAMS_/HOMEWORK_/GRADES_CORE_EDU_known-issues §2.15 已确认 core-edu 子域统一前缀);STUDENT_BFF_BFF_STUDENT_coord §5.2 裁决)
  7. MF 配置修订:按 ARB-002Shell 暴露 GraphQLProvider/useGraphQLClient/urql/graphql 单例student-portal 不再实现自己的 ApiClient
  8. 遗漏补全考试作答边界场景、学情诊断与个性化推荐、学生隐私合规COPPA/FERPA/PIPL/未成年人保护法、测试策略分层、韧性模式、性能预算、CSP/前端安全、跨标签同步、API 版本演进、未来扩展铺垫、长远愿景
  9. 新增章节§11 考试作答边界场景、§12 学生隐私与合规、§13 测试策略、§14 性能与预算、§15 前端安全、§16 跨标签与跨设备同步、§17 i18n 深化、§18 移动端与 PWA、§19 长远愿景与演进路径

1. 我在架构中的位置

  • 层级L2 微前端层004 §3.1 六层架构中的前端层)
  • MF 角色Remote 子应用,挂载到 teacher-portal ShellARB-002 裁决P3 首个 Remote
  • 上游(谁调用我):浏览器(学生)— 含桌面 Chrome/Edge/Safari、移动端 iOS Safari/Android Chrome、考试机 lockdown 浏览器P6+ 评估)
  • 下游(同步)api-gatewayGraphQL over HTTP,经 Next.js rewrites 代理 /api/v1/*/api/auth/*
  • 下游推送P5push-gatewayWebSocket含 HTTP 长轮询降级)
  • BFF 对接student-bffai04 设计,端口 3009GraphQL Yoga endpoint POST /graphql
  • 通信方式GraphQL over HTTP前端→Gateway→student-bff+ WebSocket前端→push-gatewayP5+ HTTP 长轮询降级P5+
  • 不直连:前端不直连任何业务服务或 BFF 后端实例,全部经 api-gateway 代理

说明

  • 通过 next.config.jsrewrites/api/v1/*/api/auth/* 代理到 api-gateway
  • MF 架构下student-portal 作为 Remote 暴露页面入口,由 teacher-portal Shell 的 AppShell 动态加载ARB-002
  • 复用 Shell 暴露的共享依赖react/react-dom/urql/graphql/@tanstack/react-query/zustand/nuqs/@edu/ui-components/@edu/ui-tokens/@edu/contracts/@edu/hooks/@edu/shared-ts
  • 复用 Shell 暴露的 GraphQLProviderurql client 单例student-portal 不重复创建 GraphQL client
  • 不独立提供 RootLayout / 字体加载 / 设计令牌 / i18n Provider / 登录页,全部由 Shell 提供ARB-002登录页 P2 不走 MF

2. 我的限界上下文

2.1 我负责的聚合 / 实体(前端视图模型)

  • 考试作答ExamTaking、考试草稿ExamTakingDraft、作业提交HomeworkSubmit
  • 学情诊断Diagnostic、错题本Weakness、学习路径LearningPath
  • 会话状态Session、视口Viewport、权限Permission— 与 Shell 共享,引用 teacher-portal 文档

2.2 业务领域

  • D3 教学核心领域(前端场景域:学习场景域,学生视角)

2.3 不负责

  • 教师批改界面(归 teacher-portal
  • AI 出题(归 teacher-portal
  • 班级/考试/作业 CRUD 管理(归 teacher-portal
  • 用户/角色/权限 CRUD归 admin-portal
  • 家长多子女切换(归 parent-portal
  • 教师沟通(归 teacher-portal P7+
  • 成绩录入(归 teacher-portal学生仅查看

2.4 数据范围

  • DataScope L0仅本人:学生只能看到自己的作业、考试、成绩、学情、错题、考勤
  • 例外:班级排名、班级均分等聚合数据由 student-bff 聚合返回,学生不可见其他同学个体数据

3. 我与外部的契约

3.1 消费的后端 API经 api-gateway 代理)

路径前缀 下游 BFF/服务 关键端点
POST /api/auth/login api-gateway 学生登录api-gateway 聚合 iam gRPC返回 JWT + UserInfo
POST /api/v1/student/graphql student-bff GraphQL 端点dashboard / currentUser / myClasses / myExams / myHomework / submitHomework / myGrades / myAttendance / textbooks / chapters / learningPath / studentDashboard / myWeakness / myTrend / myNotifications / markAsRead
GET /api/v1/notifications/* msg + push-gateway 通知中心P5HTTP 长轮询降级)

协议说明ARB-001 已裁决student-bff 是 GraphQL 聚合层,前端 all-in GraphQL不再消费 REST。除登录(POST /api/auth/login和通知中心降级P5+)外,全部业务请求经 GraphQL endpoint。student-bff 由 ai04 设计,聚合 iam + core-edu + content + data-ana + msg为学生场景提供聚合视图。

3.1.1 API 契约版本演进策略

版本信号 携带位置 演进规则
主版本 URL 路径 /api/v1/*/api/v2/* 破坏性变更升主版本student-portal 同时支持 v1 + v2 至少 1 个迭代周期4 周),通过 Feature Flag 切换
GraphQL schema 内 @deprecated + schema registry 字段废弃用 @deprecated 标注student-portal 监控使用率,< 1% 后移除调用
子版本 响应头 X-API-Version: 2026-07-10 向后兼容字段新增前端忽略未知字段Zod 默认行为)
Deprecation 响应头 Deprecation: true + Sunset: <date> 前端收到 Deprecation 头后上报埋点,跟踪使用率

student-portal 不主动驱动 API 版本升级;契约变更由 coord 协调各业务 AI 落地。student-portal 仅负责消费侧的兼容与迁移。

3.2 统一响应契约

所有 GraphQL 响应遵循 ActionState 信封ARB-001 §1.3 + 总裁裁决 §3.4 方案 B 降级模式):

// GraphQL 响应信封(与 teacher-bff 一致)
type GraphQLResponse<T> = {
  data: T | null;
  errors: Array<{
    code: string; // 错误码前缀如 IAM_/CORE_EDU_/BFF_STUDENT_/GW_
    message: string;
    path: Array<string | number>;
    extensions?: {
      degraded?: boolean; // 降级模式标记
      partial?: boolean; // 部分聚合失败
      traceId?: string;
    };
  }> | null;
};

// 业务错误码(前端 i18n 路由)
type ActionError = {
  code: string; // 如 BFF_STUDENT_UPSTREAM_UNAVAILABLE
  message: string;
  details?: unknown;
};

错误码前缀按服务名大写(如 IAM_CORE_EDU_BFF_STUDENT_GW_NETWORK_)。前端 GraphQL 请求层根据 extensions.code 前缀路由到对应的 i18n key。

3.3 推送契约P5

协议 场景 降级
WebSocketpush-gateway 考试发布通知、成绩发布、作业截止提醒 HTTP 长轮询60s 拉取通知列表)
HTTP 长轮询P5+ WebSocket 重连 5 次失败后降级 通知延迟最多 60sUI 顶部提示降级条

3.4 proto 不直接消费

前端不调用 gRPCstudent-bff 把 gRPC 聚合为 GraphQL 暴露给前端。前端仅消费 packages/contracts/src/permissions.ts 中的权限点常量TS 文件,非 proto 生成)。

4. 我的技术栈

维度 选型 说明
框架 Next.js 14+App Router server components 默认client components 按需MF Remote 优先 CSR考试作答页禁用 SSR 防缓存)
语言 TypeScript 5.5+strict 沿用 tsconfig.base.json
微前端 Module Federation 2.0@module-federation/nextjs-mf student-portal = RemoteARB-002 P3 首个 Remote
数据请求 urql GraphQL clientShell 暴露单例ARB-002 all-in GraphQL不再用 REST ApiClient
样式 Tailwind CSS 3.4+ 配合设计令牌三层模型(复用 Shell 提供的令牌)
UI 组件库 shadcn/ui迁移指南 §7.2 复用 Shell 暴露的 packages/ui-components/
状态管理 L1 URL nuqs 可分享、可刷新状态(考试作答页 URL 不携带答案,防泄露)
状态管理 L2 Server TanStack Query v5 GraphQL query 缓存、重试、乐观更新urql 与 TanStack Query 协同urql 作为 fetcher
状态管理 L3 Client Business Zustand slice 客户端业务状态(考试作答草稿、倒计时、断网队列)
状态管理 L4 Global UI Zustand ui-store + ModalRoot 全局 UI 状态(复用 Shell
状态管理 L5 Form react-hook-form + zodResolver 表单状态(作业提交表单、附件上传)
富文本 不使用(学生不作答富文本,作业提交用表单) 与 teacher-portal 差异点;客观题用单选/多选/填空,主观题用 textarea
图表 recharts 学情诊断、Dashboard、错题本掌握度
i18n next-intl 复用 Shell Provider按 scope=student 加载翻译
A11y eslint-plugin-jsx-a11yerror 级) WCAG 2.2 AA考试作答页额外要求键盘可操作 + 屏幕阅读器友好
字体 Intersans/ Frauncesserif/ JetBrains Monomono 复用 Shell 的 next/font/google 加载
考试作答专用 倒计时(基于服务器时间)+ 自动保存HTTP POST 每 30s + blur+ 断网恢复localStorage 草稿)

5. 我的阶段归属

  • 阶段P3
  • 当前状态📐 待设计(待 core-edu + student-bff 就绪apps/student-portal/ 目录为空(仅 docs/),依赖上游阶段 P3
  • 依赖上游阶段P1api-gateway + iam+ P2teacher-portal Shell 就绪 + ARB-002 MF 配置完成)+ P3core-edu + student-bff

6. 我需要对齐的黄金模板项(对照 classes 服务)

前端无 @RequirePermission 装饰器(后端概念),对齐项改造为前端等价物。

对齐项 classes后端黄金模板 student-portal 前端等价 当前状态
权限校验 @RequirePermission(Permissions.XXX) usePermission().hasPermission("XXX") Hook + <RequirePermission> 组件 待建(复用 Shell 暴露的 hooks
错误码前缀统一 CLASSES_*IAM_* GraphQL 请求层根据 extensions.code 前缀路由 i18n 待建(复用 Shell urql client
logger pino 前端 console + SentryP6 待建(复用 shared-ts Logger
metrics prom-client /metrics 前端 Web Vitals → Gateway 上报 待建
tracer OTel SDK 前端 OTel browser SDKP6 待建
/healthz + /readyz GET /healthz GET /readyz Next.js /api/health route + Dockerfile HEALTHCHECK 待建
优雅关闭 SIGTERM handler Next.js 无长连接(除 WS无需 N/A
测试覆盖率 ≥ 80% Vitest Vitest + @testing-library/react + Playwright E2E 待建
Dockerfile 多阶段构建 builder + runtime builder + runtime 待建
Zod 输入验证 class-validator + Zod schema react-hook-form + zodResolver 待建
GlobalErrorFilter NestJS 全局异常过滤器 React ErrorBoundary + GraphQL 请求层统一错误处理 待建(复用 Shell ErrorBoundary
设计令牌三层 复用 Shell 提供的 primitive/semantic-light/dark/tailwind-theme 待建(依赖 ui-tokens 包)
A11y 工具集 useA11yId / mergeA11yProps / describeInput / focus-trap 待建(复用 Shell 暴露的 hooks
GraphQL client 单例 复用 Shell 暴露的 urql clientARB-002 待建

student-portal 现状审计(对齐黄金模板)

审计表

维度 状态 说明
权限装饰器(前端等价 usePermission 待建,复用 Shell 暴露的 usePermission Hook
错误码前缀 待建,复用 Shell 暴露的 urql client + 错误扩展
logger 待建,复用 shared-ts Logger
metrics 待建Web Vitals 上报
tracer 待建OTel browser SDK
/healthz 待建Next.js /api/health route
/readyz 待建Next.js /api/ready route
优雅关闭 N/A Next.js 无长连接
测试覆盖率 0%,无测试文件(待建,目标 ≥ 80%
Dockerfile 多阶段 待建builder + runtime
Zod 输入验证 待建react-hook-form + zodResolver
GlobalErrorFilterErrorBoundary 待建,复用 Shell ErrorBoundary
设计令牌三层 待建,复用 ui-tokens 包
A11y 工具集 待建,复用 hooks 包
Module Federation 配置 待建Remote 角色ARB-002
5 层状态管理 待建,复用 Shell Provider
共享组件库 待建,复用 Shell 暴露的 ui-components
i18n 待建,复用 Shell Provider + scope=student 翻译
GraphQL 请求层 待建,复用 Shell 暴露的 urql client 单例ARB-002
ESLint flat config 自定义规则 待建,与 Shell 共用配置
考试作答自动保存 待建HTTP POST 每 30s + blur + localStorage 草稿
考试倒计时(服务器时间对齐) 待建,基于服务器 expiresAt
断网恢复队列 待建IDB 队列 + 重连重试

现有文件清单

apps/student-portal/
└─ docs/
   ├─ 01-understanding.md  # 本文件v2
   └─ 02-architecture-design.md  # v2

student-portal 当前为空目录(仅 docs/),所有维度均为 待建状态。无现有文件、无现有代码、无现有违规点。P3 阶段从零开始搭建。

主要待建项(必须在 P3 起步时建立)

  1. MF Remote 配置next.config.js 配置 name: 'student_app'exposesremotes: { teacher: ... }ARB-002 MF URL 用 4000 端口)
  2. 路由表(对齐 student-portal_contract.md §1.2/dashboard/my-homework/my-homework/:id/submit/my-exams/my-exams/:id/take/my-exams/:id/result/my-grades/my-attendance/learning-path/diagnostic/weakness/notifications/my-schedule
  3. ExamTaking 组件:考试作答(倒计时基于服务器时间 + 自动保存每 30s + blur + 断网恢复)
  4. 作业提交表单react-hook-form + zodResolver不用 Tiptap
  5. Dockerfile 多阶段构建builder + runtime + HEALTHCHECK
  6. Vitest + Playwright 测试:覆盖率 ≥ 80%(含考试作答边界场景 E2E
  7. /api/health + /api/ready route:健康检查端点
  8. urql GraphQL 查询:复用 Shell 单例 client按 GraphQL 查询域封装 hooks

7. L1 导航菜单(视口)

视口 key 文案 路由 权限 阶段
dashboard Dashboard /dashboard STUDENT_DASHBOARD_VIEW P3
my-homework 我的作业 /my-homework HOMEWORK_READ_OWN P3
my-exams 我的考试 /my-exams EXAMS_READ_OWN P3
my-grades 我的成绩 /my-grades GRADES_READ_OWN P3
my-attendance 我的考勤 /my-attendance ATTENDANCE_READ_OWN P3
learning-path 学习路径 /learning-path LEARNING_PATH_VIEW P4
diagnostic 学情诊断 /diagnostic DIAGNOSTIC_READ_OWN P4
weakness 错题本 /weakness WEAKNESS_READ_OWN P4
notifications 通知中心 /notifications NOTIFICATION_READ_OWN P5
my-schedule 我的课表 /my-schedule SCHEDULE_READ_OWN P5+

来源student-bff GraphQL viewports query聚合 iam 视口配置AppShell 按 scope=student 过滤渲染。

8. L2 路由表

路由 页面 权限 阶段
/dashboard 学生仪表盘 STUDENT_DASHBOARD_VIEW P3
/my-homework 我的作业 HOMEWORK_READ_OWN P3
/my-homework/:id/submit 提交作业 HOMEWORK_SUBMIT P3
/my-exams 我的考试 EXAMS_READ_OWN P3
/my-exams/:id/take 作答考试 EXAMS_TAKE P3
/my-exams/:id/result 考试结果 EXAMS_RESULT_VIEW P3
/my-grades 我的成绩 GRADES_READ_OWN P3
/my-attendance 我的考勤 ATTENDANCE_READ_OWN P3
/learning-path 学习路径 LEARNING_PATH_VIEW P4
/diagnostic 学情诊断 DIAGNOSTIC_READ_OWN P4
/weakness 错题本 WEAKNESS_READ_OWN P4
/notifications 通知中心 NOTIFICATION_READ_OWN P5
/my-schedule 我的课表 SCHEDULE_READ_OWN P5+

路由前缀不带 /student/student-portal 是独立 dev server :4001挂载到 Shell 后由 Shell 路由表统一前缀。L3 组件级视口用 <RequirePermission perm="HOMEWORK_SUBMIT"><Button>提交作业</Button></RequirePermission>。权限点后缀 _OWN 强调学生仅能操作自己的数据DataScope L0

9. L3 组件级差异student-portal 特有)

9.1 复用 Shell 暴露的组件

  • AppShell(左栏导航 + 主内容区)
  • RequirePermissionL3 组件级视口控制)
  • ErrorBoundaryReact 渲染异常兜底)
  • Loading(骨架屏)
  • Empty(空态)
  • DataTable(表格)
  • Formreact-hook-form + zodResolver 封装)
  • Chartrecharts 封装)
  • GraphQLProviderurql client 单例ARB-002

9.2 student-portal 特有组件

组件 用途 来源 是否 MF 暴露
ExamTaking 考试作答(倒计时 + 自动保存 + 断网恢复) 新建 暴露 ./ExamTaking(供 Shell 路由复用)
HomeworkSubmit 作业提交表单react-hook-form + zodResolver 新建 内部使用
DiagnosticChart 学情诊断图表recharts 多维雷达 + 趋势线) 新建 内部使用
WeaknessList 错题本列表(按知识点聚合 + 掌握度标签) 新建 内部使用
LearningPathMap 学习路径图(知识点前置依赖可视化) 新建 内部使用
ExamResultView 考试结果页(得分 + 错题分析 + 知识点掌握度) 新建 内部使用
CountdownTimer 倒计时组件(基于服务器时间,避免客户端篡改) 新建 内部使用
DraftRecovery 草稿恢复弹窗(断网恢复后提示是否恢复作答) 新建 内部使用

9.3 不使用的组件(与 teacher-portal 差异)

  • 不使用 RichTextEditorTiptap:学生不作答富文本,作业提交用表单
  • 不使用 SSEViewer:学生不参与 AI 出题
  • 不使用 ChildSwitcher:学生无多子女切换(家长端特有)
  • 不使用 UserManagementTable:学生不管理用户
  • 不使用 RolePermissionMatrix:学生不管理权限

10. L4 数据层差异

维度 teacher-portal student-portal
主要数据源 teacher-bff聚合多服务 student-bff聚合 iam + core-edu + content + data-ana + msg
协议 GraphQLurql GraphQLurql复用 Shell 单例)
缓存策略 5min 中等缓存 5-30s 短缓存,作业列表 30s考试作答页 0s禁缓存
数据范围 L1-L5按角色 L0仅本人
特殊状态 考试作答草稿Zustand L3 + localStorage + IDB断网恢复+ 倒计时L3基于服务器时间+ 断网队列IDB
SSR 策略 dashboard SSR dashboard SSR首屏考试作答页强制 CSR防缓存

11. 考试作答边界场景(学生端特有,必须覆盖)

考试作答是学生端最复杂、最易出问题的场景,必须在架构中预留所有边界场景的处理。本节是 ai14 新增。

11.1 网络与设备边界

场景 触发条件 前端处理 后端契约依赖
作答中断网 学生作答过程中网络中断 自动保存失败入 IDB 队列UI 显示"离线模式"标识;继续作答;网络恢复后批量重试 POST /api/v1/student/graphql mutation saveExamAnswer
作答中刷新页面 学生误刷新或浏览器崩溃 Zustand L3 + localStorage + IDB 三级草稿恢复;进入考试页时检测未提交草稿,弹 DraftRecovery 提示是否恢复 无(纯前端恢复)
作答中关闭浏览器 学生主动关闭 同上,下次进入考试页恢复草稿
作答中切到后台标签 学生切换标签(疑似作弊) visibilitychange 事件记录切换次数 + 时长;超过阈值(如 3 次)警告;服务端最终判定(前端仅记录) mutation recordExamSuspiciousBehavior(待 ai04 确认)
作答中设备时间被篡改 学生修改系统时间影响倒计时 倒计时基于服务器返回的 expiresAtISO 8601 UTC前端仅做展示提交以服务器时间为准 query myExams { expiresAt }
作答提交后网络超时 提交响应未到达但服务端已处理 客户端重试前先 query 提交状态;若已提交则跳转结果页,避免重复提交 query examSubmissionStatus(examId)

11.2 时间边界

场景 触发条件 前端处理 后端契约依赖
考试时间到自动提交 倒计时归零 前端触发自动提交 mutation若失败入 IDB 队列重试UI 显示"时间到,正在提交" mutation submitExam
服务器时间与客户端偏差 客户端时间快/慢于服务器 进入考试页时同步服务器时间差(serverTime - clientTime),倒计时按校正后的时间计算;偏差 > 5s 时提示 query serverTime
考试开始前进入 早于 startsAt 显示"考试未开始"倒计时;禁用作答区 query myExams { startsAt expiresAt }
考试结束后进入 晚于 expiresAt 跳转结果页(若已提交)或"考试已结束"提示(若未提交,按缺考处理) query examResult(examId)
考试延时 教师临时延长考试时间 WebSocket 事件 ExamExtended → 重新拉取 expiresAt → 更新倒计时 WebSocket event ExamExtended(待 ai10 msg 确认)

11.3 作答内容边界

场景 触发条件 前端处理 后端契约依赖
客观题单选/多选/填空 默认题型 Zustand L3 存 answers: Record<questionId, AnswerInput>UI 用 Radio/Checkbox/TextInput mutation saveExamAnswer
主观题文本作答 简答题/论述题 textarea + 字数统计;不使用富文本(与 teacher-portal 差异) 同上
附件上传(主观题照片) 学生拍照上传手写答案 文件大小限制(≤ 10MB+ 类型限制jpg/png/pdf+ 分片上传;上传中 UI 显示进度 mutation uploadAttachment(待 ai04 确认)
答案序号变更 教师调整题目顺序(考试中) WebSocket 事件 ExamQuestionReordered → 重新拉取题目 → 草稿按 questionId 映射(不依赖序号) WebSocket event ExamQuestionReordered(待 ai10 确认)
答案为空提交 学生未作答部分题目 提交前弹窗确认"还有 N 题未作答,确认提交?";学生确认后才提交 无(前端校验)

11.4 防作弊边界(前端配合,服务端最终判定)

场景 触发条件 前端处理 备注
切屏检测 visibilitychange 到 hidden 记录切换次数 + 时长 + 时间戳;超过阈值(如 3 次/分钟)警告;服务端最终判定是否违规 仅记录,不阻断作答
复制粘贴检测 监听 copy/paste 事件 阻止默认行为 + 警告;记录次数 主观题允许粘贴自己输入的内容(待产品确认)
全屏退出检测 fullscreenchange 事件 警告 + 记录;不强制全屏(避免影响体验) P6+ lockdown 浏览器才强制
多标签检测 BroadcastChannel 检测同源标签 若检测到同源标签,警告"请勿多开标签" 防止学生开多个标签查答案
右键禁用 contextmenu 事件 考试作答页禁用右键 防止查看源码/搜索

12. 学生隐私与合规

学生数据是高敏感数据含未成年人student-portal 必须在设计阶段预留合规框架。本节是 ai14 新增,覆盖 COPPA、FERPA、PIPL、未成年人保护法等法规对前端架构的要求。

12.1 适用法规矩阵

法规 适用范围 对前端的要求 阶段
COPPA 美国 <13 岁儿童 收集前需家长可验证同意;展示同意记录入口;可删除数据请求入口 P6 海外扩展
FERPA 美国教育记录 学生有权查看自己教育记录;学校有权限制访问 P6 海外扩展
PIPL 中国个人信息保护法 隐私政策弹窗 + 同意按钮;敏感信息(成绩)展示前需二次确认;数据导出请求入口 P3 起强制
GDPR 欧盟用户 Cookie 同意管理;被遗忘权请求入口;数据可携带权导出 P6 海外扩展
未成年人保护法 中国 <18 岁 14 岁以下需家长同意;展示适合年龄段的内容;防沉迷时间提醒 P3 起强制

12.2 前端合规设计

合规点 实现位置 阶段
隐私政策同意弹窗 Shell RootLayout 首次登录后弹窗 P3
Cookie 同意管理(按类别) Shell + student-portal 复用 P3
成绩展示二次确认 /my-grades 默认遮罩,点击"查看"展示 P3
数据导出请求入口 /settings#data-export 页面 P5
数据删除请求入口 /settings#data-deletion 页面 P5
同意记录查看 /settings#consent-history 页面 P5
防沉迷时间提醒 连续使用 > 2 小时弹窗提醒休息 P3
敏感数据脱敏 截图/分享时自动遮罩成绩数字 P5+

12.3 数据保留策略(前端配合)

数据类型 前端保留 后端保留 前端处理
考试作答草稿 IDB 永久(直至提交) 永久 提交成功后清除 IDB 草稿
成绩列表缓存 30sTanStack Query 永久 staleTime 30s 后自动失效
通知列表 30s 90 天 同上
学情诊断数据 30s 永久 同上
行为埋点(防作弊) 内存队列 100 条 90 天 队列满后批量上报,上报后清空

13. 测试策略分层

ai07 初稿仅提到"覆盖率 ≥ 80%"未细化测试类型与覆盖率分目标。ai14 补全。

13.1 测试金字塔

层级 工具 覆盖率目标 测试范围
单元测试 Vitest + @testing-library/react ≥ 85% 所有组件渲染/交互、Hook 业务逻辑、Zod schema 校验、纯函数 utils、倒计时计算、草稿恢复逻辑
集成测试 Vitest + MSWMock GraphQL ≥ 75% urql query + TanStack Query hook 组合、ExamTaking + Zustand slice + 自动保存流程、断网恢复流程
视觉回归 Playwright + Percy/ApplitoolsP6 引入) 关键页面 dashboard/my-homework/my-exams/take/my-grades/diagnostic/weakness 七个核心页面在 light/dark + mobile/desktop 4 组合
E2E 测试 Playwright 关键路径 登录→看 dashboard→作答考试→提交→看成绩→查错题本→登出
A11y 测试 axe-core + jest-axe + @axe-core/playwright 0 严重违规 所有页面 WCAG 2.2 AA 自动扫描 + 手动键盘导航测试;考试作答页键盘可操作
性能测试 Lighthouse CI ≥ 90 分 LCP < 2.5s / CLS < 0.1 / TBT < 200ms移动端 4G 模拟);考试作答页 LCP < 1.5s
契约测试 Pactstudent-bff ↔ student-portal 双向P6 引入) 关键 query/mutation 防止 BFF schema 变更打破前端消费

13.2 关键 E2E 场景(必须覆盖)

- name: exam_taking_normal_flow
  steps:
    - login as student
    - navigate to /my-exams
    - click exam "期末数学"
    - assert /my-exams/:id/take loaded
    - assert countdown timer running
    - answer question 1 (single choice A)
    - answer question 2 (multiple choice A,C)
    - assert draft saved to IDB within 30s
    - reload page
    - assert DraftRecovery dialog shown
    - click "恢复作答"
    - assert answers restored
    - click "提交"
    - assert confirm dialog "还有 8 题未作答"
    - click "确认提交"
    - assert redirect to /my-exams/:id/result
    - assert score displayed

- name: exam_taking_offline_recovery
  steps:
    - login as student
    - enter exam
    - answer 3 questions
    - simulate offline (Playwright network condition)
    - assert "离线模式" indicator shown
    - answer 2 more questions
    - assert auto-save failed but queued in IDB
    - simulate online
    - assert queued answers retried and saved
    - submit exam
    - assert IDB draft cleared

- name: exam_auto_submit_on_timeout
  steps:
    - login as student
    - enter exam with 60s remaining (mock server time)
    - wait 60s
    - assert countdown reaches 0
    - assert "时间到,正在提交" shown
    - assert redirect to result page

- name: homework_submit_flow
  steps:
    - login as student
    - navigate to /my-homework
    - click homework "数学作业 5"
    - assert /my-homework/:id/submit loaded
    - fill answers in form
    - upload attachment (mock file)
    - click submit
    - assert success toast
    - assert homework list refreshed

- name: diagnostic_view
  steps:
    - login as student
    - navigate to /diagnostic
    - assert DiagnosticChart rendered (radar + trend)
    - assert knowledge point mastery displayed

- name: anti_cheat_detection
  steps:
    - login as student
    - enter exam
    - switch to another tab (Playwright)
    - switch back
    - assert warning toast "请勿切换标签"
    - assert suspicious behavior recorded

13.3 Mock 数据策略

数据来源 Mock 方式 维护方
GraphQL 响应 MSW handlersapps/student-portal/src/mocks/handlers.ts 集中管理 ai14
WebSocket 事件 Mock WebSocket ServerPlaywright fixture ai14
i18n 文案 真实 next-intl messages不 Mock coord
设计令牌 真实 ui-tokens不 Mock coord
权限列表 按 fixture 角色预设student_with_exams 等) ai14
服务器时间 MSW 拦截 serverTime query 返回固定时间 ai14

14. 性能预算与代码分割

ai07 初稿未提及性能预算。ai14 补全。

14.1 性能预算Bundle Size

资源类型 预算gzipped 备注
student-portal 首屏 JS ≤ 80 KB 含 ExamTaking + Dashboard + 共享依赖分摊
student-portal 首屏 CSS ≤ 20 KB 含 Tailwind purged + 设计令牌
路由级懒加载 chunk ≤ 30 KB/chunk 每个二级路由单独 chunk考试作答页单独 chunk
考试作答页 JS ≤ 50 KB 含倒计时 + 自动保存 + 断网恢复逻辑
图片 ≤ 100 KB/页 学情图表渲染、空态插画
总下载量(首屏) ≤ 200 KB 4G 网络下 LCP < 2.5s;考试作答页 LCP < 1.5s

14.2 代码分割策略

// apps/student-portal/src/app/(app)/my-exams/[id]/take/page.tsx
import dynamic from "next/dynamic";

const ExamTaking = dynamic(() => import("./ExamTaking"), {
  loading: () => <Skeleton rows={20} />,
  ssr: false, // 考试作答页强制 CSR防缓存 + 防预渲染泄露答案
});

const DiagnosticPage = dynamic(() => import("./DiagnosticPage"), {
  loading: () => <Skeleton rows={10} />,
});

// 学情诊断(重型 recharts独立 chunk
const WeaknessPage = dynamic(() => import("./WeaknessPage"), {
  loading: () => <Skeleton rows={8} />,
});

14.3 预加载策略

触发时机 预加载内容
Dashboard 加载完成 预加载 my-homework chunk + my-exams chunk最常访问
鼠标 hover 考试列表项 预加载该考试的题目数据GraphQL prefetch
通知未读数 > 0 预加载 notifications chunk
用户进入 my-grades 页面 预加载 diagnostic chunk趋势图 next-step
考试开始前 5 分钟 预加载 ExamTaking chunk + 题目数据

14.4 渲染策略

页面 渲染模式 理由
/dashboard SSR首屏+ CSR交互 SEO 无关,但首屏速度
/my-homework CSR + Suspense 认证后数据,无需 SEO
/my-exams CSR 认证后数据
/my-exams/:id/take CSR 强制ssr: false 防缓存 + 防预渲染泄露答案 + 个性化数据
/my-exams/:id/result CSR 认证后数据
/my-grades CSR + Suspense 数据频变
/diagnostic CSR + 流式渲染 图表渲染慢,流式加载
/notifications CSR + 流式渲染 实时性要求

15. 前端安全

ai07 初稿仅在横切关注点提到 401 处理。ai14 补全完整前端安全策略。

15.1 安全头HTTP Headers

由 teacher-portal Shell 在 next.config.js 配置student-portal 复用:

Header 用途
Content-Security-Policy default-src 'self'; script-src 'self' 'unsafe-inline'; ... XSS 防护MF 远程加载需放行 Shell 域
X-Frame-Options SAMEORIGIN 防止 click-jacking
X-Content-Type-Options nosniff 防止 MIME 嗅探
Referrer-Policy strict-origin-when-cross-origin 限制 referrer 泄漏
Permissions-Policy camera=(), microphone=(), geolocation=() 禁用不需要的浏览器能力
Strict-Transport-Security max-age=31536000; includeSubDomains 强制 HTTPS

15.2 XSS 防护

场景 防护措施
考试题目内容展示 React 默认转义,禁止 dangerouslySetInnerHTML
通知内容(含富文本) DOMPurify 清洗后渲染project_rules §4
URL 参数 ?examId= Zod 校验为 UUID 格式,禁止任意字符
localStorage 存储草稿 仅存答案数据,不存敏感信息;登出时清除
主观题文本作答 textarea 默认转义;服务端二次清洗

15.3 CSRF 防护

  • student-portal 仅消费 GraphQL POST + 登录 POST所有 mutation 经 urql client
  • urql client 自动注入 X-Requested-With: XMLHttpRequest
  • 后端 BFF 校验该头 + 同源 Cookie SameSite=Strictproject_rules §4

15.4 考试作答安全(前端配合)

安全点 前端措施 服务端最终判定
防作弊(切屏/复制/多标签) §11.4 边界场景记录
答案篡改 草稿仅前端临时存储,提交时服务端校验题目有效性 + 答案格式
时间篡改 倒计时基于服务器 expiresAt,不依赖客户端时间
重复提交 提交前 query 提交状态,避免重复
越权访问他人考试 BFF DataScope=L0 校验,前端 URL 篡改 ?examId= 无效

15.5 敏感数据处理

数据 敏感级别 前端处理
学生姓名 默认展示截图时脱敏P5+
学生成绩 默认遮罩,点击"查看"展示;页面离开 5s 后自动遮罩
考试作答内容 不缓存到 localStorage仅 IDB 草稿),提交后清除
学生 ID URL 可携带,但 BFF 校验本人权限
通知内容 不缓存到 localStorage仅 TanStack Query 内存缓存

16. 跨标签与跨设备同步

16.1 同步机制矩阵

场景 同步机制 同步内容 冲突解决
同浏览器多标签考试检测 BroadcastChannel API 考试作答中标签切换检测 阻止多标签作答
考试作答草稿跨标签 BroadcastChannel + IDB 草稿变更(仅同一考试) 最后写入胜出LWW
通知未读数跨标签同步 BroadcastChannel unread count 服务端为准
跨设备状态同步 WebSocket 事件P5 通知变更 服务端为准
网络恢复后状态对齐 重连后批量 invalidate + refetch 全部学生维度数据 服务端为准

16.2 BroadcastChannel 实现(考试作答防多标签)

// apps/student-portal/src/lib/examTabGuard.ts
const EXAM_CHANNEL_NAME = "student-exam-guard";

// 进入考试页时检测是否有其他标签正在作答同一考试
export function useExamTabGuard(examId: string) {
  useEffect(() => {
    const channel = new BroadcastChannel(EXAM_CHANNEL_NAME);
    const tabId = `tab-${Date.now()}-${Math.random().toString(36).slice(2)}`;

    // 广播:我正在作答此考试
    channel.postMessage({ type: "exam-entered", examId, tabId, ts: Date.now() });

    // 监听其他标签的响应
    const onMessage = (event: MessageEvent) => {
      const msg = event.data;
      if (msg.tabId === tabId) return;
      if (msg.type === "exam-entered" && msg.examId === examId) {
        // 已有其他标签在作答,警告
        toast.warning(t("exam.multiTabDetected"));
        // 记录可疑行为
        recordSuspiciousBehavior("multi-tab-exam");
      }
    };
    channel.addEventListener("message", onMessage);

    return () => {
      channel.postMessage({ type: "exam-left", examId, tabId });
      channel.close();
    };
  }, [examId]);
}

16.3 考试草稿跨标签恢复

若学生在 Tab A 开始作答,关闭 Tab A 后在 Tab B 重新打开:

  • IDB 草稿跨标签共享(同源策略保证)
  • Tab B 进入考试页时检测 IDB 草稿
  • DraftRecovery 提示"检测到未完成的作答草稿,是否恢复?"
  • 用户确认后从 IDB 加载草稿

17. i18n 深化

ai07 初稿仅提到 next-intl。ai14 补全多语言策略。

17.1 支持语言矩阵

语言 locale 阶段 完成度要求
简体中文 zh-CN P3 强制 100%
英文 en-US P6 海外扩展 100%
繁体中文 zh-TW P6 海外扩展 100%
日文 ja-JP P6+ 未来 ≥ 80%
阿拉伯文RTL ar-SA P6+ 未来 ≥ 80%

17.2 locale 路由策略

采用 URL 前缀策略(与 Shell 共享,由 Shell 配置):

/zh-CN/dashboard
/en-US/dashboard
/dashboard   → 默认重定向到浏览器首选语言
  • Next.js 中间件根据 Accept-Language 头自动重定向
  • 用户主动切换语言时写入 Cookie NEXT_LOCALE,下次访问直接命中
  • student-portal 不维护 locale 路由,复用 Shell 中间件

17.3 翻译文件组织

apps/student-portal/src/i18n/messages/
├─ zh-CN/
│  ├─ common.json       # 通用文案(确认/取消/加载中等)
│  ├─ dashboard.json
│  ├─ homework.json
│  ├─ exams.json
│  ├─ exam-taking.json  # 考试作答专用文案(含倒计时/自动保存/断网提示)
│  ├─ grades.json
│  ├─ attendance.json
│  ├─ learning-path.json
│  ├─ diagnostic.json
│  ├─ weakness.json
│  ├─ notifications.json
│  └─ errors.json       # 错误码 → i18n key 映射
├─ en-US/
│  └─ ... (镜像 zh-CN 结构)
└─ index.ts             # 按需加载 messages

按路由切分 message bundle避免首屏加载全部翻译。

17.4 国际化格式

数据类型 示例zh-CN 示例en-US
日期 Intl.DateTimeFormat 2026年7月10日 July 10, 2026
时间 同上 下午3:30 3:30 PM
数字 Intl.NumberFormat 1,234.56 1,234.56
百分比 同上 85.5% 85.5%
成绩等级 自定义映射表 优秀/良好/及格/不及格 A/B/C/D/F
时区 Intl.DateTimeFormat with timeZone Asia/Shanghai America/Los_Angeles
倒计时 自定义HH:mm:ss 格式) 01:30:00 01:30:00

17.5 RTL 支持P6+ 预留)

  • Tailwind CSS logical propertiesms-*/me-*/ps-*/pe-* 替代 ml-*/mr-*/pl-*/pr-*
  • 设计令牌预留 RTL 语义令牌:--space-inline-start / --space-inline-end
  • 图标方向敏感(如返回箭头)需根据 dir 属性翻转

18. 移动端与 PWA

学生端在移动端使用比例较高(学生多在课后/自习时间查看作业/成绩/学情),移动端策略必须前置。考试作答移动端体验需要特别考虑。

18.1 响应式断点

断点 宽度 典型设备 student-portal 布局变化
sm (default) < 640px iPhone/Android 手机 单列布局;考试作答区全屏;侧栏导航抽屉化
md 640-1024px iPad Mini/Android 平板 双列布局(侧栏 + 内容);考试作答区优化
lg 1024-1280px iPad Pro/小笔记本 三列布局(侧栏 + 内容 + 详情);学情图表完整展示
xl > 1280px 桌面 三列布局;考试作答区居中固定宽度

18.2 移动端交互优化

场景 移动端优化
考试作答 题目纵向滚动;选项触摸友好;倒计时顶部固定
作业提交 附件拍照上传;底部固定提交按钮
查看成绩 卡片式纵向滚动;图表触摸缩放
通知列表 左滑标记已读、右滑删除iOS 风格)
学情诊断 图表简化为单维度;横屏展示完整雷达图
表单提交 底部固定按钮;软键盘弹出时自动避让
长列表 无限滚动 + 骨架屏

18.3 PWA 配置P5+ 引入)

// apps/student-portal/public/manifest.json
{
  "name": "Edu 学生端",
  "short_name": "EduStudent",
  "start_url": "/dashboard",
  "display": "standalone",
  "orientation": "portrait",
  "background_color": "#ffffff",
  "theme_color": "#1677ff",
  "icons": [
    { "src": "/icons/student-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/student-512.png", "sizes": "512x512", "type": "image/png" }
  ],
  "shortcuts": [
    { "name": "我的作业", "url": "/my-homework" },
    { "name": "我的考试", "url": "/my-exams" },
    { "name": "我的成绩", "url": "/my-grades" }
  ]
}

18.4 Service Worker 策略P5+

资源类型 缓存策略 TTL
静态资源JS/CSS/图片) Cache First + 网络更新 24 小时
学生 dashboard 数据 Stale While Revalidate 30 秒
考试作答页 Network Only
考试题目数据 Network Only
通知列表 Network Only
API 401 响应 不缓存

考试作答相关资源强制 Network Only防止缓存泄露答案。


19. 长远愿景与演进路径

19.1 阶段演进路线

graph LR
    P3[P3 核心教学<br/>学生端 MVP<br/>dashboard+homework+exams+take+grades] --> P4[P4 学情分析<br/>diagnostic+weakness+learning-path]
    P4 --> P5[P5 推送<br/>通知中心+WebSocket+防作弊增强]
    P5 --> P6[P6 硬化<br/>PWA+A11y+性能+安全+多语言]
    P6 --> P7[P7+ 扩展<br/>AI 辅导+错题推荐+学习计划]
    P7 --> P8[P8+ 多租户<br/>学区/教育局版]

19.2 未来功能铺垫(架构预留)

未来功能 架构预留点 启用阶段
AI 辅导(个性化答疑) API 路由 /api/v1/student/ai/* 预留SSE 复用 teacher-portal 模式 P7
错题智能推荐 WeaknessList 组件抽象推荐策略GraphQL query recommendedWeakness 预留 P7
学习计划(自动生成) LearningPathMap 组件预留计划编辑模式GraphQL mutation generatePlan P7
同学协作(学习小组) NotificationFeed 抽象为通用消息流WebSocket 事件协议预留 group.* 类型 P7+
移动端原生壳 PWA → Capacitor 打包MF 不变 P8+
离线模式(考试作答断网增强) Service Worker + IDB 队列P5+ 引入) P5+
推送通知Web Push Service Worker PushManager + VAPIDP5+ 引入) P5+
学区/教育局版多租户 URL 路由前缀 /{tenantId}/student/* 预留TanStack Query key 加 tenantId 维度 P8+
lockdown 浏览器(防作弊) 考试作答页检测 lockdown 环境;启用更严格策略 P6+

19.3 模块解耦与演化

演化方向 触发条件 迁移策略
student-portal 拆分为多个 Remote bundle > 200KB 或团队规模 > 5 人 按场景域拆分student-core-remotedashboard/homework/grades+ student-exam-remote考试作答
MF 2.0 → 3.0 升级 MF 3.0 稳定且解决 SSR 问题 Shell 端 @module-federation/nextjs-mf 升级student-portal 仅改 name/filename 字段
切换为原生 SSR脱离 MF SEO 需求强烈或 MF 维护成本过高 保留 GraphQL 请求层和组件库;移除 MF 配置;独立部署为完整 Next.js 应用
状态管理迁移Zustand → Jotai Zustand 性能瓶颈或团队偏好 逐 slice 迁移Hook 接口保持不变
GraphQL → 更轻量协议 GraphQL 性能瓶颈 评估 RPC / tRPCurql client 替换为对应 client

19.4 监控与降级

监控项 阈值 触发动作
student-portal 5xx 错误率 > 1% 告警 SRE自动切换到只读模式隐藏提交按钮
MF Remote 加载失败 加载超时 10s Fallback 到 Shell 内置的最小化 dashboard静态引导页
WebSocket 连接失败 重试 5 次仍失败 降级为 HTTP 轮询(每 60s 拉取通知列表)
BFF 响应延迟 P95 > 3s 前端展示"加载缓慢"提示;自动缩短缓存 TTL
考试作答自动保存失败率 > 5% 告警UI 提示学生手动保存;增加重试频率
考试提交失败 单次失败 自动重试 + 入 IDB 队列3 次失败后人工介入提示

AI Agent: ai14 (student-portal remote) Branch: feat-review-student-portal-docs-9yN6Av Coordinator: coord-ai Predecessor: ai07初版起草ai14 接管审计与补全)