diff --git a/apps/teacher-portal/docs/00-ai13-audit-report.md b/apps/teacher-portal/docs/00-ai13-audit-report.md new file mode 100644 index 0000000..2571dad --- /dev/null +++ b/apps/teacher-portal/docs/00-ai13-audit-report.md @@ -0,0 +1,199 @@ +# ai13 审核 report — teacher-portal 现有文档遗漏分析 + +> AI:ai13(TS/React · teacher-portal 正式负责人,依据 ai-allocation.md §3.2) +> 阶段:阶段 1/2 文档审核 + 长远架构补全 +> 日期:2026-07-09 +> 审核对象: +> +> - [01-understanding.md](./01-understanding.md)(ai07 于 2026-07-09 产出) +> - [02-architecture-design.md](./02-architecture-design.md)(ai07 于 2026-07-09 产出) +> +> 关联裁决:[coord 交叉审查报告](../../../docs/architecture/coord-cross-review.md) §4.1/§5.2/§5.5/§6(整改项 #2/#5/#17/#18)、[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md) §1.2/§4.2/§7.2/§11.4 + +--- + +## 1. 归属与版本背景 + +| 维度 | ai07 版本(被审核) | ai13 版本(本次修正) | +| -------- | --------------------------------------------- | ----------------------------------------- | +| AI 标识 | ai07 | ai13(ai-allocation.md §3.2 正式负责人) | +| 文档日期 | 2026-07-09 | 2026-07-09(同日审核+补全) | +| 文档分支 | docs/teacher-portal-stage1-stage2-design-ai07 | docs/teacher-portal-ai13-audit-supplement | +| 触发原因 | ai07 临时承接 4 端合并设计 | ai13 接管 teacher-portal 正式所有权 | + +> **背景说明**:ai-allocation.md §3.2 矩阵中 ai07 正式职责是 classes(黄金模板),ai13 才是 teacher-portal 的负责人。前期因并行加速,ai07 临时承担 4 端合并设计并产出 `apps/teacher-portal/README.md`(945 行)与 docs/01-02 两份文档。coord 已在 [coord-cross-review.md](../../../docs/architecture/coord-cross-review.md) §6 整改清单 #2/#17/#18 中列出多项需修正项,但整改责任原标注为 ai07。本次 ai13 正式接管并一次性完成审核 + 修正 + 长远架构补全。 + +--- + +## 2. 需修正项(来自 coord 裁决) + +### 2.1 P0 端口冲突修正(coord §4.1,整改 #2) + +| 项 | ai07 原文档 | 正确值 | 依据 | +| -------------- | -------------------------- | ------------------------------------------------------------- | ---------------------------------- | +| teacher-portal | 3000 | **4000** | 004 §1.2 + project_memory 端口约束 | +| student-portal | 3001 | **4001** | 004 §1.2 | +| parent-portal | 3002 | **4002** | 004 §1.2 | +| admin-portal | 3003 | **4003** | 004 §1.2 | +| MF remoteEntry | `localhost:3001/3002/3003` | `localhost:4001/4002/4003` | 与端口矩阵一致 | +| 后端冲突说明 | "3001-3003 已被避让" | 删除(错误描述,3001-3003 仍被 classes/iam/teacher-bff 占用) | coord §4.1 表 | + +### 2.2 P1 端点契约对齐(coord §6 整改 #17,4 处端点不一致) + +| ai07 原文档 | 正确值(以后端服务为准) | 依据 | +| ------------------------------------------------------------ | ------------------------------------------------------------------------- | -------------- | +| `GET /api/v1/ai/generate-questions`(SSE) | `POST /api/v1/ai/chat/stream`(SSE)+ `POST /api/v1/ai/generate/question` | coord §3 表 #1 | +| `GET /iam/effective-permissions` | `GET /iam/permissions/effective` | coord §3 表 #2 | +| `GET /iam/rbac/...` | `GET /iam/viewports`、`GET /iam/roles`、`GET /iam/permissions/*` | coord §3 表 #3 | +| `POST /parent/switch-child`(家长端,不影响 teacher-portal) | `POST /parent/children/:childId/select` | coord §3 表 #4 | + +### 2.3 P1 错误码前缀期望修正(coord §5.5,整改 #18) + +| ai07 原文档 | 正确值 | 依据 | +| ---------------------------------------------- | ---------------------------------------------------------------------- | ---------- | +| 期望 `EXAMS_` / `HOMEWORK_` / `GRADES_` 子前缀 | 删除,统一为 `CORE_EDU_*`(classes 模块保留 `CLASSES_*` 黄金模板遗留) | coord §5.5 | +| BFF 错误码前缀 `BFF_TEACHER_` | 保留(coord §5.2 已裁决 3 BFF 统一为 `BFF_XXX_` 风格) | coord §5.2 | +| 期望 `GW_` 前缀 | 保留(coord §5.4 已裁决 api-gateway 加 `GW_` 前缀) | coord §5.4 | + +--- + +## 3. 长远架构遗漏分析(ai13 新增) + +ai07 原文档已覆盖阶段 1/2 模板要求(位置/限界/契约/技术栈/分层图/领域模型/数据模型/API/事件/横切/交互/风险),但**未充分覆盖以下"长远、全面、面向未来"的架构维度**。这些维度是通用前端架构标准(Next.js + Module Federation + 企业级 SaaS)必备项,本次审核需补全。 + +### 3.1 演进路线缺失 + +| 维度 | ai07 原文档 | 缺失内容 | +| ------------------- | --------------------- | ------------------------------------------------- | +| 当前 → MF 迁移路径 | 仅描述目标态 | 当前单端部署如何演进到 Shell+Remote 的分步路径 | +| 阶段间能力累积 | 仅 P2/P5/P6 三段 | P2/P3/P4/P5/P6 每阶段 teacher-portal 必备能力清单 | +| 后端契约演进对齐 | 无 | 后端 P3 启用 gRPC 后前端是否需要切换、何时切换 | +| REST → GraphQL 切换 | 提一句"待 coord 仲裁" | 切换的触发条件、迁移策略、灰度方案 | + +### 3.2 安全架构缺失 + +| 维度 | ai07 原文档 | 缺失内容 | +| ------------------- | ----------------- | --------------------------------------------------------------- | +| Token 存储安全 | 仅说 localStorage | localStorage 的 XSS 风险 + httpOnly Cookie 替代方案 + CSRF 防护 | +| CSP(内容安全策略) | 无 | Next.js `csp()` / `nonce()` 配置,限制 script/style/img 来源 | +| XSS 防护 | 仅提 DOMPurify | Tiptap 富文本的 XSS 防护、`dangerouslySetInnerHTML` 白名单 | +| CSRF 防护 | 无 | 若改用 Cookie 鉴权需 CSRF Token / SameSite | +| 点击劫持防护 | 无 | X-Frame-Options: DENY + CSP frame-ancestors | +| 子资源完整性 SRI | 无 | MF remoteEntry.js 的 SRI 校验、CDN 静态资源 integrity | +| 敏感数据脱敏 | 无 | 日志/错误上报中 token/PII 脱敏 | + +### 3.3 性能架构缺失 + +| 维度 | ai07 原文档 | 缺失内容 | +| ------------------- | -------------- | ------------------------------------------------------------------ | +| 性能预算(Budget) | 无 | JS bundle ≤ 200KB / CSS ≤ 50KB / LCP ≤ 2.5s / CLS ≤ 0.1 等具体目标 | +| 路由级代码分割 | 无 | Next.js App Router 默认按路由分割 + 动态 import 重型组件 | +| SSR/CSR 决策矩阵 | 仅提"优先 CSR" | 每类页面(dashboard/exam-taking/lesson-prep)的 SSR/CSR 选择标准 | +| 字体加载策略 | 仅说 next/font | FOUT vs FOIT 选择、preload、font-display: swap | +| 图片优化 | 无 | next/image + AVIF/WebP + 响应式 sizes + lazy load | +| MF 共享依赖版本漂移 | 仅说 singleton | 共享依赖版本协商机制、peer deps 警告 | +| Prefetch / Preload | 无 | 路由 prefetch 策略、关键资源 preload | +| 长任务优化 | 无 | React 18 concurrent features、useTransition、useDeferredValue | + +### 3.4 可观测性深度缺失 + +| 维度 | ai07 原文档 | 缺失内容 | +| -------------- | -------------------- | ---------------------------------------------------------------- | +| 错误上报 | 提"P6 接入 Sentry" | Sentry DSN 配置、release tracking、source maps、采样率、PII 过滤 | +| 性能监控 | 仅列 4 项 Web Vitals | RUM(Real User Monitoring)方案、长任务监听、卡顿追踪 | +| 用户行为分析 | 无 | 哪些事件需要埋点、埋点规范、隐私合规 | +| Session Replay | 无 | Sentry Replay / LogRocket 选型、隐私区域遮罩 | +| 前端日志聚合 | 仅 console | 浏览器日志如何汇聚到 Loki(pino-browser → Gateway → Loki) | +| trace_id 透传 | 仅提"从响应头提取" | 浏览器入口生成 trace_id、写入后续请求 header、跨 MF 透传 | + +### 3.5 a11y 实施细节缺失 + +| 维度 | ai07 原文档 | 缺失内容 | +| ---------------- | --------------- | ------------------------------------------ | +| WCAG 2.2 AA 清单 | 仅列工具集 | 逐条对照 WCAG 2.2 AA 13 项准则 + 实施方式 | +| 键盘导航 | 仅提 focus-trap | 跳过链接、Tab 顺序、可见焦点、快捷键 | +| 屏幕阅读器测试 | 无 | NVDA/JAWS/VoiceOver 测试矩阵 + ARIA 模式 | +| 颜色对比度 | 无 | 自动化检测(axe-core)+ 设计令牌对比度审计 | +| 移动端 a11y | 无 | 触摸目标 ≥ 44px、缩放支持、手势替代 | + +### 3.6 i18n 实施细节缺失 + +| 维度 | ai07 原文档 | 缺失内容 | +| -------------- | --------------- | ------------------------------------------------------ | +| next-intl 配置 | 仅提"next-intl" | locale 路由策略(前缀 vs 域名)、SSR i18n、locale 检测 | +| fallback 策略 | 无 | 缺失翻译时回退到默认语言、缺失 key 在 dev 环境警告 | +| 复数/性别 | 无 | ICU MessageFormat 复数规则、性别变化 | +| 日期/数字格式 | 无 | Intl.DateTimeFormat / Intl.NumberFormat 按 locale | +| RTL 支持 | 无 | 是否需要支持阿拉伯语/希伯来语(影响布局) | +| 翻译协作流程 | 无 | 翻译文件位置、协作工具(Crowdin/Transifex)、CI 校验 | + +### 3.7 部署拓扑缺失 + +| 维度 | ai07 原文档 | 缺失内容 | +| ------------- | ---------------- | ---------------------------------------------------- | +| 部署目标 | 仅说 Docker | Docker Compose(P1-P5)→ K8s(P6)演进路径 | +| CDN/Edge 缓存 | 无 | Next.js 静态资源 CDN、edge middleware、ISR 缓存 | +| 多环境配置 | 无 | dev/staging/prod 环境变量管理、Next.js runtimeConfig | +| 健康检查端点 | 仅说 /api/health | /api/health vs /api/ready 区别、K8s probe 配置 | +| 灰度发布 | 无 | MF remoteEntry 版本协商、灰度发布策略 | +| 回滚策略 | 仅说 git revert | 前端回滚特殊性:CDN 缓存失效、用户会话保持 | + +### 3.8 测试策略细节缺失 + +| 维度 | ai07 原文档 | 缺失内容 | +| -------------- | --------------- | ------------------------------------------------------- | +| 单元测试 | 仅说 Vitest | 测试什么(hooks/utils/pure components)、mocking 策略 | +| 集成测试 | 无 | 组件树集成、API mock(MSW)、TanStack Query 集成测试 | +| E2E 测试 | 仅说 Playwright | 关键路径(登录/dashboard/班级 CRUD)、CI 并发、稳定策略 | +| 视觉回归测试 | 无 | Storybook + Chromatic / Percy 选型 | +| 覆盖率目标分层 | 仅说 ≥80% | hooks ≥90% / utils ≥90% / components ≥80% / pages ≥60% | +| 性能测试 | 无 | Lighthouse CI / WebPageTest 集成 | + +### 3.9 文件/目录结构目标态缺失 + +ai07 原文档 §6/§7 列出共享组件库/Hooks/令牌的"待建包",但**未给出 teacher-portal 自身的目录结构目标态**(当前 vs 目标对比)。 + +### 3.10 移动端策略缺失 + +ai07 原文档未提及: + +- 是否需要响应式适配(教师在家备课用手机查看)? +- 是否需要 PWA / Service Worker(离线批改作业)? +- 是否需要后续原生 App(React Native / Electron 桌面端)? +- 移动端手势、触摸目标规范 + +### 3.11 MF 演进策略缺失 + +ai07 原文档描述了 MF 2.0 目标态,但**未回答关键演进问题**: + +- 当前 teacher-portal 是单端部署(无 MF),何时引入 MF? +- 引入 MF 后如何保持现有路由不被破坏? +- MF 失败时的降级方案(remoteEntry.js 加载失败怎么办)? +- MF 版本协商(Shell 与 Remote 的合约版本)? + +### 3.12 数据流与乐观更新缺失 + +ai07 原文档 §4.2 提了 TanStack Query mutation,但**未覆盖**: + +- 乐观更新策略(提交作业、批改成绩时如何乐观更新 UI) +- 冲突解决(多教师同时批改同一作业) +- 失败回滚机制 +- 缓存失效链路(mutate A 后哪些 B/C/D 需要失效) + +--- + +## 4. 本次审核产出 + +ai13 本次审核产出 **3 份文档**: + +1. **本报告**:`apps/teacher-portal/docs/00-ai13-audit-report.md`(审核报告,永久保留作为审核凭证) +2. **更新** `apps/teacher-portal/docs/01-understanding.md`:修正归属/端口/端点/前缀,补充长远维度 +3. **更新** `apps/teacher-portal/docs/02-architecture-design.md`:修正归属/端口/端点/前缀,补充横切与演进章节 +4. **新建** `apps/teacher-portal/docs/03-long-term-architecture.md`:长远架构补全(演进/安全/性能/可观测/a11y/i18n/部署/测试/移动端/MF 演进) + +--- + +**AI Agent**: ai13 (teacher-portal) +**Branch**: docs/teacher-portal-ai13-audit-supplement +**Coordinator**: coord-ai +**Audited**: ai07 stage1+2 outputs (2026-07-09) diff --git a/apps/teacher-portal/docs/03-long-term-architecture.md b/apps/teacher-portal/docs/03-long-term-architecture.md new file mode 100644 index 0000000..91382f2 --- /dev/null +++ b/apps/teacher-portal/docs/03-long-term-architecture.md @@ -0,0 +1,770 @@ +# teacher-portal 长远架构补全 + +> AI:ai07(4 端合并设计,承接 ai13 审核 §3 长远遗漏补全) +> 阶段:阶段 2 补充交付物(长远架构) +> 日期:2026-07-09 +> 关联: +> +> - [02-architecture-design.md](./02-architecture-design.md)(ai13 修正版,阶段 2 主文档) +> - [00-ai13-audit-report.md](./00-ai13-audit-report.md) §3 长远遗漏分析 +> - [004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md) §1.1a/§3.1/§5.4 +> - [project_rules](../../../.trae/rules/project_rules.md) §3.10/§4 +> +> 本文档承接 ai13 在 [00-ai13-audit-report.md](./00-ai13-audit-report.md) §3 列出的 12 项长远遗漏,逐一补全。原则:**架构不堵死未来演进路径**,当前不实现的也要预留接入点。 + +--- + +## 1. 演进与迁移策略(补 ai13 §3.1 / §3.11) + +### 1.1 阶段能力累积矩阵 + +| 阶段 | teacher-portal 必备能力 | 依赖上游 | 退出标准 | +| ---- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------- | ------------------------------------------------------------- | +| P2 | 登录 + AppShell + 视口导航 + classes CRUD + GraphQL client(urql,F9 裁决 P2 起) + 设计令牌三层 + ErrorBoundary + /api/health&ready | iam、teacher-bff、classes | 教师登录看到按角色的侧边栏 + 班级 CRUD | +| P3 | 考试/作业/成绩页面 + MF Shell 配置就绪(暂无 Remote)+ 乐观更新 + 多 Tab 会话同步 | core-edu(exams/homework/grades) | 教师创建考试→学生(student-portal Remote)作答→教师批改全链路 | +| P4 | 知识图谱页 + 学情分析页 + parent-portal Remote 接入 | content、data-ana、parent-bff | 教师查看知识图谱 + 学情宽表 5s 返回 | +| P5 | WebSocket 推送 + AI 辅助出题 SSE + 通知中心 | msg、push-gateway、ai | 全校广播实时到达 + AI 流式出题 | +| P6 | Sentry + RUM + OTel browser + A11y 审计 + 性能优化 + localStorage→httpOnly Cookie 迁移 | 全链路可观测 | 99.9% 可用 + WCAG 2.2 AA | + +### 1.2 后端 gRPC 启用对前端的影响(补 ai13 §3.1) + +004 §4.2 裁决各服务分阶段启用 gRPC(P3 iam+core-edu、P4 content+data-ana、P5 ai)。**前端不直接消费 gRPC**,影响只在 BFF 层。F9 裁决 BFF P2 起即用 GraphQL(非"P4 起 REST→GraphQL 迁移"),前端 P2 起即引入 urql client。 + +| 后端切换 | BFF 动作 | 前端动作 | +| ---------------------------- | --------------------------------------------- | -------------------------------------------- | +| 服务 HTTP→gRPC | BFF 改为 gRPC client 调用服务 | 无感(BFF 仍对前端暴露 GraphQL) | +| BFF GraphQL(P2 起,已裁决) | teacher-bff P2 起即 GraphQL Yoga + DataLoader | 前端 P2 起即引入 urql client,统一走 GraphQL | + +**前端契约**:始终消费 `ActionState` 信封(004 §11.5),与传输协议无关。BFF GraphQL schema 由 ai03 维护,coord 在批次 1 启动前仲裁第一版(ISSUE-037)。 + +### 1.3 单体 → MF 迁移路径(绞杀者模式,补 ai13 §3.11) + +``` +当前态 过渡态 目标态 +┌─────────────┐ ┌──────────────────┐ ┌──────────────────┐ +│ teacher-portal │ │ teacher-portal │ │ teacher-portal │ +│ 单体 Next.js │ ──> │ Shell + MF 配置 │ ──> │ Shell │ +│ 5 个页面 │ │ (暂无 Remote) │ │ + student Remote │ +│ │ │ + student-portal │ │ + parent Remote │ +│ │ │ 作为首个 Remote │ │ + admin Remote │ +└─────────────┘ └──────────────────┘ └──────────────────┘ +``` + +| 步骤 | 动作 | 回滚 | 验证 | +| ---- | ------------------------------------------------------------------------------ | -------------------------------------------------------- | ------------------------- | +| 1 | 升级 Next.js 14→15 + react 18→19 | git revert | classes CRUD 端到端 | +| 2 | 抽取 ui-tokens/ui-components/hooks 到 packages | 引用回退到本地 | 视觉无变化 | +| 3 | teacher-portal 加 NextFederationPlugin(Shell,exposes AppShell,暂无 Remote) | 删除 plugin 配置 | 现有页面不受影响 | +| 4 | student-portal(P3)接入为首个 Remote | feature flag `NEXT_PUBLIC_MF_ENABLED=false` 回退单体路由 | 学生作答页在 Shell 内渲染 | +| 5 | parent/admin 逐步接入 | 同上 | 各端独立可访问 | + +**关键**:每步用 feature flag 控制,失败可一键回退到单体路由;保留旧路由文件直到 MF 稳定(2 个版本周期)。 + +### 1.4 GraphQL 客户端架构(P2 起最终方案,补 ai13 §3.1) + +> 本节原为"REST → GraphQL 切换策略"(过渡方案描述)。按总裁裁决 F9(P2 起 BFF 用 GraphQL)+ §4.4(删除过渡方案本身,保留设计决策记录)+ ISSUE-036-ai13 回写义务,已替换为最终方案描述。 + +**裁决依据**:coord-final-decisions F9 + 总裁裁决 §2.17(MF AppShell GraphQL client 单例,采纳方案 A:Shell 暴露 GraphQLProvider)。 + +#### 1.4.1 client 选型与单例 + +| 维度 | 方案 | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| client 库 | urql(轻量、可插拔 exchange、TS 友好;备选 apollo,但 urql bundle 更小,符合 §3.1 性能预算) | +| 单例归属 | teacher-portal Shell 暴露 `GraphQLProvider`(@edu/mf-contracts 登记),student/parent/admin Remote 复用 Shell 单例(总裁 §2.17 方案 A) | +| MF shared 配置 | `react` / `urql` / `graphql` 设为 MF singleton,避免 Remote 各自初始化导致多实例 + 缓存不一致 | +| 缓存交换 | urql `cacheExchange`(文档缓存 +normalized 可选);MF 下 Shell 单例 cache 跨 Remote 共享 | + +#### 1.4.2 client 初始化(P2 起) + +```typescript +// teacher-portal Shell:packages/hooks 提供 useGraphQLClient,Shell 注入 GraphQLProvider +import { createClient, Client } from "urql"; +import { cacheExchange, fetchExchange } from "urql"; + +const client: Client = createClient({ + url: process.env.NEXT_PUBLIC_TEACHER_BFF_GRAPHQL_URL!, // teacher-bff /graphql endpoint + fetchOptions: () => { + const token = readAuthToken(); // useAuth 注入 + const traceId = readTraceId(); + return { + headers: { + ...(token ? { Authorization: `Bearer ${token}` } : {}), + "X-Trace-Id": traceId, + }, + }; + }, + exchanges: [cacheExchange, fetchExchange], +}); +``` + +- Shell 在 `app/providers.tsx` 包裹 ``,所有 Remote 通过 Shell 暴露的 `useGraphQLClient()` 取同一 client +- token 刷新/401 处理由 useAuth 统一拦截(不污染 urql exchange 链),见 §2.7 + +#### 1.4.3 错误处理与 ActionState 契约 + +| 场景 | 处理 | +| ------------------------------ | --------------------------------------------------------------------------------------------------------------- | +| GraphQL 错误(errors[]) | 解析 `extensions.code`(BFF_TEACHER_* 前缀,G14 裁决),映射到 i18n key `error.teacher_bff.`(F11) | +| 网络错误 | 重试 1 次 → 提示网络异常 → 记录 trace_id | +| 401 | useAuth 静默 refresh 一次 → 重试;失败跳登录(见 §2.7) | +| 部分失败(data + errors 共存) | 渲染 data 可用部分 + errors 区域提示,不整体失败 | + +- BFF 始终返回 `ActionState` 信封(004 §11.5),前端统一走 `normalizeActionState()` 工具(packages/hooks) + +#### 1.4.4 缓存策略 + +| 数据类型 | staleTime | 失效策略 | +| -------------- | ----------------- | --------------------------------------------- | +| 班级列表/详情 | 30s | mutation 后 invalidate(见 §10.2) | +| 考试/作业/成绩 | 10s | mutation 后 invalidate + 乐观更新(见 §10.1) | +| 用户权限/视口 | 0(always fetch) | 角色变更推送后强制 invalidate | +| Dashboard 概览 | 60s | ISR + 客户端 stale-while-revalidate | + +- urql 默认 document cache 适合简单场景;复杂关联(如班级↔考试↔成绩)P3 起评估切 `@urql/exchange-graphcache` normalized cache +- 不引入 TanStack Query 做 GraphQL 缓存(urql 自带缓存足够,避免双缓存层);TanStack Query 仅用于非 GraphQL 场景(文件上传/SSE,见 useApi) + +#### 1.4.5 与 ISSUE-037/038/039 的关联 + +| ISSUE | 关联点 | 状态 | +| -------------- | ----------------------------------------------------------------------- | ------------- | +| ISSUE-037-ai13 | teacher-bff GraphQL schema 第一版由 ai03 维护,coord 批次 1 启动前仲裁 | 待 coord 仲裁 | +| ISSUE-038-ai13 | MF AppShell 暴露 GraphQL client 单例(总裁 §2.17 方案 A 已采纳) | 已裁决 | +| ISSUE-039-ai13 | packages(含 hooks 中的 GraphQL client 工具)建立时机:批次 0.15 已建立 | 已执行 | + +#### 1.4.6 设计决策记录(原过渡方案追溯) + +> 本节为决策追溯,非过渡方案描述。 + +- **决策**:P2 起 BFF 即 GraphQL,前端即 urql,不经过 REST 中间态 +- **裁决来源**:coord F9 + 总裁 §0.3"不分阶段"原则 + §2.17 MF GraphQL client 单例 +- **原过渡方案**(已废弃):曾考虑 P2-P4 REST + P5+ 评估迁移 GraphQL + feature flag 灰度。该方案违反"不分阶段"原则,已删除 +- **影响范围**:02-architecture-design.md(已回写)+ 03-long-term-architecture.md §1.4(本次回写) + +--- + +## 2. 安全架构(补 ai13 §3.2) + +### 2.1 Token 存储演进 + +| 阶段 | access token | refresh token | 风险等级 | +| ------- | -------------------------------------- | ------------------------------------------ | -------------- | +| P2 过渡 | localStorage(Zustand 持久化) | localStorage | 高(XSS 可读) | +| P6 终态 | 内存(Zustand sessionSlice,刷新丢失) | httpOnly + Secure + SameSite=Strict cookie | 极低 | + +**切换条件**:iam 支持 refresh cookie 端点 + CSRF 防护就绪后迁移。 + +### 2.2 CSP(内容安全策略) + +MF 下 CSP 必须显式允许 remote origin。Next.js 通过 `next.config.js` headers 或 middleware.ts 注入: + +``` +Content-Security-Policy: + default-src 'self'; + script-src 'self' 'unsafe-inline' http://localhost:4001 http://localhost:4002 http://localhost:4003; + connect-src 'self' http://localhost:8080 ws://localhost:8081; + style-src 'self' 'unsafe-inline'; + img-src 'self' data: blob:; + font-src 'self' data:; + frame-ancestors 'none'; + base-uri 'self'; + object-src 'none'; +``` + +- 生产环境 remote origin 改对应域名,逐步移除 `'unsafe-inline'`(用 nonce/hash 替代) +- 配套头:`X-Content-Type-Options: nosniff`、`X-Frame-Options: DENY`(防点击劫持)、`Referrer-Policy: strict-origin-when-cross-origin`、`Permissions-Policy: geolocation=(), microphone=(), camera=()` + +### 2.3 MF Remote 完整性校验 + +Remote `remoteEntry.js` 由 Shell 动态加载,存在"被篡改 remote 注入恶意代码到 Shell 上下文"风险(MF 共享 singleton 使 remote 可访问 Shell 全局状态)。 + +| 缓解措施 | 说明 | +| ---------------------------- | ----------------------------------------------------------------------------------------------- | +| Subresource Integrity(SRI) | remoteEntry.js 计算 SHA-384,Shell 加载时校验(MF 2.0 支持 `integrity` 字段) | +| Remote origin 白名单 | CSP `script-src` 限定已知 remote 域名 | +| 最小暴露面 | Shell `exposes` 仅暴露必要组件,不暴露整个 store/ApiClient 内部 | +| Remote 隔离 | Remote 不直接持有 Shell 的 token,统一通过 `useApi()` Hook 取 ApiClient(token 注入在 Hook 层) | +| CDN 静态资源 SRI | next/image、next/font 产物带 integrity 属性 | + +### 2.4 CSRF 防护(切换 cookie 后) + +- SameSite=Strict cookie 阻断跨站携带 +- 对 state-changing mutation(POST/PUT/DELETE)额外校验 `X-CSRF-Token` header(双提交 cookie 模式) +- Next.js middleware.ts 校验 origin header 与允许域名匹配 + +### 2.5 多 Tab 会话同步(关键遗漏补齐) + +教师多 Tab 操作时,登出/角色变更须跨 Tab 同步,否则出现"Tab A 登出但 Tab B 仍可用"的越权窗口。 + +| 事件 | 同步机制 | 动作 | +| ---------- | ------------------------------------------------------- | ----------------------------------------- | +| 登出 | `BroadcastChannel('edu-session')` postMessage('logout') | 所有 Tab 清 session + 跳登录 | +| Token 刷新 | BroadcastChannel('token-refreshed') | 其他 Tab 更新内存 token | +| 角色变更 | WebSocket 推送(P5)+ BroadcastChannel | 所有 Tab invalidate 权限缓存 + 重渲染视口 | +| 会话过期 | BroadcastChannel('session-expired') | 跳登录 + 提示 | + +降级:不支持 BroadcastChannel 的浏览器走 `localStorage` `storage` 事件。 + +### 2.6 XSS / 注入防护 + +- `dangerouslySetInnerHTML` 全局禁用(project_rules §4),AI 流式内容先用 DOMPurify 清洗再渲染 +- Tiptap 内容序列化为 JSON 存储,渲染走 ProseMirror schema(白名单节点),禁止 raw HTML +- 用户输入展示统一走 React 转义 + +### 2.7 鉴权边界与权限失效 + +| 场景 | 前端动作 | +| ------------------ | ----------------------------------------------------------------------------------------------------------------- | +| access token 过期 | 401 → 静默 refresh 一次 → 重试;失败跳登录 | +| refresh token 过期 | 跳登录 + 提示"会话已过期" | +| 角色被撤销 | WebSocket 推送 `RoleRevoked` → invalidate 权限 → 重新拉取 → 视口收敛;权限不足以访问当前路由 → 重定向到 dashboard | +| 强制下线 | iam 调 push-gateway 推 `ForceLogout` → 前端清 session | + +### 2.8 敏感数据脱敏(补 ai13 §3.2) + +- 日志/错误上报前过滤:token、password、refreshToken、PII(身份证/手机号)字段置 `[REDACTED]` +- Sentry 配置 `beforeSend` 钩子剥离 PII;Session Replay 对敏感输入框 `mask` 所有文本 +- 前端不持久化密码原文,登录后立即从内存清除 + +--- + +## 3. 性能架构(补 ai13 §3.3) + +### 3.1 性能预算 + +| 指标 | 目标 | 度量 | CI 门禁 | +| ----------------------- | ----------------- | ----------------------------- | --------------- | +| LCP | < 2.5s(P75) | Web Vitals RUM | lighthouse CI | +| INP | < 200ms(P75) | Web Vitals RUM(CWV 2024) | lighthouse CI | +| CLS | < 0.1 | Web Vitals RUM | lighthouse CI | +| TTFB | < 800ms | Web Vitals RUM | — | +| 长任务(>50ms) | < 5 次/页 | PerformanceObserver Long Task | — | +| Shell JS bundle(gzip) | < 150KB | @next/bundle-analyzer | size-limit fail | +| 每个 Remote JS(gzip) | < 80KB | bundle analyzer | size-limit fail | +| CSS(gzip) | < 50KB | bundle analyzer | — | +| shared deps 重复 | 0(MF singleton) | MF manifest 校验 | CI 检查 | +| 首屏请求数 | < 20 | lighthouse CI | — | + +- CI 集成 `@next/bundle-analyzer` + size-limit,超预算 fail build +- 每版本 lighthouse CI 报告归档,回归监控 + +### 3.2 渲染模式决策矩阵 + +| 页面类型 | 模式 | 理由 | +| -------------------------- | ------------------------------- | ---------------------------- | +| 登录页 | SSR | SEO + 首屏快 | +| Dashboard 概览 | SSR + Streaming | 首屏骨架 + Suspense 流式填充 | +| 列表/表格(classes/exams) | CSR + TanStack Query | 强交互、筛选、分页 | +| 详情页(成绩/学情) | ISR(revalidate 60s)+ CSR 交互 | 数据准实时,CDN 缓存降负载 | +| AI 出题(SSE) | CSR | 长连接流式 | +| 知识图谱可视化 | CSR | 交互重 | +| 管理端配置 | CSR | 纯交互 | +| 考试作答(student) | CSR | 防刷新丢答 + 离线缓存 | + +### 3.3 Next.js 缓存层级(App Router) + +| 缓存层 | 用途 | 失效策略 | +| ------------------------- | -------------------- | --------------------- | +| Request Memoization | 单次请求内去重 fetch | 请求结束即失效 | +| Data Cache(fetch cache) | 跨请求 fetch 结果 | revalidate / tag 失效 | +| Full Route Cache | 静态路由产物 | 按路由 revalidate | +| Router Cache(客户端) | RSC payload + 预取 | staleTime 30s | + +**约定**:BFF 数据 `fetch(..., { next: { revalidate: 30, tags: ['classes'] } })`;mutation 后 `revalidateTag('classes')`。 + +### 3.4 Server / Client Component 边界 + +- **Server Component**:数据获取、静态展示、设计令牌注入、i18n Provider +- **Client Component**:交互(onClick/state)、TanStack Query、Zustand、Tiptap、图表 +- 边界标记:`'use client'` 仅在交互根节点声明,子树尽量保持 Server Component + +### 3.5 字体加载策略(补 ai13 §3.3) + +| 策略 | 实现 | +| -------- | ------------------------------------------------------------------------ | +| 加载方式 | `next/font/google`(self-host,无第三方请求) | +| 显示策略 | `font-display: swap`(FOUT,优先文本可见性,避免 FOIT 白屏) | +| preload | 首屏字体 `preload`,次屏字体 lazy | +| 子集化 | Inter/Fraunces 按中文字符子集(next/font 自动 latin,中文需手动 subset) | +| 变量暴露 | `var(--font-family-sans/serif/mono)`,禁止字面量(project_rules §3.10) | + +### 3.6 图片优化(补 ai13 §3.3) + +- 统一 `next/image`:自动 AVIF/WebP 转换 + 响应式 `sizes` + lazy load + blur placeholder +- 知识图谱节点图、用户头像走 `next/image`;远程图配 `remotePatterns` 白名单 +- 图表截图导出走 `canvas.toBlob` + CDN,不走 base64 内联 + +### 3.7 路由级代码分割与长任务优化(补 ai13 §3.3) + +- App Router 默认按路由分割;重型组件(Tiptap/recharts/知识图谱)用 `@next/dynamic` 懒加载 +- React 18 concurrent:长列表用 `useDeferredValue`;路由切换用 `useTransition` 标记非紧急更新 +- MF Remote 按 scope 动态加载,非当前 scope 不下载 remoteEntry.js + +### 3.8 Prefetch / Preload 策略 + +| 资源 | 策略 | +| ------------------ | ------------------------------------------- | +| 当前路由数据 | TanStack Query 预取(`prefetchQuery`) | +| 可能访问的 Remote | `requestIdleCallback` 预拉取 remoteEntry.js | +| 关键字体/Logo | `` | +| 下一页 RSC payload | Next.js Link `prefetch={true}`(默认) | + +--- + +## 4. 可观测性深度(补 ai13 §3.4) + +### 4.1 三支柱前端落地 + +| 支柱 | 工具 | 实现 | 阶段 | +| ---- | ------------------------------------------ | ------------------------------------------- | ---- | +| 日志 | pino-browser → Gateway → Loki | 结构化 JSON,含 trace_id/user_id/scope/path | P6 | +| 指标 | Web Vitals → `/api/v1/admin/web-vitals` | LCP/INP/CLS/TTFB + Long Task | P6 | +| 链路 | OTel browser SDK → OTLP collector → Jaeger | 自动埋点 fetch/XHR/document load | P6 | + +### 4.2 trace_id 全链路透传(补 ai13 §3.4) + +``` +浏览器入口生成 trace_id(crypto.randomUUID) + → 写入 fetch header 'X-Request-Id' + → Gateway 透传 + → 服务间 gRPC metadata + → 日志/metrics/trace 统一 trace_id + → 响应头返回 'X-Request-Id' + → 前端日志记录(便于用户报障定位) +``` + +- MF 透传:Shell 生成 trace_id 后通过共享 `useApi()` 注入所有 Remote 的请求 +- Session 模型补 `traceId` 字段(ai13 已在 02 补全) + +### 4.3 错误追踪与 Session Replay(补 ai13 §3.4) + +| 维度 | 方案 | +| ---------------- | ------------------------------------------------------------------------- | +| 平台 | Sentry(P6) | +| DSN | 环境变量 `NEXT_PUBLIC_SENTRY_DSN` | +| release tracking | 每次构建注入 commit SHA | +| source maps | CI 上传到 Sentry(生产不暴露) | +| 采样率 | errors 100%,traces 10%,replay 1% | +| PII 过滤 | `beforeSend` 剥离 token/password/身份证;Session Replay 对敏感输入 `mask` | +| Session Replay | Sentry Replay,敏感区域 `data-sentry-mask` | + +### 4.4 用户行为埋点规范(补 ai13 §3.4) + +| 事件类别 | 示例 | 上报 | +| -------- | -------------------------------------------- | -------------------------- | +| 页面访问 | `page_view` {path, scope, role} | Gateway 聚合 | +| 关键操作 | `exam_create`/`homework_grade`/`ai_generate` | Gateway 聚合 | +| 错误 | `action_failed` {code, path} | Sentry + Gateway | +| 性能 | `web_vitals` {lcp, inp, cls} | `/api/v1/admin/web-vitals` | + +- 埋点统一走 `packages/hooks/src/useAnalytics()`,禁止散落 `gtag`/`mixpanel` 直调 +- 隐私合规:不采集身份证/成绩具体值,仅采集聚合指标 + +### 4.5 前端日志聚合(补 ai13 §3.4) + +``` +pino-browser (batch 10 条/5s flush) + → POST /api/v1/admin/logs + → Gateway → Loki + → Grafana 查询(按 trace_id/user_id/path 过滤) +``` + +- 级别:debug(dev only)/info/warn/error +- 采样:info 10%,warn 100%,error 100% + +--- + +## 5. a11y 实施细节(补 ai13 §3.5) + +### 5.1 WCAG 2.2 AA 对照清单 + +| 准则 | 实施方式 | +| -------------- | ----------------------------------------------------- | +| 1.1 非文本内容 | 所有 `` 有 `alt`;装饰图 `alt=""` | +| 1.3 适应性 | 语义化 HTML(`