Files
Edu/apps/teacher-portal/README.md
SpecialX b53a486c6e docs(teacher-portal): ai07 阶段1+2 模块理解确认书与架构设计文档
4端合并交付:理解确认书+架构设计+MF Shell/Remote架构+4端差异化对照表

AI Agent: ai07 (4 portals)

Branch: docs/teacher-portal-stage1-stage2-design-ai07
2026-07-09 17:24:58 +08:00

66 KiB
Raw Blame History

teacher-portal / 4 端微前端架构设计

版本1.0 日期2026-07-09 AI Agentai07前端 4 端teacher-portal / student-portal / parent-portal / admin-portal 阶段:阶段 1理解确认书+ 阶段 2模块架构设计文档 关联文档:

本文档合并 4 端的设计,因 ai07 单一负责全部 4 端Module Federation shell + remote 架构需统一设计4 端共享组件库和权限体系。当前仓库仅 teacher-portal 已实现P1 测试页 + P2 骨架student/parent/admin-portal 待建。


目录

  1. 阶段 1模块理解确认书4 端)
  2. teacher-portal 现状审计(对齐黄金模板)
  3. 阶段 2模块架构设计文档
  4. 4 端差异化对比表
  5. 与其他模块的交互点(契约清单)
  6. 风险与假设
  7. coord 交叉审查所需信息

阶段 1模块理解确认书4 端)

1.1 我在架构中的位置

维度 teacher-portal student-portal parent-portal admin-portal
层级 L2 微前端层 L2 微前端层 L2 微前端层 L2 微前端层
MF 角色 Shell 宿主(主应用) Remote子应用 Remote子应用 Remote子应用
上游 浏览器(教师 / 教导主任 / 教研组长) 浏览器(学生) 浏览器(家长) 浏览器(系统/校管理员)
下游(同步) api-gatewayREST经 Next.js rewrites 代理) api-gateway api-gateway api-gateway
下游(推送) push-gatewayWebSocket/SSEP5 push-gateway push-gateway
BFF 对接 teacher-bffGraphQL Yoga + DataLoader student-bff parent-bff teacher-bff 复用 + iam 直连
通信方式 HTTP/REST前端→Gateway+ WebSocket前端→push-gateway 同左 同左 HTTP/REST

