Files
Edu/apps/teacher-portal/docs/02-architecture-design.md
2026-07-10 15:05:47 +08:00

36 KiB
Raw Blame History

模块架构设计文档 — teacher-portal

AIai13TS/React · 教学场景域前端 shell 阶段:阶段 2 交付物 日期2026-07-092026-07-10 审查回写:对齐 F9 GraphQL P2 起 + ARB-001/002 仲裁REST→GraphQL 全量重写) 关联:阶段 1 理解确认书长远架构补全004 架构影响地图 §5.4、coord 仲裁 ARB-001/002pending-features P2 状态: 已对齐 F9GraphQL P2 起)+ ARB-001schema 第一版)+ ARB-002MF Shell 暴露清单)


1. 模块内部分层图4 端统一 MF + GraphQL 架构)

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

    subgraph Shell["teacher-portalShell 宿主 :4000"]
        RootLayout[RootLayout<br/>字体/令牌/i18n Provider]
        GraphQLProvider[GraphQLProvider<br/>urql client 单例ARB-002 §2.17 方案 A]
        AppShell[AppShell<br/>左栏导航 + 主内容区 + 路由守卫]
        Router[Next.js App Router]
        SharedDeps["共享依赖暴露<br/>react/react-dom/urql/graphql/@edu/*ARB-002 shared singleton"]
    end

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

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

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

    subgraph RemoteAdmin["admin-portalRemoteP6+"]
        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/useGraphQLClient]
        LibTS[shared-ts<br/>GraphQL schema + 通用工具]
    end

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

    subgraph TeacherBFF["teacher-bff :3003"]
        BFF[GraphQL Yoga + DataLoader<br/>POST /graphqlARB-001 schema]
    end

    Browser --> URL
    URL --> RootLayout
    RootLayout --> GraphQLProvider
    GraphQLProvider --> AppShell
    AppShell --> Router
    Router -->|动态加载| RemoteTeacher
    Router -->|动态加载 P3+| RemoteStudent
    Router -->|动态加载 P4+| RemoteParent
    Router -->|动态加载 P6+| 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

    GraphQLProvider -->|POST /graphql over HTTP| GW
    GW -->|反向代理 /api/v1/teacher/*| BFF
    BFF -->|gRPC 聚合 iam+classes+core-edu+content+data-ana+msg+ai| Services

1.1 MF 拓扑选型

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

Shell 职责teacher-portalARB-002 §2.2

  • RootLayout字体、设计令牌、i18n Provider、GraphQLProvider、Zustand StoreProvider
  • AppShell左侧导航 + 主内容区 + 用户信息 + 登出 + 路由守卫)
  • GraphQLProviderurql client 单例,总裁 §2.17 方案 ARemote 复用)
  • 共享依赖暴露react、react-dom、urql、graphql、@edu/ui-tokens、@edu/ui-components、@edu/hooks
  • 路由表4 端路由前缀:/teacher/*/student/*/parent/*/admin/*
  • 登录页(统一登录入口 POST /api/auth/login,非 MF登录是认证前提

1.2 MF 配置teacher-portal/next.config.jsARB-002 §2.2 裁决)

// teacher-portal/next.config.jsShellARB-002 第一版暴露清单)
const NextFederationPlugin = require("@module-federation/nextjs-mf");

// P2remotes 为空NEXT_PUBLIC_MF_ENABLED=falseARB-002 §2.3P3+ 逐步接入
const remotes = (isServer) => {
  if (process.env.NEXT_PUBLIC_MF_ENABLED !== "true") return {};
  return {
    student: `student_app@http://localhost:4001/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
    parent: `parent_app@http://localhost:4002/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
    admin: `admin_app@http://localhost:4003/_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), // P2 为 {}ARB-002 §2.3
        exposes: {
          // ARB-002 §2.2 暴露清单
          "./AppShell": "./src/components/AppShell.tsx",
          "./GraphQLProvider": "./src/app/providers.tsx",
          "./useAuth": "./packages/hooks/src/use-auth.ts",
          "./usePermission": "./packages/hooks/src/use-permission.ts",
          "./useGraphQLClient": "./packages/hooks/src/use-graphql-client.ts",
          "./ErrorBoundary": "./packages/ui-components/src/error-boundary.tsx",
          "./Loading": "./packages/ui-components/src/loading.tsx",
          "./Empty": "./packages/ui-components/src/empty.tsx",
          "./RequirePermission": "./packages/ui-components/src/require-permission.tsx",
        },
        shared: {
          // ARB-002 §2.2 singleton 配置
          react: { singleton: true, requiredVersion: "^18.3.0" },
          "react-dom": { singleton: true, requiredVersion: "^18.3.0" },
          urql: { singleton: true, requiredVersion: "^2.2.0" },
          graphql: { singleton: true, requiredVersion: "^16.8.0" },
          "@edu/ui-tokens": { singleton: true },
          "@edu/ui-components": { singleton: true },
          "@edu/hooks": { 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*`,
      },
    ];
  },
};

P2 Remote 数量 = 0ARB-002 §2.3NEXT_PUBLIC_MF_ENABLED=false 默认关。P3 student-portal 作为首个 Remote 接入(总裁 §1.3 step 4。feature flag 控制回退单体路由。

2. 领域模型(前端视角)

前端不持有业务聚合根,仅持有"视图模型"ViewModel和"会话状态"。数据来源统一为 teacher-bff GraphQLF9 裁决)。

2.1 会话状态Session

interface Session {
  user: UserInfo; // { id, email, name, roles, dataScope }GraphQL me Query
  tokens: { accessToken: string; refreshToken: string }; // F12: localStorageP6 迁移 cookie
  viewports: ViewportItem[]; // L1 导航视口GraphQL viewports Query
  permissions: string[]; // 权限点GraphQL me Query 内嵌或 effective-permissions
  expiresAt: number; // access token 过期时间戳
}

存储Zustand sessionSliceL3+ localStorage 持久化刷新恢复F12+ urql cacheL2GraphQL document cache

2.2 视口模型Viewport

// 对齐 ARB-001 §1.2 schemaViewportItem
interface ViewportItem {
  key: string; // 'dashboard' | 'classes' | ...
  label: string; // i18n key 或显式文案
  route: String; // '/teacher/dashboard'schema 为 String
  icon: String | null; // 图标 keyschema 可空)
  sortOrder: String; // 排序schema 为 String
  requiredPermission: String | null; // 'CLASSES_READ' 等
}

来源GraphQL viewports QueryARB-001 §1.2。AppShell 按 requiredPermission + usePermission().hasPermission() 过滤渲染导航。

2.3 权限模型Permission

interface PermissionState {
  permissions: string[]; // ['CLASSES_READ', 'EXAMS_CREATE', ...]GraphQL me Query 返回)
  dataScope: DataScope; // SELF | CLASS | GRADE | SCHOOL | DISTRICT | ALLARB-001 enum
  hasPermission: (perm: string) => boolean;
  hasAnyPermission: (perms: string[]) => boolean;
  hasAllPermissions: (perms: string[]) => boolean;
}

来源GraphQL me Query 返回 { user, roles, dataScope }ARB-001 §1.2)。角色变更由 iam 发 Kafka → msg → push-gateway WebSocket 推送 → 前端 invalidate urql cache03 §2.5)。

3. 数据模型(前端缓存层)

前端无数据库,仅有 urql 缓存层03 §1.4.4 缓存策略):

数据类型 存储 staleTime 失效策略
Sessiontoken + user localStorage + ZustandF12 access 15min / refresh 7day 401 自动 refreshrefresh 失败跳登录
权限列表 + 视口 urql cacheExchange 0always fetch 角色变更 WebSocket 推送后强制 invalidate
班级/年级列表 urql cacheExchange 30s mutation 后 invalidate
考试/作业/成绩 urql cacheExchange 10s mutation 后 invalidate + 乐观更新03 §10.1
教学资源详情 urql cacheExchange 30s staleTime 30s
学情宽表 urql cacheExchange 60s ISR revalidate 60s + CSR 交互
URL 状态(分页/筛选) nuqs 永久(可分享)
表单临时态 react-hook-form 卸载即销毁

urql 默认 document cache 适合简单场景复杂关联班级↔考试↔成绩P3 起评估切 @urql/exchange-graphcache normalized cache03 §1.4.4)。不引入 TanStack Query 做 GraphQL 缓存避免双缓存层TanStack Query 仅用于非 GraphQL 场景(文件上传/SSE

4. API 设计(前端 → teacher-bff GraphQL

F9 裁决P2 起 all-in GraphQL。前端不再设计 REST 请求层,统一通过 urql client 消费 teacher-bff GraphQLARB-001 schema 第一版)。

4.1 urql client 单例packages/hooks + Shell GraphQLProvider

// packages/hooks/src/use-graphql-client.tsShell 暴露Remote 复用ARB-002 §2.17 方案 A
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 POST /graphql经 api-gateway 代理)
  fetchOptions: () => {
    const token = readAuthToken(); // useAuth 注入F12: localStorage
    const traceId = readTraceId();
    return {
      headers: {
        ...(token ? { Authorization: `Bearer ${token}` } : {}),
        "X-Trace-Id": traceId,
      },
    };
  },
  exchanges: [cacheExchange, fetchExchange],
});

// Shell: src/app/providers.tsx 包裹 <Provider value={client}>,所有 Remote 复用同一 client

职责

  • Shell 初始化 urql client 单例,通过 React Context 注入给所有 RemoteARB-002 方案 A
  • token 刷新/401 处理由 useAuth 统一拦截(不污染 urql exchange 链,见 03 §2.7
  • MF shared singletonreact/urql/graphql避免 Remote 多实例 + 缓存不一致
  • 请求/响应 trace_id 透传(X-Trace-Id header

4.2 GraphQL Query/Mutation 约定(对齐 ARB-001 schema

// P2 QueryARB-001 §1.25 个 Must Have
import { gql } from "urql";

const DashboardQuery = gql`
  query Dashboard {
    dashboard {
      user { id email name roles dataScope }
      classes { id name gradeId studentCount }
      viewports { key label route icon sortOrder requiredPermission }
      stats { totalExams pendingGrading todayHomework }
    }
  }
`;

const ViewportsQuery = gql`query { viewports { key label route icon sortOrder requiredPermission } }`;
const MeQuery = gql`query { me { id email name roles dataScope } }`;
const ClassesQuery = gql`query { classes { id name gradeId studentCount } }`;

// P3+ MutationARB-001 §1.2 P2 不包含)
const CreateExamMutation = gql`
  mutation CreateExam($input: CreateExamInput!) {
    createExam(input: $input) { ... on ExamCreated { id } }
  }
`;

urql mutation + 乐观更新

const [, createExam] = useMutation(CreateExamMutation);
// 乐观更新onMutate 回滚 + invalidateQueries见 03 §10.1

4.3 错误处理ActionState 信封 + GraphQL errorsARB-001 §1.3

场景 处理
GraphQL 错误errors[] 解析 extensions.codeBFF_TEACHER_* 前缀G14映射到 i18n key error.teacher_bff.<code_snake>F11
网络错误 重试 1 次 → 提示网络异常 → 记录 trace_id
401 useAuth 静默 refresh 一次 → 重试;失败跳登录(见 03 §2.7
部分失败data + errors 共存) 渲染 data 可用部分 + errors 区域提示不整体失败ARB-001 §1.3 降级模式方案 B
  • BFF 始终返回 ActionState 信封004 §11.5),前端统一走 normalizeActionState() 工具packages/hooks

5. 事件设计

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

5.1 WebSocket 推送P5

事件 触发 teacher-portal 前端动作
NotificationRequested msg 服务投递 toast 提示 + 通知中心未读数 +1
HomeworkSubmitted 学生提交作业 toast + urql cache invalidate 作业批改列表
角色变更iam iam Kafka urql cache invalidate 权限/视口(强制刷新 me Query

5.2 SSE 流式P5 AI 辅助出题)

GET /api/v1/ai/generate-questions (SSE) 或 GraphQL SubscriptionP6+ 评估)
data: {"delta": "题目"}\n\n
data: {"done": true}\n\n

前端用 AsyncIterable<T> 消费(非 GraphQLTanStack Query 场景Tiptap 逐字插入。

6. 横切关注点对齐清单

6.1 权限前端等价F7 命名 <RESOURCE>_<ACTION>

路由 requiredPermission
/teacher/dashboard TEACHER_DASHBOARD_VIEW
/teacher/classes CLASSES_READ
/teacher/classes/new CLASSES_CREATE
/teacher/exams EXAMS_READ
/teacher/exams/new EXAMS_CREATE
/teacher/homework HOMEWORK_READ
/teacher/homework/:id/grade HOMEWORK_GRADE
/teacher/grades GRADES_READ
/teacher/ai-assist AI_GENERATE
/teacher/lesson-prep LESSON_PREP_VIEW
/teacher/knowledge-graph CONTENT_READ

完整权限点常量集中在 packages/contracts/src/permissions.tscoord 维护ai13 消费。L3 组件级视口用 <RequirePermission perm="EXAMS_CREATE"><Button>新建考试</Button></RequirePermission>

6.2 错误码清单(前端 i18n 路由,对齐 matrix.md §6

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

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

6.4 MetricsWeb Vitals

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

P6 接入P2-P5 暂缓。

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

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 可达 + 内存 < 阈值

6.7 优雅关闭

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

7. 共享组件库packages/ui-components/ai13 维护ARB-002 暴露)

组件 用途 MF 暴露ARB-002
AppShell 左侧栏 + 主内容区布局 ./AppShell
RequirePermission L3 组件级视口控制(无权限不渲染 children ./RequirePermission
ErrorBoundary React 渲染异常兜底fallback UI ./ErrorBoundary
Loading 骨架屏Skeleton ./Loading
Empty 空态(插画 + 文案 + CTA ./Empty
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 封装 新建
RichTextEditor Tiptap 封装(备课/出题/反馈) 新建

8. 共享 Hookspackages/hooks/ai13 维护ARB-002 暴露)

Hook 职责 MF 暴露ARB-002
useGraphQLClient() urql client 单例Shell 注入Remote 复用) ./useGraphQLClient
useAuth() 会话状态user/token/refresh/login/logout ./useAuth
usePermission() 权限查询hasPermission/hasAny/hasAll + dataScope ./usePermission
useViewports(scope) 视口列表GraphQL viewports Query按 scope 过滤)
useApi() 非 GraphQL 场景(文件上传/SSEApiClient 实例
useA11yId() 唯一 ARIA ID 生成
useAriaLive() aria-live 区域管理
useToast() 全局 toastZustand ui-store

9. 设计令牌三层packages/ui-tokens/ai13 维护)

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

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

方向 对方服务 协议 接口/事件 用途 阶段
调用 api-gateway HTTPGraphQL over HTTP /api/v1/teacher/* 代理到 teacher-bff POST /graphql 全部 GraphQL 业务请求 P2+
调用 api-gateway HTTP POST /api/auth/login 教师登录(非 MF认证前提 P2
调用 push-gateway WebSocket ws://push-gateway:8081/ws 实时推送 P5
调用 ai SSE GET /api/v1/ai/generate-questions AI 流式出题(非 GraphQL P5
被调用 前端不暴露接口给其他服务
消费 teacher-bff GraphQLARB-001 schema dashboard/viewports/me/classes/class QueryP2+ P3+ Mutation 教师场景聚合 P2+
消费 push-gateway WebSocket NotificationRequested/HomeworkSubmitted/角色变更 实时推送 P5+
依赖 coord 维护 packages/shared-proto TS 类型(仅 contracts 部分) P1+
依赖 coord 维护 packages/shared-ts + contracts/graphql/teacher-bff.graphql ApiClient/Logger/GraphQL schema/通用工具 P2+
依赖 ai13 维护 packages/ui-tokens(已建,批次 0.15 三层设计令牌 P2+
依赖 ai13 维护 packages/ui-components(已建,批次 0.15 shadcn + 共享组件ARB-002 暴露) P2+
依赖 ai13 维护 packages/hooks(已建,批次 0.15 useGraphQLClient/usePermission/useAuthARB-002 暴露) P2+
依赖 coord 维护 packages/contracts Permissions 常量 + 类型 P2+

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

11. 风险与假设

11.1 假设

  1. 假设 coord 建立 packages/shared-tspackages/contracts:包含 GraphQL schema、Logger、Permissions 常量、通用类型。 packages 骨架ui-tokens/ui-components/hooks已于批次 0.15 建立ISSUE-039
  2. 假设 teacher-bff 提供 POST /graphqlARB-001 schema 第一版):返回 dashboard/viewports/me/classes/class 5 个 Query。 待 ai03 + coord 仲裁。
  3. 假设 api-gateway 代理 /api/v1/teacher/* → teacher-bff:3003 待 ai01。
  4. 假设 Next.js 14+ Module Federation 2.0 稳定@module-federation/nextjs-mf 在 Next.js App Router 下可用。若不稳定,降级为 4 端独立部署 + 各自 Shell重复实现 AppShell
  5. 假设 iam 提供 refresh cookie 端点P6 localStorage→httpOnly cookie 迁移前置。 待 ai06 P6。

11.2 技术风险

风险 影响 缓解
MF SSR 对齐复杂 Remote 在 SSR 时需 Shell 提供上下文 优先 CSRSSR 仅用于首屏 dashboardMF 2.0 支持 SSR
共享依赖版本漂移 Remote 与 Shell 的 react/react-dom/urql/graphql 版本不一致导致运行时错误 MF shared.singleton: trueARB-002+ CI 检查版本对齐
Token 刷新竞态 多请求同时 401 触发多次 refresh useAuth 全局单例 + refresh promise 复用03 §2.7
权限缓存陈旧 角色变更后前端仍用旧权限 iam 角色变更发 Kafka → msg → push-gateway WebSocket → urql cache invalidate
GraphQL schema 演进阻塞 ai03 schema 变更未同步 ai13 导致前端 query 失效 SDL-first + schema 注册表 packages/shared-ts/contracts/graphql/ARB-001
设计令牌迁移破坏现有样式 teacher-portal 现有硬编码令牌迁移到三层模型后样式漂移 灰度迁移:先建 ui-tokens 包teacher-portal 引入但不删除旧 globals.css验证后切换
urql cache 膨胀 长时间使用后缓存项过多 staleTime 分级 + P3 评估 graphcache normalized cache

11.3 已决策设计点(原未决决策,现已对齐仲裁)

决策点 结论 裁决依据
packages 归属 ai13 维护 ui-tokens/ui-components/hookscoord 维护 shared-ts/contracts 总裁 §2.18ISSUE-039
GraphQL vs REST P2 起 all-in GraphQLurql无 REST 过渡 F9 + ARB-001ISSUE-036/037
i18n key 命名 error.{{service}}.{{code_snake_case}},与错误码前缀对齐 F11
MF 暴露粒度 暴露 AppShell 整体 + GraphQLProvider + hooks + UI 组件ARB-002 清单) 总裁 §2.17 + ARB-002ISSUE-038
GraphQL client 单例 Shell 暴露 GraphQLProviderRemote 复用(方案 A 总裁 §2.17ISSUE-038
P2 Remote 数量 0NEXT_PUBLIC_MF_ENABLED=falseP3 student 首个接入 ARB-002 §2.3

12. coord 交叉审查所需信息

12.1 端口矩阵

dev 端口 生产端口 备注
teacher-portal 4000 4000 Shell 宿主

matrix.md §1 端口矩阵对齐teacher-portal :4000

12.2 依赖的共享包

路径 维护方 内容
shared-ts packages/shared-ts/ coord ApiClient、Logger、GraphQL schema、通用工具
contracts packages/contracts/ coord Permissions 常量、ActionState 类型、UserInfo 类型
ui-tokens packages/ui-tokens/ ai13 三层设计令牌( 已建,批次 0.15
ui-components packages/ui-components/ ai13 shadcn + ErrorBoundary + RequirePermission 已建ARB-002 暴露)
hooks packages/hooks/ ai13 useGraphQLClient/usePermission/useAuth 已建ARB-002 暴露)

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

契约 提供方 当前状态
POST /graphql + dashboard/viewports/me/classes/class QueryARB-001 teacher-bffai03 待 coord 仲裁
POST /api/auth/login api-gatewayai01→ iam 待 ai01
classExams/classHomework/studentGrades Query + Mutation teacher-bffai03 P3 待 ai03 P3
knowledgeGraph/studentAnalytics Query teacher-bffai03 P4 待 ai03 P4
myNotifications/markAsRead Query + Mutation teacher-bffai03 P5 待 ai03 P5
ws://push-gateway:8081/ws 推送 push-gatewayai02 P5 待 ai02 P5
GET /api/v1/ai/generate-questionsSSE aiai12 P5 待 ai12 P5

12.4 错误码前缀(前端 i18n 路由依赖,对齐 matrix.md §6

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

前缀 服务 状态
IAM_ iam 已用
CORE_EDU_ core-edu 已用
CLASSES_ core-edu 已用
EXAMS_/HOMEWORK_/GRADES_ core-edu ⚠️ 待确认
CONTENT_ content ⚠️ 待确认
MSG_ msg ⚠️ 待确认
AI_ ai ⚠️ 待确认
BFF_TEACHER_ teacher-bff 已裁决G14
GW_ api-gateway 已用
NETWORK_ 前端 ai13 自有

12.5 不产生 Kafka 事件

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

13. 实施路线ai13 自用,详见 workline.md

P2MF Shell + GraphQL client + 基础页面ARB-001/002

  1. packages 骨架ui-tokens/ui-components/hooks批次 0.15 已完成)
  2. NextFederationPlugin 配置ARB-002 exposes + shared无 remotes
  3. GraphQLProvider + urql client 单例Shell 暴露§2.17 方案 A
  4. AppShellLayout + 侧边栏 + 路由守卫 + ErrorBoundary + 设计令牌三层)
  5. 登录页(POST /api/auth/login → JWT 存 localStorageF12
  6. 权限上下文usePermission + useViewports权限点 <RESOURCE>_<ACTION> F7
  7. Dashboard 框架(dashboard GraphQL Query+ 班级列表(classes/class Query+ 学生列表 + 个人设置
  8. 补 ErrorBoundary + /api/health route
  9. 引入 ESLint flat config 自定义规则no-hardcoded-fonts / design-tokens
  10. 补 Vitest 单测 + Playwright E2E覆盖率 ≥ 80%

P3考试/作业/成绩 + 乐观更新 + 多 Tab 同步)

  1. 考试/作业/成绩管理页面GraphQL Query + Mutation
  2. 乐观更新urql mutation onMutate 回滚 + invalidate
  3. 多 Tab 会话同步BroadcastChannel03 §2.5

P5推送 + AI 接入)

  1. teacher-portal 接入 WebSocketpush-gateway
  2. teacher-portal AI 辅助出题SSE + Tiptap

P6硬化

  1. Web Vitals + OTel browser SDK 接入
  2. A11y WCAG 2.2 AA 审计
  3. localStorage → httpOnly Cookie 迁移
  4. 性能优化MF shared 单例验证、bundle 分析)

AI Agent: ai13 (teacher-portal shell) Branch: feat-review-teacher-portal-docs-vErc0L Coordinator: coord-ai