Files
Edu/apps/admin-portal/docs/02-architecture-design.md
SpecialX e691cd267d docs(teacher-portal): ai07 阶段1+2 拆分到4端portal的docs目录
删除合并版README,按portal拆分8份文档(每端01-understanding+02-architecture-design)

teacher-portal(shell/P2)+student-portal(remote/P3)+parent-portal(remote/P4)+admin-portal(remote/P6)

AI Agent: ai07 (4 portals)

Branch: docs/portals-stage1-stage2-design-ai07
2026-07-09 18:23:27 +08:00

41 KiB
Raw Blame History

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

AIai07TS/React · 管理场景域前端 remote 阶段:阶段 2 交付物 日期2026-07-09 关联:阶段 1 理解确认书004 架构影响地图 §5.4、pending-features P6teacher-portal 阶段2 状态:待 coord 交叉审查


1. 模块内部分层图简化版admin-portal Remote + Shell 引用 + Gateway

graph TB
    subgraph Browser["浏览器(系统/校管理员)"]
        URL[URL 路由 /admin/*]
    end

    subgraph Shell["teacher-portalShell 宿主)"]
        AppShell[AppShell<br/>左栏导航 + 主内容区<br/>按 scope=admin 过滤视口]
        RootLayout[RootLayout<br/>字体/令牌/i18n Provider<br/>TanStack QueryClientProvider<br/>Zustand StoreProvider]
        Router[Next.js App Router<br/>动态加载 admin Remote]
        SharedDeps["共享依赖暴露singleton<br/>react/react-dom/@tanstack/react-query/zustand/nuqs<br/>ui-components/ui-tokens/contracts/hooks/shared-ts"]
        Rewrites["rewrites /api/v1/* → api-gateway"]
    end

    subgraph RemoteAdmin["admin-portalRemote 子应用)"]
        AdminPages[管理场景页面<br/>dashboard/users/roles/permissions/viewports/organization/monitoring]
        AdminComponents["admin 特有组件<br/>UserManagementTable/RolePermissionMatrix/ViewportConfigEditor/PlatformMonitor"]
        AdminHooks["admin 业务 Hooks<br/>useUsers/useRoles/usePermissions/useViewports/useMonitoring"]
    end

    subgraph Shared["共享层packages/,由 Shell 暴露)"]
        UITokens[ui-tokens<br/>三层设计令牌]
        UIComponents[ui-components<br/>shadcn + A11y + ErrorBoundary + RequirePermission]
        Contracts[contracts<br/>Permissions 常量 + 类型]
        Hooks[hooks<br/>usePermission/useAuth/useViewports/useApi]
        LibTS[shared-ts<br/>ApiClient/Logger/Tracer]
    end

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

    subgraph Backend["后端服务"]
        IAM[iam<br/>用户/角色/权限/视口 CRUD]
        BFF[teacher-bff<br/>/admin/* 聚合]
        MSG[msg<br/>/notifications/*P5]
    end

    Browser --> URL
    URL --> RootLayout
    RootLayout --> AppShell
    AppShell --> Router
    Router -->|动态加载 /admin/*| RemoteAdmin

    RemoteAdmin -->|消费 singleton| SharedDeps
    SharedDeps --> UITokens
    SharedDeps --> UIComponents
    SharedDeps --> Contracts
    SharedDeps --> Hooks
    SharedDeps --> LibTS

    AdminPages --> AdminComponents
    AdminPages --> AdminHooks
    AdminHooks -->|useApi → ApiClient| LibTS
    AdminComponents --> UIComponents

    AdminPages -->|fetch /api/v1/iam/*| Rewrites
    AdminPages -->|fetch /api/v1/admin/*| Rewrites
    AdminPages -->|fetch /api/v1/notifications/*| Rewrites
    Rewrites --> GW
    GW --> IAM
    GW --> BFF
    GW --> MSG

    AppShell -->|fetch /api/v1/iam/effective-permissions| Rewrites

1.1 Remote 与 Shell 的职责边界

职责 归属 说明
RootLayout字体/令牌/Provider teacher-portal Shell admin-portal 复用,不重复引入
AppShell左栏 + 主内容区) teacher-portal Shell admin-portal 通过 <AppShell scope="admin"> 渲染管理端视口
路由表 /admin/* teacher-portal Shell Shell 注册 /admin/* 路由组,动态 import admin Remote 模块
登录页 teacher-portal Shell 统一登录入口,按角色重定向到 /admin/dashboard
rewrites /api/v1/* teacher-portal Shell admin-portal 不实现 rewrites依赖 Shell
管理场景页面 admin-portal Remote /admin/* 下的所有 page.tsx
管理特有组件 admin-portal Remote UserManagementTable 等 4 个
管理业务 Hooks admin-portal Remote useUsers/useRoles/usePermissions/useViewports/useMonitoring
共享组件库 Shell 暴露 AppShell/RequirePermission/ErrorBoundary/DataTable/Form/Chart 等
共享 Hooks Shell 暴露 usePermission/useAuth/useViewports/useApi/useA11yId
ApiClient Shell 暴露 packages/shared-ts/src/api-client.tsadmin-portal 通过 useApi 获取

1.2 MF 配置admin-portal/next.config.jsRemote 角色)

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

module.exports = {
  reactStrictMode: true,
  transpilePackages: [
    "@edu/ui-tokens",
    "@edu/ui-components",
    "@edu/hooks",
    "@edu/contracts",
    "@edu/shared-ts",
  ],
  webpack(config, { isServer }) {
    config.plugins.push(
      new NextFederationPlugin({
        name: "admin_app",
        filename: "static/chunks/remoteEntry.js",
        // Remote 不暴露任何模块给 ShellShell 通过 dynamic import 加载 admin 的 pages
        exposes: {
          "./pages": "./src/pages",
        },
        // Remote 反向引用 Shell 暴露的共享组件(可选,多数通过 singleton shared 解决)
        remotes: {
          teacher: `teacher_app@http://localhost:3000/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
        },
        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 },
          "@edu/ui-tokens": { singleton: true },
          "@edu/ui-components": { singleton: true },
          "@edu/hooks": { singleton: true },
          "@edu/contracts": { singleton: true },
          "@edu/shared-ts": { singleton: true },
        },
        extraOptions: { exposePages: false },
      }),
    );
    return config;
  },
  // admin-portal 不实现 rewrites依赖 teacher-portal Shell 的 rewrites 代理 /api/v1/*
};

说明

  • admin-portal 作为 Remotename: 'admin_app',被 Shell 在 remotes 中引用为 admin: 'admin_app@http://localhost:3003/...'
  • admin-portal 不实现 rewrites,所有 /api/v1/* 请求由 Shell 的 rewrites 代理到 api-gateway
  • shared 全部声明为 singleton: true,确保与 Shell 共享同一实例(避免 React 多实例报错、Zustand store 分裂)
  • transpilePackages 列出本地 packages/* 软链依赖,确保 SWC 正确编译

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

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

2.1 会话状态 / 视口 / 权限(与 teacher-portal 共享)

SessionViewportPermission 三个模型与 teacher-portal 完全共享,定义和存储策略见 teacher-portal 阶段2 §2.1-2.3

admin-portal 作为 Remote通过 Shell 暴露的 useAuth() / usePermission() / useViewports('admin') Hook 消费这些模型,不重复定义

2.2 管理场景视图模型admin-portal 特有)

// 用户管理视图模型
interface UserViewModel {
  id: string;
  email: string;
  name: string;
  roles: RoleViewModel[]; // 用户拥有的角色
  status: "active" | "disabled" | "locked";
  dataScope: DataScope; // L0-L5
  organizationId: string | null; // 所属组织
  lastLoginAt: number | null;
  createdAt: number;
  updatedAt: number;
}

// 角色管理视图模型
interface RoleViewModel {
  id: string;
  name: string;
  code: string; // 角色编码,如 'school_admin'
  description: string;
  permissions: PermissionViewModel[]; // 角色拥有的权限
  userCount: number; // 该角色下的用户数(用于删除前校验)
  dataScope: DataScope; // 角色默认数据范围
  isSystem: boolean; // 系统预置角色不可删除
  createdAt: number;
  updatedAt: number;
}

// 权限管理视图模型
interface PermissionViewModel {
  id: string;
  code: string; // 'IAM_USER_READ' 等
  name: string; // 显示名
  description: string;
  resource: string; // 资源类型 'user' | 'role' | 'permission' | 'viewport' | 'org' | 'monitoring'
  action: string; // 'read' | 'create' | 'update' | 'delete' | 'manage'
  isSystem: boolean; // 系统预置权限不可删除
}

// 视口配置视图模型(管理端可编辑)
interface ViewportConfigViewModel {
  id: string;
  key: string; // 'dashboard' | 'users' | ...
  label: string; // i18n key
  route: string; // '/admin/dashboard'
  icon: string | null;
  sortOrder: number;
  requiredPermission: string | null;
  scope: "teacher" | "student" | "parent" | "admin";
  isVisible: boolean; // 是否在导航显示
}

// 平台监控视图模型
interface MonitoringMetricsViewModel {
  timestamp: number;
  activeUsers: number; // 当前在线用户数
  totalUsers: number;
  requestsPerMinute: number;
  errorRate: number; // 0-1
  avgResponseTimeMs: number;
  serviceHealth: Array<{
    serviceName: string;
    status: "healthy" | "degraded" | "down";
    latencyMs: number;
  }>;
}

2.3 数据范围控制admin-portal 特有)

// admin-portal 用户可见的数据范围由 dataScope 决定
// L3 校管理员:仅见本校用户/角色/组织
// L4 区教研员:仅见本区
// L5 系统管理员:全平台
interface AdminDataScopeFilter {
  dataScope: DataScope; // L3-L5
  schoolId?: string; // L3 时必填
  districtId?: string; // L4 时必填
}

// 所有列表查询 API 自动注入此 filter由 ApiClient 拦截器添加)

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

admin-portal 无数据库,仅有 TanStack Query 缓存层。管理数据低频变,采用 5min 长缓存策略:

数据类型 存储 TTL 失效策略
Sessiontoken + user localStorage + Zustand access 15min / refresh 7day 401 自动 refreshrefresh 失败跳登录(复用 Shell
权限列表 TanStack Query cache 5min 角色变更主动 invalidate复用 Shell
视口列表 TanStack Query cache 5min 视口配置变更主动 invalidate
用户列表 TanStack Query cache 5min staleTime 5minmutation 后 invalidate
角色列表 TanStack Query cache 5min staleTime 5minmutation 后 invalidate
权限列表(全量) TanStack Query cache 30min staleTime 30min权限点极少变更
视口配置列表 TanStack Query cache 5min staleTime 5minmutation 后 invalidate
组织树 TanStack Query cache 5min staleTime 5min
平台监控指标 TanStack Query cache 60s staleTime 60srefetchInterval 60s 轮询)
用户活动统计 TanStack Query cache 5min staleTime 5minrefetchInterval 5min 轮询)
URL 状态(分页/筛选) nuqs 永久(可分享)
表单临时态 react-hook-form 卸载即销毁

缓存策略说明:管理数据(用户/角色/权限/视口/组织低频变更5min 长缓存减少 BFF 压力监控指标需要相对实时60s 轮询权限点常量几乎不变30min 长缓存。所有 mutation 成功后主动 invalidate 对应 queryKey确保 UI 立即刷新。

4. API 设计(前端 → 后端)

前端不设计后端 API仅声明消费的端点。详见 01-understanding.md §3.1

4.1 统一 API 请求层(复用 Shell 暴露的 ApiClient

admin-portal 不重复实现 ApiClient通过 Shell 暴露的 useApi() Hook 获取 ApiClient 实例。ApiClient 定义见 teacher-portal 阶段2 §4.1

// admin-portal 业务 Hook 示例(消费 Shell 暴露的 useApi
import { useApi } from "@edu/hooks";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";

export function useUsers(filter: AdminDataScopeFilter) {
  const api = useApi();
  return useQuery({
    queryKey: ["admin", "users", filter],
    queryFn: () => api.get<UserViewModel[]>("/api/v1/iam/users", filter),
    staleTime: 5 * 60 * 1000, // 5min
  });
}

export function useCreateUser() {
  const api = useApi();
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: (input: CreateUserInput) =>
      api.post("/api/v1/iam/users", input),
    onSuccess: () =>
      queryClient.invalidateQueries({ queryKey: ["admin", "users"] }),
  });
}

4.2 TanStack Query 约定

// Query Key 命名:[scope, resource, ...args]
queryKey: ["admin", "users", { schoolId, status }];
queryKey: ["admin", "users", userId]; // 详情
queryKey: ["admin", "roles", { dataScope }];
queryKey: ["admin", "permissions"]; // 全量30min 缓存
queryKey: ["admin", "viewports", { scope }]; // 按 scope 过滤
queryKey: ["admin", "organization", { parentId }];
queryKey: ["admin", "monitoring", "metrics"]; // 60s 轮询
queryKey: ["admin", "stats", "active-sessions"]; // 5min 轮询

// Mutation 约定mutation 后主动 invalidate
const createUser = useMutation({
  mutationFn: (input) => api.post("/api/v1/iam/users", input),
  onSuccess: () =>
    queryClient.invalidateQueries({ queryKey: ["admin", "users"] }),
  onError: (e: ApiError) => toast.error(e.message),
});

// 轮询约定(监控指标)
const useMonitoringMetrics = () => {
  const api = useApi();
  return useQuery({
    queryKey: ["admin", "monitoring", "metrics"],
    queryFn: () =>
      api.get<MonitoringMetricsViewModel>("/api/v1/admin/monitoring/metrics"),
    staleTime: 60 * 1000, // 60s
    refetchInterval: 60 * 1000, // 60s 轮询
  });
};

5. 事件设计

— N/A

admin-portal 不消费 WebSocket 推送(不走 push-gateway不消费 SSE 流式响应。管理端场景对实时性要求低,采用轮询策略:

场景 轮询方式 说明
平台监控指标 TanStack Query refetchInterval: 60s 实时性要求中等
用户活动统计 TanStack Query refetchInterval: 5min 实时性要求低
通知中心P5 TanStack Query refetchInterval: 30s 不走 push-gatewayHTTP 拉取
长期监控面板 <iframe> 嵌入 GrafanaPlatformMonitor 组件) 不经过 BFF直连 Grafana

管理端不接入 push-gateway 的原因:管理员场景低频,推送基础设施成本不划算;轮询策略对 BFF 压力可控5min TTL 缓存命中率高)。

6. 横切关注点对齐清单

6.1 权限(前端等价)

路由 requiredPermission
/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

完整权限点常量集中在 packages/contracts/src/permissions.tscoord 维护。L3 组件级视口用 <RequirePermission perm="IAM_USER_CREATE"><Button>新建用户</Button></RequirePermission>(复用 Shell 暴露组件)。

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

前缀 来源服务 i18n key 模式
IAM_ iam iam.error.{{code}}
BFF_TEACHER_ teacher-bff bff.error.{{code}}
GW_ api-gateway gateway.error.{{code}}
NETWORK_ 前端网络层 network.error.{{code}}

admin-portal 仅涉及 4 个错误码前缀iam / teacher-bff / gateway / 网络层),不消费 core-edu/content/msg/ai 服务,不涉及 CORE_EDU_ / CONTENT_ / MSG_ / AI_ 前缀。

6.3 Logger

// 复用 packages/shared-ts/src/logger.tsShell 暴露)
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 + 结构化;生产环境 → SentryP6admin-portal 本身就是 P6 阶段)
// 必含字段trace_id从响应头提取、user_id、scope='admin'、path

6.4 MetricsWeb Vitals

指标 类型 上报
admin_portal_lcp_seconds LCP next/web-vitalsPOST /api/v1/admin/web-vitals
admin_portal_cls CLS 同上
admin_portal_fid_seconds FID 同上
admin_portal_ttfb_seconds TTFB 同上

admin-portal 本身就是 P6 阶段Web Vitals 在建站时即接入(与 teacher-portal P6 同步)。

6.5 TracerOTel browser SDKP6

// 复用 packages/shared-ts/src/tracer.tsShell 暴露)
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
// BatchSpanProcessor → OTLP exporter → collector → Tempo
// 自动埋点fetch、XMLHttpRequest、document load、user interaction
// admin-portal 通过 Shell 的 TracerProvider 注入,不重复初始化

6.6 健康检查

端点 用途 实现
GET /api/health Dockerfile HEALTHCHECK Next.js Route Handler返回 { status: 'ok', ts: Date.now() }
GET /api/ready K8s readinessProbe 检查 process.env.TEACHER_PORTAL_URL 可达(依赖 Shell 已启动)

6.7 优雅关闭

Next.js 无长连接admin-portal 不消费 WebSocket/SSE无需特殊处理。

6.8 横切关注点对齐清单(汇总)

对齐项 teacher-portal 实现 admin-portal 对齐方式
权限校验 usePermission() + <RequirePermission> 复用 Shell 暴露,权限点用 IAM_* / ADMIN_* / ORG_*
错误码前缀统一 ApiClient 按 error.code 前缀路由 i18n 复用 Shell 暴露的 ApiClient前缀集缩减为 4 个
logger packages/shared-ts/src/logger.ts 复用 Shell 暴露scope 字段固定为 'admin'
metrics Web Vitals → POST /api/v1/admin/web-vitals admin-portal 本身 P6建站即接入
tracer OTel browser SDKShell 初始化) 复用 Shell 的 TracerProvider不重复初始化
/healthz + /readyz Next.js /api/health + Dockerfile HEALTHCHECK admin-portal 独立实现 Route Handler
优雅关闭 N/ANext.js 无长连接) N/A
测试覆盖率 ≥ 80% Vitest + @testing-library/react + Playwright 同 teacher-portal
Dockerfile 多阶段构建 builder + runtime 同 teacher-portal
Zod 输入验证 react-hook-form + zodResolver 同 teacher-portal
GlobalErrorFilter React ErrorBoundary + API 请求层 复用 Shell 暴露的 ErrorBoundary
设计令牌三层 packages/ui-tokens/Shell 暴露) 复用 Shell 暴露,不重复引入
A11y 工具集 packages/ui-components/Shell 暴露) 复用 Shell 暴露

7. 共享组件库(复用 Shell 暴露 + admin 特有)

7.1 复用 Shell 暴露的组件

组件 用途 来源
AppShell 左侧栏 + 主内容区布局 teacher-portal Shell 暴露
RequirePermission L3 组件级视口控制 teacher-portal Shell 暴露
ErrorBoundary React 渲染异常兜底 teacher-portal Shell 暴露
Loading 骨架屏Skeleton teacher-portal Shell 暴露
Empty 空态(插画 + 文案 + CTA teacher-portal Shell 暴露
Modal / Dialog 全局 Modal teacher-portal Shell 暴露shadcn/ui
Toast 全局 toast teacher-portal Shell 暴露sonner
Button / Input / Select / Textarea 基础表单 teacher-portal Shell 暴露shadcn/ui
DataTable 表格(排序/分页/筛选) teacher-portal Shell 暴露TanStack Table
Chart 图表封装recharts teacher-portal Shell 暴露
A11y 工具集 useA11yId / mergeA11yProps 等 teacher-portal Shell 暴露
Form react-hook-form + zodResolver 封装 teacher-portal Shell 暴露

7.2 admin-portal 特有组件(新建)

组件 用途 实现要点
UserManagementTable 用户管理表格(列表/筛选/分页/批量操作) 基于 Shell 的 DataTable 封装;列:邮箱/姓名/角色/状态/数据范围/最后登录;筛选:角色/状态/组织;批量:启用/禁用/分配角色
RolePermissionMatrix 角色-权限矩阵编辑器checkbox 网格) 行=角色,列=权限(按 resource 分组);勾选触发 PUT /api/v1/iam/roles/:id/permissions;系统预置角色只读
ViewportConfigEditor 视口配置编辑器(拖拽排序 + 权限绑定) 基于 @dnd-kit 拖拽排序每行key/label/route/requiredPermission/scope/isVisible保存触发 PUT /api/v1/iam/viewports/:id
PlatformMonitor 平台监控Grafana iframe embed <iframe> 嵌入 Grafana 面板(按 serviceName 切换备选recharts 渲染 MonitoringMetricsViewModel

7.3 不使用的组件(明确排除)

组件 归属 排除原因
RichTextEditor teacher-portal 管理端无富文本场景
ExamTaking student-portal 管理端无作答场景
SSEViewer teacher-portal 管理端不消费 SSE
ChildSwitcher parent-portal 管理端无多子女切换

8. 共享 Hooks复用 Shell 暴露 + admin 特有)

8.1 复用 Shell 暴露的 Hooks

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

8.2 admin-portal 特有业务 Hooks新建

Hook 职责
useUsers(filter) 用户列表查询(含筛选)
useUser(userId) 用户详情
useCreateUser() 创建用户 mutation
useUpdateUser() 更新用户 mutation
useToggleUserStatus() 启用/禁用用户 mutation
useRoles(filter) 角色列表查询
useRole(roleId) 角色详情(含权限列表)
useCreateRole() 创建角色 mutation
useUpdateRolePermissions() 更新角色权限 mutationRolePermissionMatrix 用)
usePermissions() 全量权限列表30min 缓存)
useViewportsConfig(scope) 视口配置列表(可编辑)
useUpdateViewport() 更新视口配置 mutationViewportConfigEditor 用)
useOrganization(parentId) 组织树查询
useMonitoringMetrics() 平台监控指标60s 轮询)
useActiveSessionsStats() 用户活动统计5min 轮询)

9. 设计令牌三层(复用 Shell 暴露)

admin-portal 不重复建立设计令牌,通过 Shell 暴露的 packages/ui-tokens/ 消费。三层令牌结构见 teacher-portal 阶段2 §9

强制规则project_rules §3.10admin-portal 同样遵守):

  • 禁止 #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-* 或默认阶梯)

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

方向 对方服务 协议 接口/事件 用途 阶段
调用 api-gateway HTTP/REST /api/v1/* 代理(经 Shell rewrites 全部业务请求 P1+
被调用 前端不暴露接口给其他服务
消费 iam HTTP经 Gateway /iam/*(含用户/角色/权限/视口 CRUD 用户/角色/权限/视口管理 P2+
消费 teacher-bff HTTP经 Gateway /admin/* 平台监控、统计聚合 P6+
消费 msg HTTP经 Gateway /notifications/* 通知中心 P5+
依赖 coord 维护 packages/shared-proto TS 类型(仅 contracts 部分) P1+
依赖 coord 维护 packages/shared-ts(待建) ApiClient/Logger/通用工具 P6+
依赖 ai07 维护 packages/ui-tokens(待建) 三层设计令牌 P6+
依赖 ai07 维护 packages/ui-components(待建) shadcn + 共享组件 P6+
依赖 ai07 维护 packages/hooks(待建) usePermission/useAuth 等 P6+
依赖 coord 维护 packages/contracts(待建) Permissions 常量 + 类型 P6+
依赖 teacher-portal Shell Shell 暴露的 AppShell + 共享依赖 + rewrites 宿主环境 P6+

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

11. 风险与假设

11.1 假设

  1. 假设 coord 建立 packages/shared-tspackages/contracts:包含 ApiClient、Logger、Permissions 常量、通用类型。若 coord 未建立ai07 自行在 apps/teacher-portal/src/shared/ 内实现(已在 teacher-portal 阶段2 计划中admin-portal 通过 MF shared 消费。
  2. 假设 iam 提供用户/角色/权限/视口 CRUD 端点IAM_USER_READ/CREATE/UPDATEIAM_ROLE_READIAM_PERMISSION_READIAM_VIEWPORT_READ 及对应 REST 端点。当前已实现known-issues §2.3 iam
  3. 假设 teacher-portal Shell 已就绪AppShell + 共享依赖暴露 + MF 配置 + rewrites。admin-portal 是 P6 阶段,依赖 teacher-portal P2-P5 已完成。
  4. 假设 Next.js 14+ Module Federation 2.0 稳定@module-federation/nextjs-mf 在 Next.js App Router 下可用。若不稳定admin-portal 降级为独立部署(重复实现 AppShell
  5. 假设 Grafana 可嵌入 iframePlatformMonitor 组件用 <iframe> 嵌入 Grafana 面板。若 Grafana 配置了 X-Frame-Options: DENY,需协调 SRE 放开 iframe 嵌入白名单,或降级为 recharts 渲染监控指标。

11.2 技术风险

风险 影响 缓解
Shell 未就绪阻塞 admin admin-portal 是 P6 阶段,依赖 teacher-portal P2-P5 完成 P6 启动前确认 Shell 已暴露 AppShell + 共享依赖;若 Shell 延迟admin-portal 先用 mock Shell 开发
MF SSR 对齐复杂 Remote 在 SSR 时需 Shell 提供上下文 优先 CSRSSR 仅用于首屏 dashboardMF 2.0 支持 SSR
共享依赖版本漂移 Remote 与 Shell 的 react/react-dom 版本不一致导致运行时错误 MF shared.singleton: true + CI 检查版本对齐
Token 刷新竞态 多请求同时 401 触发多次 refresh 复用 Shell 的 ApiClient 全局单例 + refresh promise 复用
权限缓存陈旧 角色变更后前端 5min 内仍用旧权限 iam 角色变更后管理员手动 invalidateadmin-portal 本身就是管理端,可主动触发
数据范围越权 L3 校管理员看到 L5 系统管理员视角的数据 所有列表 API 由后端强制注入 dataScope filter前端 AdminDataScopeFilter 仅作 UI 提示
Grafana iframe 嵌入失败 PlatformMonitor 组件白屏 降级为 recharts 渲染 MonitoringMetricsViewModel
轮询对 BFF 压力 60s 监控轮询 + 5min 统计轮询增加 BFF 负载 5min TTL 缓存命中率高;监控指标直连 Grafana iframe 不经过 BFF

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

以下 4 项与 teacher-portal 阶段2 §11.3 相同admin-portal 跟随 teacher-portal 决策:

  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-P5 用 RESTP6 admin-portal 同样用 REST未来 BFF 切 GraphQL 再统一引入。
  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 自行决定内部布局。

12. coord 交叉审查所需信息

12.1 端口矩阵

dev 端口 生产端口 备注
admin-portal 3003 3003 Remote挂载到 Shell

full-stack-runbook 端口矩阵对齐。Shellteacher-portal在 3000其余 Remotestudent-portal 3001 / parent-portal 3002 / admin-portal 3003。

12.2 依赖的共享包

路径 维护方 内容
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

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

契约 提供方 当前状态
GET/POST/PUT /iam/usersGET /iam/rolesGET /iam/permissions iam 已实现
GET/PUT /iam/viewportsPUT /iam/viewports/:id iam 已实现
PUT /iam/roles/:id/permissions iam ⚠️ 待确认(角色-权限矩阵)
GET /admin/dashboardGET /admin/monitoring/metrics teacher-bff 已实现(复用)
GET /admin/stats/usersGET /admin/stats/active-sessions teacher-bff ⚠️ 待确认
POST /admin/web-vitals teacher-bff ⚠️ 待确认Web Vitals 上报端点)
GET /notifications/* msg 📐 待 P5
Grafana 面板 iframe 嵌入 SREinfra/grafana/ ⚠️ 待确认 X-Frame-Options

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

前端不产生错误码仅消费。admin-portal 涉及的前缀(需各服务确认不重叠):

前缀 服务 状态
IAM_ iam 已用
BFF_TEACHER_ teacher-bff ⚠️ 待确认
GW_ api-gateway 已用
NETWORK_ 前端 ai07 自有(复用 Shell

12.5 不产生 Kafka 事件

admin-portal 不发布/消费 Kafka 事件,不消费 WebSocket/SSE 推送。所有数据通过 HTTP 轮询获取。

13. 实施路线ai07 自用P6 阶段)

P6admin-portal 建站 + 硬化)

  1. apps/admin-portal/ 骨架Remote 角色)
    • next.config.js 配置 MF Remote见 §1.2
    • package.json 引入 @module-federation/nextjs-mf + 软链 @edu/* 共享包
    • tsconfig.json 沿用 tsconfig.base.json
    • tailwind.config.js 引入 @edu/ui-tokens
    • Dockerfile 多阶段构建builder + runtime
  2. 实现用户/角色/权限/视口/组织/监控管理页面
    • /admin/dashboard — 管理仪表盘recharts 渲染统计图表)
    • /admin/users + /admin/users/new + /admin/users/:id — 用户管理UserManagementTable + Form
    • /admin/roles — 角色管理RolePermissionMatrix
    • /admin/permissions — 权限管理DataTable 只读列表)
    • /admin/viewports — 视口配置ViewportConfigEditor
    • /admin/organization — 组织管理(树形 + DataTable
    • /admin/monitoring — 平台监控PlatformMonitor = Grafana iframe + recharts
  3. 实现 admin 特有业务 Hooks(见 §8.2
  4. 接入 5 层状态管理(复用 Shell 暴露的 nuqs/TanStack Query/Zustand/Zustand-UI/react-hook-form
  5. 接入 i18nnext-intladmin 命名空间)
  6. 接入权限校验usePermission() + <RequirePermission>,按 §6.1 路由表)
  7. Web Vitals + OTel browser SDK 接入(复用 Shell 的 TracerProviderscope='admin'
  8. A11y WCAG 2.2 AA 审计eslint-plugin-jsx-a11y error 级 + 手动审计)
  9. 性能优化MF shared 单例验证、bundle 分析、5min 长缓存验证)
  10. 补 Vitest 单测 + Playwright E2E(覆盖率 ≥ 80%
  11. /api/health + /api/ready Route Handler + Dockerfile HEALTHCHECK

P6 验收标准

  • 所有 9 个路由页面可访问,权限校验生效
  • MF shared 单例验证通过react/react-dom/Zustand/TanStack Query 不重复实例化)
  • Web Vitals 上报到 POST /api/v1/admin/web-vitals
  • OTel browser SDK trace 上报到 collector
  • A11y 审计 0 个 error 级违规
  • 测试覆盖率 ≥ 80%
  • /api/health + /api/ready 返回 200
  • Dockerfile 多阶段构建,非 root 用户HEALTHCHECK 配置

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