说明

  • 4 端均通过 next.config.jsrewrites/api/v1/* 代理到 api-gateway,前端不直连任何业务服务或 BFF 后端实例
  • MF 架构下4 端共享同一 Shellteacher-portal 作为 Shell 宿主),其余 3 端作为 Remote 子应用挂载Shell 提供 AppShell + 共享组件库 + 权限 Hook + API 请求层
  • 场景域 BFF 复用策略004 §5.4):教导主任/教研组长复用 teacher-portal + 额外管理视口,不单独建 portal

1.2 我的限界上下文

teacher-portal student-portal parent-portal admin-portal
业务领域 教学场景域 学习场景域 家长场景域 管理场景域
主要聚合 班级、考试、作业、成绩、备课、AI 出题 作答、作业提交、学情诊断、错题本 多子女切换、通知偏好、学情查看、成绩通知 用户/角色/权限/视口 CRUD、组织/班级管理、平台监控
不负责 学生作答界面、家长多子女切换 教师批改界面、AI 出题 教师沟通、学生作答 教学业务编排(归 teacher-portal
数据范围 DataScope L1-L5教师 L1 班级 / 教导主任 L2 年级 / 校管理员 L3 学校 / 区教研员 L4 / 系统管理员 L5 DataScope L0仅本人 DataScope L0仅子女 DataScope L3-L5校管理员 L3 / 系统管理员 L5

1.3 我与外部的契约

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

路径前缀 下游 BFF/服务 关键端点
teacher-portal /api/v1/iam/* iam POST /iam/loginPOST /iam/registerPOST /iam/refreshGET /iam/meGET /iam/rbac/...GET /iam/effective-permissions
teacher-portal /api/v1/teacher/* teacher-bff GET /teacher/viewportsGET /teacher/dashboardGET /teacher/classes/:id/examsGET /teacher/classes/:id/homeworkGET /teacher/exams/:id/grades
teacher-portal /api/v1/classes/* core-educlasses 模块) CRUD黄金模板
teacher-portal /api/v1/exams/* /api/v1/homework/* /api/v1/grades/* core-edu P3 教学核心
teacher-portal /api/v1/textbooks/* /api/v1/knowledge-points/* /api/v1/questions/* content P4 内容
teacher-portal /api/v1/ai/* aiSSE 流式) P5 AI 辅助出题
student-portal /api/v1/student/* student-bff GET /student/viewportsGET /student/dashboardGET /student/homeworkPOST /student/homework/:id/submitGET /student/diagnostic
parent-portal /api/v1/parent/* parent-bff GET /parent/viewportsGET /parent/childrenPOST /parent/switch-childGET /parent/notificationsPUT /parent/notification-preferences
admin-portal /api/v1/iam/*(管理用) iam 用户/角色/权限/视口 CRUD
admin-portal /api/v1/admin/* teacher-bff 复用 + iam 直连 平台监控、统计数据聚合
全部 /api/v1/notifications/* msg 通知中心P5

1.3.2 统一响应契约

所有后端响应遵循 ActionState 结构(迁移指南 §7.5

type ActionState<T> =
  | { success: true; data: T }
  | {
      success: false;
      error: { code: string; message: string; details?: unknown };
    };

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

1.3.3 推送契约P5

协议 场景
teacher-portal WebSocketpush-gateway 学生提交作业通知、考试成绩录入提醒、全校广播
student-portal WebSocket 考试发布通知、成绩发布、作业截止提醒
parent-portal WebSocket 子女成绩发布、教师沟通、学校通知
admin-portal 不消费推送(管理端用轮询)

1.4 我的技术栈

维度 选型 说明
框架 Next.js 14+App Router 4 端统一server components 默认client components 按需
语言 TypeScript 5.5+strict 沿用 tsconfig.base.json
微前端 Module Federation 2.0@module-federation/nextjs-mf teacher-portal = Shell其余 = Remote
样式 Tailwind CSS 3.4+ 配合设计令牌三层模型
UI 组件库 shadcn/ui迁移指南 §7.2 平移至 packages/ui-components/MF 共享
状态管理 L1 URL nuqs 可分享、可刷新状态
状态管理 L2 Server TanStack Query v5 服务端数据缓存、重试、乐观更新
状态管理 L3 Client Business Zustand slice 客户端业务状态
状态管理 L4 Global UI Zustand ui-store + ModalRoot 全局 UI 状态
状态管理 L5 Form react-hook-form + zodResolver 表单状态
富文本 Tiptap备课、出题、反馈 SSR 安全
图表 recharts 学情、Dashboard
i18n next-intl BFF/服务返回 i18n key + 参数,前端翻译
A11y eslint-plugin-jsx-a11yerror 级) WCAG 2.2 AA
字体 Intersans/ Frauncesserif/ JetBrains Monomono next/font/google 加载CSS 变量暴露

1.5 我的阶段归属

阶段 当前状态 依赖上游阶段
teacher-portal P2 已实现 P1 测试页 + P2 骨架(登录/AppShell/Dashboard/classes CRUD⚠️ 待审计对齐黄金模板 + 引入 MF + 共享组件库 P1api-gateway + classes + iam
student-portal P3 📐 需设计(待 core-edu + student-bff 就绪) P3core-edu + student-bff
parent-portal P4 📐 需设计(待 parent-bff + data-ana 就绪) P4parent-bff + data-ana
admin-portal P6 📐 需设计(待全部业务服务稳定) P6硬化阶段

1.6 黄金模板对齐清单(对照 classes 服务)

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

对齐项 classes后端黄金模板 teacher-portal 前端等价 当前状态
权限校验 @RequirePermission(Permissions.XXX) usePermission().hasPermission("XXX") Hook + <RequirePermission> 组件 缺失,直接硬编码 user.roles.join(", ")
错误码前缀统一 CLASSES_*IAM_* API 请求层根据 error.code 前缀路由 i18n 缺失统一请求层
logger pino 前端 console + SentryP6 ⚠️ 仅 console.error
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 ⚠️ Dockerfile 有 HEALTHCHECK无 /api/health
优雅关闭 SIGTERM handler Next.js 无长连接,无需 N/A
测试覆盖率 ≥ 80% Vitest Vitest + @testing-library/react + Playwright E2E 0%
Dockerfile 多阶段构建 builder + runtime 已有多阶段 已对齐
Zod 输入验证 class-validator + Zod schema react-hook-form + zodResolver 缺失
GlobalErrorFilter NestJS 全局异常过滤器 React ErrorBoundary + API 请求层统一错误处理 缺失
设计令牌三层 primitive.css / semantic-light/dark.css / tailwind-theme.css 硬编码在 globals.css + tailwind.config.js
A11y 工具集 useA11yId / mergeA11yProps / describeInput / focus-trap 缺失

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

2.1 审计表

维度 状态 说明
权限装饰器(前端等价 usePermission AppShell.tsx 直接 user.roles.join(", "),违反 project_rules §3.8
错误码前缀 无统一 API 请求层,错误处理散落在每个 page.tsx
logger ⚠️ console.error,无结构化、无 trace_id
metrics 无 Web Vitals 采集
tracer 无 OTel browser SDK
/healthz ⚠️ Dockerfile 有 HEALTHCHECK wget /,但无 /api/health route
/readyz
优雅关闭 N/A Next.js 无长连接
测试覆盖率 0%,无测试文件
Dockerfile 多阶段 builder + runtime非 root 用户HEALTHCHECK
Zod 输入验证 表单直接 useState无 zodResolver
GlobalErrorFilterErrorBoundary 无 React ErrorBoundary
设计令牌三层 硬编码在 globals.css:root 变量)+ tailwind.config.jshex 字面量)
A11y 工具集 无 useA11yId、focus-trap 等
Module Federation 配置 next.config.js 仅有 rewrites无 MF
5 层状态管理 仅 useState + localStorage无 nuqs/TanStack Query/Zustand
共享组件库 仅 AppShell无 ErrorBoundary/Loading/Empty/RequirePermission
i18n 中文硬编码在 JSX
API 请求层 每页重复 fetch + authHeaders + try/catch
ESLint flat config 自定义规则 未配置 no-hardcoded-fonts / design-tokens 规则

2.2 现有文件清单

apps/teacher-portal/
├─ src/
│  ├─ app/
│  │  ├─ (app)/                    # 受保护路由组(套 AppShell
│  │  │  ├─ classes/page.tsx       # 班级 CRUDP1 测试页)
│  │  │  ├─ dashboard/page.tsx     # 教师仪表盘
│  │  │  ├─ exams/page.tsx         # 考试列表
│  │  │  ├─ grades/page.tsx        # 成绩查询
│  │  │  ├─ homework/page.tsx      # 作业列表
│  │  │  └─ layout.tsx             # 套 AppShell
│  │  ├─ login/page.tsx            # 登录页(不套壳)
│  │  ├─ globals.css               # 全局样式 + 设计令牌(硬编码)
│  │  ├─ layout.tsx                # 根布局(字体加载)
│  │  └─ page.tsx                  # 根路径重定向
│  ├─ components/
│  │  └─ AppShell.tsx              # 左侧栏 + 主内容区
│  └─ lib/
│     └─ auth.ts                   # token + userInfo localStorage 管理
├─ Dockerfile                      # 多阶段构建 ✅
├─ next.config.js                  # 仅 rewrites无 MF ❌
├─ package.json                    # 仅 next/react/react-dom无 MF/Query/Zustand ❌
├─ tailwind.config.js              # 硬编码 hex ❌
├─ postcss.config.js
└─ tsconfig.json

2.3 主要违规点(必须在 P2 收尾或 P3 起步时修复)

  1. 权限硬编码AppShell.tsx:143 user.roles.join(", ") 违反 project_rules §3.8,必须改为 usePermission().hasPermission()
  2. 设计令牌硬编码globals.css:6-13tailwind.config.js:7-19 出现 hsl(...) 字面量与 'Fraunces'/'Inter' 字面量,违反 project_rules §3.10
  3. 无统一 API 请求层4 个 page.tsx 重复 authHeaders() + fetch + try/catch + setError,必须抽取到 lib/api.ts
  4. 无权限 Hook:缺少 usePermission().hasPermission(),无法做 L3 组件级视口控制
  5. 无 ErrorBoundaryReact 渲染异常会白屏
  6. 无 5 层状态管理:登录态用 localStorageL3但无 TanStack QueryL2导致每页重复 fetch
  7. 字体名硬编码layout.tsx:3-7 直接 import Inter/Fraunces/JetBrains_Mono,应改为 var(--font-family-sans/serif/mono)

阶段 2模块架构设计文档

3.1 模块内部分层图4 端统一 MF 架构)

graph TB
    subgraph Browser["浏览器"]
        URL[URL 路由]
    end

    subgraph Shell["teacher-portalShell 宿主)"]
        AppShell[AppShell<br/>左栏导航 + 主内容区]
        RootLayout[RootLayout<br/>字体/令牌/i18n Provider]
        Router[Next.js App Router]
        SharedDeps["共享依赖暴露<br/>react/react-dom/@tanstack/react-query/zustand/nuqs"]
    end

    subgraph RemoteTeacher["teacher-portal Remote 模块"]
        TeacherPages[教学场景页面<br/>dashboard/classes/exams/homework/grades/ai-assist]
    end

    subgraph RemoteStudent["student-portalRemote"]
        StudentPages[学习场景页面<br/>dashboard/homework/submit/diagnostic/exam-taking]
    end

    subgraph RemoteParent["parent-portalRemote"]
        ParentPages[家长场景页面<br/>dashboard/children-switch/grades/notifications]
    end

    subgraph RemoteAdmin["admin-portalRemote"]
        AdminPages[管理场景页面<br/>users/roles/permissions/viewports/monitoring]
    end

    subgraph Shared["共享层packages/"]
        UITokens[ui-tokens<br/>三层设计令牌]
        UIComponents[ui-components<br/>shadcn + A11y + ErrorBoundary]
        Contracts[contracts<br/>Permissions 常量 + 类型]
        Hooks[hooks<br/>usePermission/useAuth/useA11y]
        LibTS[shared-ts<br/>通用工具]
    end

    subgraph Gateway["api-gateway"]
        GW[Gin 路由/鉴权/限流]
    end

    Browser --> URL
    URL --> RootLayout
    RootLayout --> AppShell
    AppShell --> Router
    Router -->|动态加载| RemoteTeacher
    Router -->|动态加载| RemoteStudent
    Router -->|动态加载| RemoteParent
    Router -->|动态加载| RemoteAdmin

    RemoteTeacher --> SharedDeps
    RemoteStudent --> SharedDeps
    RemoteParent --> SharedDeps
    RemoteAdmin --> SharedDeps

    Shell --> UITokens
    Shell --> UIComponents
    Shell --> Contracts
    Shell --> Hooks
    RemoteTeacher --> UITokens
    RemoteStudent --> UITokens
    RemoteParent --> UITokens
    RemoteAdmin --> UITokens

    AppShell -->|fetch /api/v1/iam/effective-permissions| Hooks
    Hooks -->|透传 token| GW
    RemoteTeacher -->|fetch /api/v1/teacher/*| GW
    RemoteStudent -->|fetch /api/v1/student/*| GW
    RemoteParent -->|fetch /api/v1/parent/*| GW
    RemoteAdmin -->|fetch /api/v1/iam/* + /api/v1/admin/*| GW

3.1.1 MF 拓扑选型

方案 选否 理由
4 端独立部署 + 独立域名 + 各自 Shell 4 套 Shell 重复,登录态/权限/组件库要重复实现
单 Shell + 4 Remote采用 teacher-portal 作为 Shell 宿主,提供 AppShell + 共享依赖;其余 3 端作为 Remote 动态加载
单一 Next.js 应用 + 4 路由组 违反"微前端独立部署"目标ADR-012

Shell 职责

  • RootLayout字体、设计令牌、i18n Provider、TanStack QueryClientProvider、Zustand StoreProvider
  • AppShell左侧导航 + 主内容区 + 用户信息 + 登出)
  • 共享依赖暴露react、react-dom、@tanstack/react-query、zustand、nuqs、ui-components、ui-tokens、contracts、hooks
  • 路由表4 端路由前缀:/teacher/*/student/*/parent/*/admin/*
  • 登录页(统一登录入口,按角色重定向到对应 portal

Remote 职责

  • 各场景域页面page.tsx
  • 各场景域专属组件
  • 各场景域专属 Zustand slice
  • 通过 MF 共享 Shell 暴露的依赖,避免重复加载

3.1.2 MF 配置next.config.js

// teacher-portal/next.config.jsShell
const NextFederationPlugin = require("@module-federation/nextjs-mf");

const remotes = (isServer) => ({
  student: `student_app@http://localhost:3001/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
  parent: `parent_app@http://localhost:3002/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
  admin: `admin_app@http://localhost:3003/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
});

module.exports = {
  reactStrictMode: true,
  webpack(config, { isServer }) {
    config.plugins.push(
      new NextFederationPlugin({
        name: "teacher_app",
        filename: "static/chunks/remoteEntry.js",
        remotes: remotes(isServer),
        exposes: {
          "./AppShell": "./src/components/AppShell",
          "./shared-deps": "./src/shared/deps",
        },
        shared: {
          react: { singleton: true, requiredVersion: "^18.3.0" },
          "react-dom": { singleton: true, requiredVersion: "^18.3.0" },
          "@tanstack/react-query": { singleton: true },
          zustand: { singleton: true },
          nuqs: { singleton: true },
        },
        extraOptions: { exposePages: false },
      }),
    );
    return config;
  },
  async rewrites() {
    return [
      {
        source: "/api/v1/:path*",
        destination: `${process.env.API_GATEWAY_URL || "http://localhost:8080"}/api/v1/:path*`,
      },
    ];
  },
};

Remote 端配置对称:name: 'student_app'exposes: { './pages': './src/pages' }remotes: { teacher: 'teacher_app@...' }

3.2 领域模型(前端视角)

前端不持有业务聚合根,仅持有"视图模型"ViewModel和"会话状态"。

3.2.1 会话状态Session

interface Session {
  user: UserInfo; // { id, email, name, roles, permissions, dataScope }
  tokens: { accessToken: string; refreshToken: string };
  viewports: ViewportItem[]; // L1 导航视口
  expiresAt: number; // access token 过期时间戳
}

存储Zustand sessionSliceL3+ localStorage 持久化(刷新恢复)+ TanStack Query 缓存 ['session']L2

3.2.2 视口模型Viewport

interface ViewportItem {
  key: string; // 'dashboard' | 'classes' | ...
  label: string; // i18n key 或显式文案
  route: string; // '/teacher/dashboard'
  icon: string | null; // 图标 key按需
  sortOrder: number; // 排序
  requiredPermission: string | null; // 'CLASSES_READ' 等
  scope: "teacher" | "student" | "parent" | "admin"; // 标记归属哪个 portal
}

来源:GET /api/v1/{scope}/viewportsBFF 聚合 iam 视口配置。AppShell 按 scope 过滤渲染对应 portal 的导航。

3.2.3 权限模型Permission

interface PermissionState {
  permissions: string[]; // ['CLASSES_READ', 'EXAMS_CREATE', ...]
  dataScope: DataScope; // L0-L5
  hasPermission: (perm: string) => boolean;
  hasAnyPermission: (perms: string[]) => boolean;
  hasAllPermissions: (perms: string[]) => boolean;
}

来源:GET /api/v1/iam/effective-permissions{ permissions, viewports, dataScope }。Redis 缓存 5miniam 侧),前端 TanStack Query 缓存 5min角色变更主动 invalidate。

3.3 数据模型(前端)

前端无数据库,仅有缓存层:

数据类型 存储 TTL 失效策略
Sessiontoken + user localStorage + Zustand access 15min / refresh 7day 401 自动 refreshrefresh 失败跳登录
权限列表 TanStack Query cache 5min 角色变更事件 invalidate
视口列表 TanStack Query cache 5min 同上
班级/年级列表 TanStack Query cache 5min staleTime 5minmutation 后 invalidate
教学资源详情 TanStack Query cache 30s staleTime 30s
学情宽表 TanStack Query cache 30s staleTime 30s实时性由 BFF 决定)
URL 状态(分页/筛选) nuqs 永久(可分享)
表单临时态 react-hook-form 卸载即销毁

3.4 API 设计(前端 → 后端)

前端不设计后端 API仅声明消费的端点。详见 §1.3.1。

3.4.1 统一 API 请求层lib/api.ts

// packages/shared-ts/src/api-client.ts共享
interface ApiClientOptions {
  baseUrl?: string; // 默认 ''(走 Next.js rewrites
  getToken?: () => string | null;
  onUnauthorized?: () => void; // 401 → refresh → 重试 / 跳登录
  onError?: (error: ApiError) => void; // 全局 toast
}

class ApiClient {
  async get<T>(path: string, query?: Record<string, string>): Promise<T>;
  async post<T>(path: string, body: unknown): Promise<T>;
  async put<T>(path: string, body: unknown): Promise<T>;
  async delete<T>(path: string): Promise<T>;
  async sse<T>(path: string, body: unknown): AsyncIterable<T>; // AI 流式
}

// 错误结构
interface ApiError {
  code: string; // 'IAM_INVALID_CREDENTIALS'
  message: string; // 已 i18n 翻译或后端原文
  details?: unknown;
  httpStatus: number;
}

职责

  • 自动注入 Authorization: Bearer ${token}
  • 401 自动 refresh token 一次,失败调 onUnauthorized
  • 解析 ActionStatesuccess=false 抛 ApiError
  • error.code 前缀路由 i18n key
  • 全局错误 toast除 401
  • 请求/响应 trace_id 透传(从响应头 X-Request-Id 提取)

3.4.2 TanStack Query 约定

// Query Key 命名:[scope, resource, ...args]
queryKey: ["teacher", "classes", { gradeId }];
queryKey: ["teacher", "exams", classId];
queryKey: ["session", "effective-permissions"];
queryKey: ["session", "viewports", "teacher"];

// Mutation 约定
const mutation = useMutation({
  mutationFn: (input) => api.post("/api/v1/classes", input),
  onSuccess: () =>
    queryClient.invalidateQueries({ queryKey: ["teacher", "classes"] }),
  onError: (e: ApiError) => toast.error(e.message),
});

3.5 事件设计

前端不发布 Kafka 事件,仅消费 WebSocket 推送P5和 Server-Sent EventsAI 流式)。

3.5.1 WebSocket 推送P5

事件 触发 前端动作
NotificationRequested msg 服务投递 toast 提示 + 通知中心未读数 +1
ExamPublished 教师发布考试 学生端 toast + dashboard invalidate
GradeRecorded 教师录入成绩 学生/家长端 toast + 成绩列表 invalidate
HomeworkSubmitted 学生提交作业 教师端 toast + 作业批改列表 invalidate

3.5.2 SSE 流式P5 AI 辅助出题)

GET /api/v1/ai/generate-questions (SSE)
data: {"delta": "题目"}\n\n
data: {"delta": "A. option1"}\n\n
data: {"done": true}\n\n

前端用 AsyncIterable<T> 消费Tiptap 逐字插入。

3.6 横切关注点对齐清单

3.6.1 权限(前端等价)

路由 requiredPermission
teacher-portal /teacher/dashboard TEACHER_DASHBOARD_VIEW
teacher-portal /teacher/classes CLASSES_READ
teacher-portal /teacher/classes/new CLASSES_CREATE
teacher-portal /teacher/exams EXAMS_READ
teacher-portal /teacher/exams/new EXAMS_CREATE
teacher-portal /teacher/homework HOMEWORK_READ
teacher-portal /teacher/homework/:id/grade HOMEWORK_GRADE
teacher-portal /teacher/grades GRADES_READ
teacher-portal /teacher/ai-assist AI_GENERATE
student-portal /student/dashboard STUDENT_DASHBOARD_VIEW
student-portal /student/homework HOMEWORK_READ_OWN
student-portal /student/homework/:id/submit HOMEWORK_SUBMIT
student-portal /student/diagnostic DIAGNOSTIC_READ_OWN
parent-portal /parent/dashboard PARENT_DASHBOARD_VIEW
parent-portal /parent/children PARENT_CHILDREN_VIEW
parent-portal /parent/grades GRADES_READ_CHILD
admin-portal /admin/users IAM_USER_READ
admin-portal /admin/users/new IAM_USER_CREATE
admin-portal /admin/roles IAM_ROLE_READ
admin-portal /admin/permissions IAM_PERMISSION_READ
admin-portal /admin/viewports IAM_VIEWPORT_READ
admin-portal /admin/monitoring ADMIN_MONITORING_VIEW

完整权限点常量集中在 packages/contracts/src/permissions.ts待建立coord 负责 shared-tsai07 负责调用。L3 组件级视口用 <RequirePermission perm="EXAMS_CREATE"><Button>新建考试</Button></RequirePermission>

3.6.2 错误码清单(前端 i18n 路由)

前缀 来源服务 i18n key 模式
IAM_* iam iam.error.{{code}}
CORE_EDU_* core-edu coreEdu.error.{{code}}
CLASSES_* core-edu/classes classes.error.{{code}}
CONTENT_* content content.error.{{code}}
MSG_* msg msg.error.{{code}}
AI_* ai ai.error.{{code}}
BFF_* teacher/student/parent-bff bff.error.{{code}}
GW_* api-gateway gateway.error.{{code}}
NETWORK_* 前端网络层 network.error.{{code}}

3.6.3 Logger

// packages/shared-ts/src/logger.ts
interface Logger {
  info(msg: string, meta?: Record<string, unknown>): void;
  warn(msg: string, meta?: Record<string, unknown>): void;
  error(msg: string, meta?: Record<string, unknown>): void;
}

// 实现:开发环境 console + 结构化;生产环境 → SentryP6
// 必含字段trace_id从响应头提取、user_id、scope、path

3.6.4 MetricsWeb Vitals

指标 类型 上报
teacher_portal_lcp_seconds LCP next/web-vitalsPOST /api/v1/admin/web-vitals
teacher_portal_cls CLS 同上
teacher_portal_fid_seconds FID 同上
teacher_portal_ttfb_seconds TTFB 同上

P6 接入P2-P5 暂缓。

3.6.5 TracerOTel browser SDKP6

// packages/shared-ts/src/tracer.ts
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
// BatchSpanProcessor → OTLP exporter → collector → Tempo
// 自动埋点fetch、XMLHttpRequest、document load、user interaction

3.6.6 健康检查

端点 用途 实现
GET /api/health Dockerfile HEALTHCHECK Next.js Route Handler返回 { status: 'ok', ts: Date.now() }
GET /api/ready K8s readinessProbe 检查 process.env.API_GATEWAY_URL 可达 + 内存 < 阈值

3.6.7 优雅关闭

Next.js 无长连接(除 SSE/WS无需特殊处理。SSE/WS 在 P5 由 push-gateway 管理,前端断线自动重连。

3.7 共享组件库packages/ui-components/,待建立)

组件 用途 来源
AppShell 左侧栏 + 主内容区布局 teacher-portal 现有 → 抽取共享
RequirePermission L3 组件级视口控制(无权限不渲染 children 新建
ErrorBoundary React 渲染异常兜底fallback UI 新建
Loading 骨架屏Skeleton 新建
Empty 空态(插画 + 文案 + CTA 新建
Modal / Dialog 全局 ModalModalRoot + Zustand ui-store shadcn/ui
Toast 全局 toast错误/成功/警告) shadcn/ui sonner
Button / Input / Select / Textarea 基础表单 shadcn/ui
DataTable 表格(排序/分页/筛选) shadcn/ui + TanStack Table
Chart 图表封装recharts 新建
A11y 工具集 useA11yId / mergeA11yProps / describeInput / focus-trap / skip-link / visually-hidden / aria-status 迁移指南 §7.7
Form react-hook-form + zodResolver 封装 新建

3.8 共享 Hookspackages/hooks/,待建立)

Hook 职责
useAuth() 会话状态user/token/refresh/login/logout
usePermission() 权限查询hasPermission/hasAny/hasAll + dataScope
useViewports(scope) 视口列表(按 scope 过滤)
useApi() ApiClient 实例(注入 token + 401 处理)
useA11yId() 唯一 ARIA ID 生成
useAriaLive() aria-live 区域管理
useToast() 全局 toastZustand ui-store

3.9 设计令牌三层packages/ui-tokens/,待建立)

packages/ui-tokens/
├─ primitive.css              # Layer 1 原始色板/字号/间距/阴影
├─ semantic-light.css         # Layer 2 语义令牌(亮色)
├─ semantic-dark.css          # Layer 2 语义令牌(暗色)
├─ tailwind-theme.css         # Layer 3 @theme inline 暴露 bg-*/text-*/font-*
└─ package.json

