1.AI 协作文档体系重构(objections/worklines/contracts+matrix.md) 2.coord 仲裁文档(final-decisions/cross-review/final-rulings/orchestration) 3.各服务 01/02 文档补全 4.共享包初始化(shared-ts/shared-go/hooks/ui-components/ui-tokens) 5.Proto 契约补全 6.004 架构影响地图更新 7.端口分配表 8.设计规格文档
771 lines
50 KiB
Markdown
771 lines
50 KiB
Markdown
# 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` 包裹 `<Provider value={client}>`,所有 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.<code_snake>`(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 | `<link rel="preload">` |
|
||
| 下一页 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 非文本内容 | 所有 `<img>` 有 `alt`;装饰图 `alt=""` |
|
||
| 1.3 适应性 | 语义化 HTML(`<nav>/<main>/<aside>`);ARIA role 补充 |
|
||
| 1.4 可辨别性 | 对比度 ≥ 4.5:1(设计令牌审计);禁用纯色信息编码 |
|
||
| 2.1 键盘可达 | 全键盘导航;可见焦点(`focus-visible`);跳过链接 |
|
||
| 2.4 导航 | 标题层级正确;`<title>` 动态;面包屑 |
|
||
| 2.5 输入模态 | 触摸目标 ≥ 44px;取消指针;防误触 |
|
||
| 3.1 可读 | `lang` 属性;缩写标注 |
|
||
| 3.2 可预测 | 一致导航;无意外跳转 |
|
||
| 3.3 输入辅助 | 错误标识 + `aria-describedby` + 修正建议 |
|
||
| 4.1 兼容 | ARIA 正确使用;不依赖仅颜色 |
|
||
|
||
### 5.2 键盘导航
|
||
|
||
- 跳过链接(skip to main content)首位
|
||
- Tab 顺序与视觉顺序一致(DOM 顺序即 tab 顺序)
|
||
- Modal 打开时焦点陷阱(`focus-trap`),关闭后焦点返回触发元素
|
||
- 快捷键:`/` 聚焦搜索、`g` 后接字母跳转(teacher/student/parent/admin)
|
||
|
||
### 5.3 屏幕阅读器测试矩阵
|
||
|
||
| 读屏器 | 平台 | 阶段 |
|
||
| --------- | --------- | ------- |
|
||
| NVDA | Windows | P6 审计 |
|
||
| JAWS | Windows | P6 抽测 |
|
||
| VoiceOver | macOS/iOS | P6 审计 |
|
||
| TalkBack | Android | P6 抽测 |
|
||
|
||
### 5.4 自动化检测
|
||
|
||
- `eslint-plugin-jsx-a11y`(error 级,CI 强制)
|
||
- `@axe-core/playwright` E2E 集成,每版本扫描关键页面
|
||
- 设计令牌对比度审计脚本(primitive 色板两两组合计算对比度)
|
||
|
||
---
|
||
|
||
## 6. i18n 实施细节(补 ai13 §3.6)
|
||
|
||
| 维度 | 方案 |
|
||
| ----------- | --------------------------------------------------------------------------------------------- |
|
||
| 库 | next-intl(App Router 原生支持) |
|
||
| 路由 | `/[locale]/teacher/...`(前缀式,默认 `zh`) |
|
||
| 检测 | middleware.ts:cookie `NEXT_LOCALE` → `Accept-Language` → 默认 `zh` |
|
||
| 加载 | 按路由分片动态加载 message(`getMessages` + `useTranslations`) |
|
||
| fallback | `zh` 为 fallback locale,缺 key 回退;dev 环境缺 key console.warn |
|
||
| 复数/性别 | ICU MessageFormat(next-intl 内置):`{count, plural, =0 {无} one {# 项} other {# 项}}` |
|
||
| 数字/日期 | `Intl.NumberFormat` / `Intl.DateTimeFormat`(按 locale,不用 dayjs 本地化) |
|
||
| 错误码 i18n | `error.{{service}}.{{code_snake_case}}`(见 02 §6.2) |
|
||
| RTL 预留 | 布局用 logical properties(`margin-inline`/`padding-inline`),未来阿拉伯语只需切换 direction |
|
||
| 文档术语 | 业务术语统一 glossary(`packages/contracts/src/i18n-glossary.ts`)防翻译漂移 |
|
||
| 翻译协作 | `i18n/locales/*.json` 纳入 git;CI 校验 key 完整性(zh/en key 集合一致);后续可接 Crowdin |
|
||
|
||
> BFF/服务返回 i18n key + 参数(不返回已翻译文案),前端统一翻译,避免多端文案不一致。
|
||
|
||
---
|
||
|
||
## 7. 部署拓扑(补 ai13 §3.7)
|
||
|
||
### 7.1 部署演进
|
||
|
||
| 阶段 | 目标 | 方式 |
|
||
| ----- | ------------------- | ---------------------------------------------------------- |
|
||
| P1-P5 | 单机 Docker Compose | `docker compose up -d --build`(每服务 Dockerfile 多阶段) |
|
||
| P6+ | K8s | 每服务 Deployment + HPA + Service + Ingress |
|
||
|
||
### 7.2 MF 部署与 CDN
|
||
|
||
| 端 | 部署单元 | 策略 | 备注 |
|
||
| ------------------------------ | -------- | ----------- | ---------------------------------------- |
|
||
| teacher-portal(Shell) | 独立 | 滚动 | Shell 变更频率最低 |
|
||
| student/parent/admin(Remote) | 各自独立 | 蓝绿/金丝雀 | remoteEntry.js 不可变文件名 + CDN 长缓存 |
|
||
|
||
- **部署顺序约束**:破坏性 shared dep 升级时,Remote 先升、Shell 后升(Shell singleton 向下兼容旧 Remote)
|
||
- **CDN**:remoteEntry.js 设 immutable 长缓存,版本化文件名 `remoteEntry.{version}.js` 解决缓存失效
|
||
- **预拉取**:Shell 在 idle 时预拉取用户角色对应 Remote 的 remoteEntry.js(`requestIdleCallback`)
|
||
- **Edge middleware**:locale 检测、A/B 测试、灰度路由放 edge(Vercel/Cloudflare Workers)
|
||
|
||
### 7.3 多环境配置
|
||
|
||
| 环境 | 配置来源 | 说明 |
|
||
| ------- | ---------------------- | -------- |
|
||
| dev | `.env.local` | 本地开发 |
|
||
| staging | K8s ConfigMap + Secret | 预发布 |
|
||
| prod | K8s Secret | 生产 |
|
||
|
||
- 前端变量必须 `NEXT_PUBLIC_` 前缀(project_rules §4);服务端变量不加
|
||
- 环境校验用 `@t3-oss/env-nextjs` + Zod,启动时 fail-fast
|
||
|
||
### 7.4 健康检查端点
|
||
|
||
| 端点 | 用途 | 实现 |
|
||
| ----------------- | --------------------------------- | ----------------------------------------------------------------------- |
|
||
| `GET /api/health` | Dockerfile HEALTHCHECK / liveness | 返回 `{ status: 'ok', ts }` |
|
||
| `GET /api/ready` | K8s readinessProbe | 检查 `API_GATEWAY_URL` 可达 + MF remoteEntry 可达(Shell)+ 内存 < 阈值 |
|
||
|
||
### 7.5 灰度发布与回滚
|
||
|
||
| 场景 | 策略 |
|
||
| ----------- | ---------------------------------------------------------------------- |
|
||
| Remote 灰度 | remoteEntry.js 多版本并存,manifest 按用户分桶切换 |
|
||
| 回滚 | remoteEntry.js 保留 N 版,CDN 切上一版本;数据库无变更,无数据回滚风险 |
|
||
| 会话保持 | 回滚不强制登出,旧版本 Session 兼容(向前兼容设计) |
|
||
|
||
---
|
||
|
||
## 8. 测试策略细节(补 ai13 §3.8)
|
||
|
||
### 8.1 测试金字塔
|
||
|
||
| 层级 | 工具 | 覆盖目标 | 范围 |
|
||
| -------- | -------------------------------- | -------------------------------------------------- | ----------------------------------- |
|
||
| 单元 | Vitest + @testing-library/react | hooks ≥90% / utils ≥90% / 纯组件 ≥80% / pages ≥60% | Hook、工具函数、纯组件 |
|
||
| 集成 | Vitest + MSW | 关键链路 | ApiClient + TanStack Query + 权限流 |
|
||
| 契约 | Vitest + `@edu/mf-contracts` zod | 100% Shell 暴露 | Shell↔Remote 契约 |
|
||
| 视觉回归 | Playwright + Percy/Chromatic | 关键页面 | 设计令牌、布局 |
|
||
| E2E | Playwright | 4 端关键流 | 登录→操作→结果 |
|
||
| 性能 | Lighthouse CI | 每版本 | LCP/INP/CLS 回归 |
|
||
|
||
### 8.2 MF 集成测试
|
||
|
||
- Shell 单独测试:mock Remote 为 stub 组件
|
||
- Remote 单独测试:mock Shell 暴露的 Hook
|
||
- 契约测试:`@edu/mf-contracts` 携带 zod schema,CI 校验 Shell 暴露与 Remote 消费一致
|
||
- 跨端 E2E:Playwright 启动 Shell + 真实 Remote,覆盖"教师发作业→学生提交→家长查看"全链路
|
||
|
||
### 8.3 E2E 关键路径
|
||
|
||
| 路径 | 步骤 |
|
||
| ---------- | ------------------------------------------------- |
|
||
| 登录流 | 登录→侧边栏按角色渲染→dashboard |
|
||
| 班级 CRUD | 新建→列表→详情→编辑→删除 |
|
||
| 考试全链路 | 教师建考→发布→学生作答→教师批改→成绩发布→家长查看 |
|
||
| AI 出题 | 选知识点→AI 流式生成→教师审核→入库 |
|
||
| 权限降级 | 角色变更→视口收敛→路由重定向 |
|
||
|
||
### 8.4 CI 稳定策略
|
||
|
||
- E2E 并行分片(Playwright shard)
|
||
- 重试 2 次(flaky 容忍)
|
||
- 截图归档失败用例
|
||
- 独立测试 DB,每次重置
|
||
|
||
---
|
||
|
||
## 9. 移动端策略(补 ai13 §3.10)
|
||
|
||
### 9.1 响应式适配
|
||
|
||
| 端 | 适配 | 说明 |
|
||
| -------------- | ------------------ | --------------------------------- |
|
||
| teacher-portal | 响应式(桌面优先) | 教师主要用桌面,手机用于查看/通知 |
|
||
| student-portal | 移动优先 | 学生作答多用手机 |
|
||
| parent-portal | 移动优先 | 家长查看看手机 |
|
||
|
||
- Tailwind 断点:`sm 640 / md 768 / lg 1024 / xl 1280`
|
||
- AppShell 移动端折叠侧边栏为抽屉(汉堡菜单)
|
||
- 触摸目标 ≥ 44px(WCAG 2.5.5)
|
||
|
||
### 9.2 PWA / 离线(预留,补 ai13 §3.10)
|
||
|
||
| 能力 | 预留点 | 触发时机 |
|
||
| -------------- | -------------------------------------------------- | ---------------- |
|
||
| Service Worker | `sw.ts` 预留,next-pwa 接入 | 教师弱网批改需求 |
|
||
| 离线缓存 | TanStack Query `onlineManager` + mutation 离线队列 | 弱网 |
|
||
| 可安装 | manifest.json + installable | 学校要求桌面图标 |
|
||
| 后台同步 | Background Sync API | 离线提交作业 |
|
||
|
||
> 当前不实现,但 ApiClient 的 mutation 设计为可排队(失败入队列,上线重放)。
|
||
|
||
### 9.3 原生 App(远期,补 ai13 §3.10)
|
||
|
||
- hooks/contracts 纯 TS 不耦合 DOM,可被 React Native 复用
|
||
- 若未来出 RN App,复用 usePermission/useAuth/ApiClient,UI 层重写
|
||
- 不为此提前抽象,保持当前 Next.js 实现简洁(YAGNI)
|
||
|
||
---
|
||
|
||
## 10. 数据流与乐观更新(补 ai13 §3.12)
|
||
|
||
### 10.1 乐观更新模式
|
||
|
||
```typescript
|
||
const gradeMutation = useMutation({
|
||
mutationFn: (input) => api.post("/api/v1/grades", input),
|
||
onMutate: async (input) => {
|
||
await queryClient.cancelQueries({
|
||
queryKey: ["teacher", "grades", input.classId],
|
||
});
|
||
const previous = queryClient.getQueryData([
|
||
"teacher",
|
||
"grades",
|
||
input.classId,
|
||
]);
|
||
queryClient.setQueryData(["teacher", "grades", input.classId], (old) =>
|
||
old?.map((g) => (g.id === input.id ? { ...g, score: input.score } : g)),
|
||
);
|
||
return { previous };
|
||
},
|
||
onError: (err, input, context) => {
|
||
queryClient.setQueryData(
|
||
["teacher", "grades", input.classId],
|
||
context.previous,
|
||
); // 回滚
|
||
toast.error("保存失败,已回滚");
|
||
},
|
||
onSettled: (data, err, input) => {
|
||
queryClient.invalidateQueries({
|
||
queryKey: ["teacher", "grades", input.classId],
|
||
}); // 与服务端同步
|
||
},
|
||
});
|
||
```
|
||
|
||
### 10.2 缓存失效链路
|
||
|
||
mutate 一个资源后,需联动失效所有依赖它的缓存:
|
||
|
||
| mutation | 失效 queryKey |
|
||
| -------- | ------------------------------------------------------------------------------------------------------------------- |
|
||
| 创建班级 | `['teacher','classes']`、`['teacher','dashboard']` |
|
||
| 发布考试 | `['teacher','exams']`、`['teacher','classes',{id},'exams']` |
|
||
| 批改作业 | `['teacher','grades']`、`['teacher','homework',{id}]`、`['student','grades']`(跨端,走 WebSocket 推送 invalidate) |
|
||
| 角色变更 | `['session','effective-permissions']`、`['session','viewports']`、所有业务 query |
|
||
|
||
### 10.3 冲突解决
|
||
|
||
| 场景 | 策略 |
|
||
| ---------------------- | ------------------------------------------------------------------------ |
|
||
| 多教师同时批改同一作业 | 后端乐观锁(version 字段);前端 409 时重新拉取 + 提示"数据已被他人修改" |
|
||
| 学生重复提交 | 后端幂等(event_id 去重);前端禁用提交按钮 + loading |
|
||
| 缓存与服务端不一致 | staleTime 到期自动重取;关键操作后强制 invalidate |
|
||
|
||
---
|
||
|
||
## 11. MF 运维架构(补 ai13 §3.11)
|
||
|
||
### 11.1 Remote 版本与兼容性
|
||
|
||
| 维度 | 策略 |
|
||
| -------- | --------------------------------------------------------------------------------------- |
|
||
| 版本号 | Shell 与 Remote 各自 Semver;remoteEntry.js 路径带版本 `remoteEntry.{version}.js` |
|
||
| 契约版本 | Shell↔Remote 交互契约(暴露组件 props、共享 store shape)独立版本化 `@edu/mf-contracts` |
|
||
| 兼容范围 | Shell 声明 `peerDeps` 兼容的 Remote 主版本范围;不兼容版本走 fallback UI |
|
||
| 升级顺序 | 先升 shared deps(react 等)→ 再升 Remote → 最后升 Shell;每步灰度 |
|
||
| 回滚 | remoteEntry.js 保留 N 个历史版本,CI 一键切回上一版本 URL |
|
||
|
||
### 11.2 Remote 故障与降级
|
||
|
||
```mermaid
|
||
graph LR
|
||
A[Shell 加载 Remote] --> B{remoteEntry.js 可达?}
|
||
B -- 否 --> C[重试 3 次指数退避]
|
||
C --> D{仍失败?}
|
||
D -- 是 --> E[渲染 Fallback UI]
|
||
E --> F[提示模块暂不可用 + 重试按钮 + 通知运维]
|
||
B -- 是 --> G[执行 Remote 模块]
|
||
G --> H{运行时抛错?}
|
||
H -- 是 --> I[Remote 级 ErrorBoundary 捕获]
|
||
I --> E
|
||
H -- 否 --> J[正常渲染]
|
||
```
|
||
|
||
- 每个 Remote 用独立 `<ErrorBoundary fallback={...}>` 包裹,单 Remote 崩溃不带垮整个 Shell
|
||
- 动态 import 用 `@next/dynamic` + `ssr: false` + `loading` 占位
|
||
- Remote 不可用不阻断其他 Remote 与 Shell 自身路由
|
||
|
||
### 11.3 共享依赖演化
|
||
|
||
| 场景 | 处理 |
|
||
| -------------------------- | ------------------------------------------------------------------------------------- |
|
||
| React 18 → 19 大版本升级 | 全 4 端统一窗口期升级;过渡期 MF `shared.singleton` 容忍 minor 差异,major 不一致报错 |
|
||
| 新增共享依赖(如 zustand) | 先在 `@edu/mf-contracts` 登记 → Shell exposes shared → Remote 声明 shared |
|
||
| 移除共享依赖 | 先 Remote 移除引用 → 确认无 Remote 引用 → Shell 移除 shared |
|
||
| 版本漂移检测 | CI 校验 4 端 `package.json` 的 shared deps 版本一致(pnpm dedupe + 自定义检查) |
|
||
|
||
### 11.4 跨端类型安全
|
||
|
||
- Shell 通过 `@module-federation/typescript` 生成 `@mf-types/*`,Remote 消费 Shell 暴露组件的类型
|
||
- 运行时边界:跨端调用一律走 Hook(`useApi()`/`usePermission()`),不直接访问对方内部 store
|
||
- 契约测试:`@edu/mf-contracts` 携带类型 + 运行时 zod schema,Remote 接入 Shell 时校验
|
||
|
||
### 11.5 路由策略
|
||
|
||
| 关注点 | 方案 |
|
||
| ---------------- | ---------------------------------------------------------------------------------------------------- |
|
||
| 路由前缀 | `/teacher/*` `/student/*` `/parent/*` `/admin/*`(004 §5.4 场景域划分) |
|
||
| 深链直入 | URL `/student/exams/123` → Shell middleware 识别前缀 → 动态加载 student remote → remote 内部路由匹配 |
|
||
| 404 | Shell 全局 `not-found.tsx` + 各 Remote 内部 `not-found.tsx` |
|
||
| 路由守卫 | `middleware.ts` 统一鉴权:无 token → `/login`;权限不足当前前缀 → 重定向到对应 scope dashboard |
|
||
| 路由切换 loading | next.js `loading.tsx`(Suspense)骨架屏 + 滚动恢复 |
|
||
|
||
---
|
||
|
||
## 12. 远期演进铺垫
|
||
|
||
### 12.1 远期能力预留(不立即实现,但架构不堵死)
|
||
|
||
| 能力 | 预留点 | 触发时机 |
|
||
| ------------------- | ------------------------------------------------------------------ | ---------------- |
|
||
| PWA / 离线 | Service Worker 预留 `sw.ts`;TanStack Query onlineManager 离线队列 | 教师弱网批改需求 |
|
||
| 多租户 / SaaS | 前端按 `tenant_id` 注入;i18n glossary 支持租户覆写 | 多校部署 |
|
||
| Feature Flags | `packages/contracts/src/flags.ts` 统一枚举;middleware 注入 | 灰度发布 |
|
||
| 移动端复用 | hooks/contracts 纯 TS 可被 React Native 复用;不耦合 DOM | RN App |
|
||
| 插件化 | Shell 预留 Remote 注册表(动态 manifest),第三方 Remote 可挂载 | 开放生态 |
|
||
| Web Components 抽取 | ui-components 用 lit 封装跨框架版本(远期) | 跨技术栈 |
|
||
| A/B 测试 | edge middleware 分桶 + feature flag | 增长需求 |
|
||
|
||
### 12.2 ADR(架构决策记录)登记
|
||
|
||
本设计产生的关键决策应沉淀为 ADR 供未来回溯:
|
||
|
||
| ADR | 决策 | 状态 |
|
||
| ------- | --------------------------------------------------------- | ---------------------- |
|
||
| ADR-013 | Token 存储:localStorage 过渡 → 内存+httpOnly cookie 终态 | 拟采纳 |
|
||
| ADR-014 | MF 拓扑:单 Shell + 3 Remote | 已采纳(沿用 ADR-012) |
|
||
| ADR-015 | 状态管理 5 层模型 | 拟采纳 |
|
||
| ADR-016 | i18n 路由前缀式 + next-intl | 拟采纳 |
|
||
| ADR-017 | 性能预算 + bundle CI 门禁 | 拟采纳 |
|
||
| ADR-018 | MF Remote SRI + 最小暴露面 | 拟采纳 |
|
||
| ADR-019 | 多 Tab 会话同步 BroadcastChannel | 拟采纳 |
|
||
|
||
> ADR 正式登记由 coord 在 `docs/architecture/adr/` 维护,ai07 提请。
|
||
|
||
### 12.3 待 coord 协调问题
|
||
|
||
| # | 问题 | 影响 | 建议裁决 |
|
||
| --- | ------------------------------------------------------------------------------ | --------------------- | ---------------------------------------------------------------- |
|
||
| 1 | `full-stack-runbook.md` §2.1 端口矩阵未更新到 4 端(仍记 teacher-portal=3000) | 端口权威源不一致 | coord 更新 runbook 为 4000-4003 |
|
||
| 2 | packages 归属(ui-tokens/ui-components/hooks) | 维护边界 | ai07 维护前端专属包,coord 维护 shared-ts/contracts |
|
||
| 3 | ~~GraphQL vs REST(BFF 何时切 GraphQL)~~ **已裁决(F9 + 总裁 §0.3)** | 前端是否引入 urql | **P2 起 BFF 即 GraphQL,前端即 urql,无 REST 中间态**(见 §1.4) |
|
||
| 4 | admin-bff 是否存在(004 §4 未列出,但 admin-portal 消费 `/admin/*`) | admin-portal 契约缺失 | coord 确认 admin-bff 归属或由 iam 承载管理 API |
|
||
| 5 | shared deps 版本对齐 CI 校验机制 | MF singleton 安全 | coord 在 CI 增加 4 端 package.json diff 检查 |
|
||
| 6 | `@edu/mf-contracts` 包归属 | Shell↔Remote 契约载体 | 建议 coord 维护(跨 AI 共享) |
|
||
| 7 | Next.js 14→15 升级窗口 | App Router 破坏性变更 | P2 收尾统一升级,4 端同步 |
|
||
|
||
---
|
||
|
||
## 13. 覆盖度对照(ai13 审核 §3 遗漏逐项闭合)
|
||
|
||
| ai13 §3 遗漏项 | 本文档章节 | 状态 |
|
||
| -------------------------- | ---------------------- | ------- |
|
||
| §3.1 演进路线缺失 | §1 演进与迁移策略 | ✅ 闭合 |
|
||
| §3.2 安全架构缺失 | §2 安全架构 | ✅ 闭合 |
|
||
| §3.3 性能架构缺失 | §3 性能架构 | ✅ 闭合 |
|
||
| §3.4 可观测性深度缺失 | §4 可观测性深度 | ✅ 闭合 |
|
||
| §3.5 a11y 实施细节缺失 | §5 a11y 实施细节 | ✅ 闭合 |
|
||
| §3.6 i18n 实施细节缺失 | §6 i18n 实施细节 | ✅ 闭合 |
|
||
| §3.7 部署拓扑缺失 | §7 部署拓扑 | ✅ 闭合 |
|
||
| §3.8 测试策略细节缺失 | §8 测试策略细节 | ✅ 闭合 |
|
||
| §3.9 文件/目录结构缺失 | 02 §14(ai13 已补) | ✅ 闭合 |
|
||
| §3.10 移动端策略缺失 | §9 移动端策略 | ✅ 闭合 |
|
||
| §3.11 MF 演进策略缺失 | §1.3 + §11 MF 运维架构 | ✅ 闭合 |
|
||
| §3.12 数据流与乐观更新缺失 | §10 数据流与乐观更新 | ✅ 闭合 |
|
||
|
||
---
|
||
|
||
**AI Agent**: ai07(4 端合并设计 / 长远架构补全)
|
||
**Branch**: docs/teacher-portal-long-term-architecture-ai07
|
||
**Coordinator**: coord-ai
|
||
**Closes**: ai13 audit §3 all 12 items
|
||
|
||
---
|
||
|
||
## 14. 回写记录
|
||
|
||
### 14.1 ai13 回写(2026-07-09,批次 0.15 后,总裁裁决 §3.4 ISSUE-036-ai13)
|
||
|
||
**回写依据**:总裁裁决 §3.4 + §4.4 第 5 条(删除过渡方案本身,保留设计决策记录)+ coord F9(P2 起 BFF 用 GraphQL)。
|
||
|
||
**回写内容**:
|
||
|
||
| 位置 | 原内容 | 新内容 |
|
||
| ---------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
||
| §1.1 P2 行 | "统一 API 层" | "GraphQL client(urql,F9 裁决 P2 起)" |
|
||
| §1.2 表格 | "BFF 切 GraphQL(P4 起) + feature flag 灰度" | "BFF GraphQL(P2 起,已裁决) + 前端 P2 起即 urql" |
|
||
| §1.4 整节 | "REST → GraphQL 切换策略"(过渡方案:P2-P4 REST + P5+ 迁移 + feature flag 灰度) | "GraphQL 客户端架构(P2 起最终方案)"(client 选型/单例/初始化/错误处理/缓存策略/ISSUE 关联/设计决策记录) |
|
||
| §12.3 #3 | 待裁决"P2-P3 REST,P4 起 BFF 切 GraphQL" | 已裁决"P2 起 BFF 即 GraphQL,前端即 urql,无 REST 中间态" |
|
||
|
||
**未删除内容**(设计决策记录,按 §4.4 保留):
|
||
|
||
- §1.4.6 设计决策记录:明确记录原过渡方案已废弃 + 裁决来源 + 影响范围,供未来回溯
|
||
- §2.1 Token 存储演进(P2 localStorage → P6 httpOnly cookie):属独立决策(F12 裁决 P2 用 localStorage),非"REST→GraphQL 过渡方案",不在本次回写范围
|
||
|
||
**关联 ISSUE**:ISSUE-036-ai13(已通过本次回写闭合)
|
||
|
||
**AI Agent**: ai13(teacher-portal,回写执行)
|
||
**Branch**: feat/teacher-portal-docs-rewrite-issue036-ai13
|
||
**Coordinator**: coord-ai
|
||
**依据**: president-final-rulings.md §3.4 + §4.4
|