强制规则project_rules §3.10

  • 禁止 #hex 字面量ESLint no-restricted-syntax
  • 禁止 'Inter'/'Fraunces'/'JetBrains Mono' 字面量ESLint design-tokens/no-hardcoded-fonts
  • 禁止 font-size: Npx(用 var(--font-size-1~9)
  • 禁止 Tailwind 任意值 w-[Npx](用 --space-* 或默认阶梯)

令牌命名(迁移指南 §7.1

Layer 1 Primitive Layer 2 Semantic Layer 3 Tailwind
--color-blue-500 --color-accent bg-accent
--font-size-3 --font-size-body text-body
--space-4 --space-md p-md

4 端差异化对比表

4.1 整体差异

维度 teacher-portal student-portal parent-portal admin-portal
MF 角色 Shell + Remote Remote Remote Remote
路由前缀 /teacher/* /student/* /parent/* /admin/*
端口dev 3000 3001 3002 3003
对接 BFF teacher-bff student-bff parent-bff teacher-bff 复用 + iam 直连
默认角色 teacher / head_teacher / grade_director / subject_leader student parent school_admin / system_admin
DataScope 默认 L1-L5按角色 L0 L0 L3-L5
推送消费 WebSocket WebSocket WebSocket 轮询
AI 辅助 出题/备课/分析
富文本编辑 Tiptap备课/出题/反馈)
多子女切换
用户管理
角色权限配置
平台监控

4.2 L1 导航菜单差异

菜单项(视口)
teacher-portal Dashboard、班级管理、考试管理、作业管理、成绩查询、备课P5、AI 辅助P5、知识图谱P4
student-portal Dashboard、我的作业、我的考试、学情诊断P4、错题本P4、通知中心P5
parent-portal Dashboard、子女切换、成绩查看、作业查看、通知中心P5、通知偏好设置
admin-portal Dashboard、用户管理、角色管理、权限管理、视口配置、组织管理、平台监控

4.3 L2 路由表差异

teacher-portal

路由 页面 权限
/teacher/dashboard 教师仪表盘 TEACHER_DASHBOARD_VIEW
/teacher/classes 班级列表 CLASSES_READ
/teacher/classes/:id 班级详情 CLASSES_READ
/teacher/classes/new 新建班级 CLASSES_CREATE
/teacher/exams 考试列表 EXAMS_READ
/teacher/exams/:id 考试详情 EXAMS_READ
/teacher/exams/new 新建考试 EXAMS_CREATE
/teacher/homework 作业列表 HOMEWORK_READ
/teacher/homework/:id/grade 批改作业 HOMEWORK_GRADE
/teacher/grades 成绩查询 GRADES_READ
/teacher/lesson-prep 备课P5 LESSON_PREP_VIEW
/teacher/ai-assist AI 辅助P5 AI_GENERATE
/teacher/knowledge-graph 知识图谱P4 CONTENT_READ

student-portal

路由 页面 权限
/student/dashboard 学生仪表盘 STUDENT_DASHBOARD_VIEW
/student/homework 我的作业 HOMEWORK_READ_OWN
/student/homework/:id/submit 提交作业 HOMEWORK_SUBMIT
/student/exams 我的考试 EXAMS_READ_OWN
/student/exams/:id/take 作答考试 EXAMS_TAKE
/student/diagnostic 学情诊断P4 DIAGNOSTIC_READ_OWN
/student/weakness 错题本P4 WEAKNESS_READ_OWN
/student/notifications 通知中心P5 NOTIFICATION_READ_OWN

parent-portal

路由 页面 权限
/parent/dashboard 家长仪表盘 PARENT_DASHBOARD_VIEW
/parent/children 子女列表 PARENT_CHILDREN_VIEW
/parent/grades 子女成绩 GRADES_READ_CHILD
/parent/homework 子女作业 HOMEWORK_READ_CHILD
/parent/notifications 通知中心P5 NOTIFICATION_READ_OWN
/parent/preferences 通知偏好 PARENT_PREFERENCES_UPDATE

admin-portal

路由 页面 权限
/admin/dashboard 管理仪表盘 ADMIN_DASHBOARD_VIEW
/admin/users 用户管理 IAM_USER_READ
/admin/users/new 新建用户 IAM_USER_CREATE
/admin/users/:id 用户编辑 IAM_USER_UPDATE
/admin/roles 角色管理 IAM_ROLE_READ
/admin/permissions 权限管理 IAM_PERMISSION_READ
/admin/viewports 视口配置 IAM_VIEWPORT_READ
/admin/organization 组织管理 ORG_MANAGE
/admin/monitoring 平台监控 ADMIN_MONITORING_VIEW

4.4 L3 组件级差异

组件 teacher student parent admin
AppShell(左栏+主区) (复用 Shell (复用 Shell (复用 Shell
RequirePermission
ErrorBoundary
Loading / Empty
DataTable (班级/考试列表) (作业列表) (成绩列表) (用户列表)
Form (创建班级/考试) (提交作业) (通知偏好) (用户/角色 CRUD
Chart (班级成绩分布) (个人学情趋势) (子女成绩趋势) (平台监控)
RichTextEditorTiptap (备课/出题/反馈)
ChildSwitcher
ExamTaking(倒计时+自动保存)
SSEViewerAI 流式)
UserManagementTable
RolePermissionMatrix
ViewportConfigEditor
PlatformMonitorGrafana embed

4.5 L4 数据层差异

主要数据来源 缓存策略
teacher-portal teacher-bff聚合 iam + core-edu + content + data-ana 5-30s 短缓存
student-portal student-bff聚合 iam + core-edu + data-ana 5-30s 短缓存,作业列表 30s
parent-portal parent-bff聚合 iam + core-edu + data-ana含子女关联 5-30s 短缓存,子女切换 invalidate
admin-portal iam 直连 + teacher-bff 复用 5min 长缓存(管理数据低频变)

与其他模块的交互点(契约清单)

方向 对方服务 协议 接口/事件 用途 阶段
调用 api-gateway HTTP/REST /api/v1/* 代理 全部业务请求 P1+
调用 push-gateway WebSocket ws://push-gateway/ws 实时推送 P5
调用 ai SSE GET /api/v1/ai/generate-questions AI 流式出题 P5
被调用 前端不暴露接口给其他服务
消费 teacher-bff HTTP经 Gateway GET /teacher/viewports 教师场景聚合 P2+
消费 student-bff HTTP经 Gateway GET /student/viewports 学生场景聚合 P3+
消费 parent-bff HTTP经 Gateway GET /parent/viewports 家长场景聚合 P4+
消费 iam HTTP经 Gateway /iam/* 登录/权限/视口/用户管理 P2+
消费 core-edu HTTP经 Gateway /classes/* /exams/* /homework/* /grades/* 教学核心 P2+
消费 content HTTP经 Gateway /textbooks/* /knowledge-points/* /questions/* 内容资源 P4+
消费 data-ana HTTP经 Gateway /analytics/* 学情分析 P4+
消费 msg HTTP经 Gateway /notifications/* 通知中心 P5+
依赖 coord 维护 packages/shared-proto TS 类型(仅 contracts 部分) P1+
依赖 coord 维护 packages/shared-ts(待建) ApiClient/Logger/通用工具 P2+
依赖 ai07 维护 packages/ui-tokens(待建) 三层设计令牌 P2+
依赖 ai07 维护 packages/ui-components(待建) shadcn + 共享组件 P2+
依赖 ai07 维护 packages/hooks(待建) usePermission/useAuth 等 P2+
依赖 coord 维护 packages/contracts(待建) Permissions 常量 + 类型 P2+

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


风险与假设

8.1 假设

  1. 假设 coord 建立 packages/shared-tspackages/contracts:包含 ApiClient、Logger、Permissions 常量、通用类型。若 coord 未建立ai07 自行在 apps/teacher-portal/src/shared/ 内实现,后续提取到 packages。
  2. 假设 ai02 iam 提供 GET /iam/effective-permissions:返回 { permissions, viewports, dataScope }。当前已实现known-issues §2.3 iam
  3. 假设 ai03 teacher-bff 提供 GET /teacher/viewports:返回 L1 导航视口。当前已实现。
  4. 假设 ai03 core-edu classes 模块维持 ActionState 响应结构:前端 API 请求层依赖此契约。
  5. 假设 Next.js 14+ Module Federation 2.0 稳定@module-federation/nextjs-mf 在 Next.js App Router 下可用。若不稳定,降级为 4 端独立部署 + 各自 Shell重复实现 AppShell

8.2 技术风险

风险 影响 缓解
MF SSR 对齐复杂 Remote 在 SSR 时需 Shell 提供上下文 优先 CSRSSR 仅用于首屏 dashboardMF 2.0 支持 SSR
共享依赖版本漂移 Remote 与 Shell 的 react/react-dom 版本不一致导致运行时错误 MF shared.singleton: true + CI 检查版本对齐
Token 刷新竞态 多请求同时 401 触发多次 refresh ApiClient 全局单例 + refresh promise 复用
权限缓存陈旧 角色变更后前端 5min 内仍用旧权限 iam 角色变更发 Kafka 事件 → msg 推送 WebSocket → 前端 invalidate
设计令牌迁移破坏现有样式 teacher-portal 现有硬编码令牌迁移到三层模型后样式漂移 灰度迁移:先建 ui-tokens 包teacher-portal 引入但不删除旧 globals.css验证后切换
4 端独立部署运维成本 4 个 Next.js 实例 = 4 倍内存 Shell + 3 Remote 共享 node_modulesMF 运行时共享),实际内存增量 < 2x
TanStack Query 缓存膨胀 长时间使用后缓存项过多 gcTime 5min + staleTime 按数据类型分级

8.3 未决设计决策(需 coord 仲裁)

  1. packages 归属ui-tokens / ui-components / hooks 是 ai07 维护还是 coord 维护建议ai07 维护前端专属coord 仅维护 shared-ts / contracts(跨语言/跨服务)。
  2. GraphQL vs REST004 §11.3 提到 BFF GraphQL Yoga + DataLoader但当前 teacher-bff 实现为 REST。前端 API 请求层是否需要 GraphQL clienturql/apollo建议P2-P3 用 RESTP4 起若 BFF 切 GraphQL 再引入 urql。
  3. i18n key 命名iam.error.IAM_INVALID_CREDENTIALS 还是 error.iam.invalid_credentials?建议:error.{{service}}.{{code_snake_case}},与错误码前缀对齐。
  4. MF 暴露粒度Shell 暴露整个 AppShell 还是暴露更细粒度的组件Sidebar、Header、Content建议暴露 AppShell 整体 + 各 Remote 自行决定内部布局。

coord 交叉审查所需信息

9.1 端口矩阵4 端)

dev 端口 生产端口 备注
teacher-portal 3000 3000 Shell 宿主
student-portal 3001 3001 Remote
parent-portal 3002 3002 Remote
admin-portal 3003 3003 Remote

full-stack-runbook 端口矩阵对齐3000-3003 前端3001-3003 已被 Grafana3030/其他服务避让。

9.2 依赖的共享包(需 coord 建立)

路径 维护方 内容
shared-ts packages/shared-ts/ coord ApiClient、Logger、通用工具
contracts packages/contracts/ coord Permissions 常量、ActionState 类型、UserInfo 类型
ui-tokens packages/ui-tokens/ ai07建议 三层设计令牌
ui-components packages/ui-components/ ai07建议 shadcn + ErrorBoundary + RequirePermission
hooks packages/hooks/ ai07建议 usePermission、useAuth、useViewports

9.3 依赖的后端契约(需对应 AI 确认)

契约 提供方 当前状态
POST /iam/loginGET /iam/effective-permissionsGET /iam/me ai02 iam 已实现
GET /teacher/viewportsGET /teacher/dashboard ai03 teacher-bff 已实现
/classes/* CRUD ai03 core-edu 已实现
/exams/* /homework/* /grades/* ai03 core-edu 已实现P3
/textbooks/* /knowledge-points/* /questions/* ai05 content 已实现P4
/analytics/* ai06 data-ana 已实现P4 CDC
GET /student/viewports ai04 student-bff 📐 待 ai04 设计
GET /parent/viewports ai04 parent-bff 📐 待 ai04 设计
/notifications/* + WebSocket 推送 ai05 msg + ai01 push-gateway 📐 待 P5
GET /ai/generate-questionsSSE ai06 ai 📐 待 P5

9.4 错误码前缀(前端 i18n 路由依赖)

前端不产生错误码,仅消费。需各服务确认错误码前缀不重叠:

前缀 服务 状态
IAM_ iam ai02 已用
CLASSES_ core-edu/classes 已用
EXAMS_ / HOMEWORK_ / GRADES_ core-edu ⚠️ 待 ai03 确认
CONTENT_ content ⚠️ 待 ai05 确认
MSG_ msg ⚠️ 待 ai05 确认
AI_ ai ⚠️ 待 ai06 确认
BFF_TEACHER_ / BFF_STUDENT_ / BFF_PARENT_ 3 BFF ⚠️ 待 ai03/ai04 确认
GW_ api-gateway ai01 已用
NETWORK_ 前端 ai07 自有

9.5 不产生 Kafka 事件

前端不发布/消费 Kafka 事件。WebSocket 推送由 push-gateway 消费 Kafka 转发。


实施路线ai07 自用)

P2 收尾teacher-portal 审计对齐)

  1. packages/ui-tokens/(三层设计令牌)+ packages/ui-components/ErrorBoundary/RequirePermission/Loading/Empty+ packages/hooks/usePermission/useAuth
  2. teacher-portal 引入 TanStack Query + Zustand + nuqs + react-hook-form
  3. 抽取 lib/api.ts 统一 API 请求层
  4. AppShell 改用 usePermission(),删除 user.roles.join(", ") 硬编码
  5. globals.css / tailwind.config.js 迁移到 ui-tokens 三层令牌
  6. 引入 next-intl + i18n key 路由
  7. 引入 ESLint flat config 自定义规则no-hardcoded-fonts / design-tokens
  8. 补 ErrorBoundary + /api/health route
  9. 配置 next.config.js Module FederationShell 角色)
  10. 补 Vitest 单测 + Playwright E2E覆盖率 ≥ 80%

P3student-portal

  1. apps/student-portal/Remote 角色)
  2. 配置 MFexposes pagesremotes teacher
  3. 实现 Dashboard + 我的作业 + 提交作业 + 我的考试 + 作答考试
  4. 复用 Shell 的 AppShell + 共享组件
  5. SSE 接入(考试作答自动保存)

P4parent-portal

  1. apps/parent-portal/Remote 角色)
  2. 实现 Dashboard + 子女切换 + 成绩查看 + 通知偏好
  3. 多子女状态管理Zustand slice

P5推送 + AI 接入)

  1. teacher-portal 接入 WebSocketpush-gateway
  2. teacher-portal AI 辅助出题SSE + Tiptap
  3. student/parent-portal 接入通知推送

P6admin-portal + 硬化)

  1. apps/admin-portal/Remote 角色)
  2. 实现用户/角色/权限/视口/组织/监控管理
  3. Web Vitals + OTel browser SDK 接入
  4. A11y WCAG 2.2 AA 审计
  5. 性能优化MF shared 单例验证、bundle 分析)

AI Agent: ai07 (teacher-portal / student-portal / parent-portal / admin-portal) Branch: docs/teacher-portal-stage1-stage2-design-ai07 Coordinator: coord-ai