# 模块架构设计文档 — admin-portal > AI:ai07(TS/React · 管理场景域前端 remote) > 阶段:阶段 2 交付物 > 日期:2026-07-09 > 关联:[阶段 1 理解确认书](./01-understanding.md)、[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md) §5.4、[pending-features P6](../../../docs/architecture/roadmap/pending-features.md)、[teacher-portal 阶段2](../../teacher-portal/docs/02-architecture-design.md) > 状态:待 coord 交叉审查 --- ## 1. 模块内部分层图(简化版:admin-portal Remote + Shell 引用 + Gateway) ```mermaid graph TB subgraph Browser["浏览器(系统/校管理员)"] URL[URL 路由 /admin/*] end subgraph Shell["teacher-portal(Shell 宿主)"] AppShell[AppShell
左栏导航 + 主内容区
按 scope=admin 过滤视口] RootLayout[RootLayout
字体/令牌/i18n Provider
TanStack QueryClientProvider
Zustand StoreProvider] Router[Next.js App Router
动态加载 admin Remote] SharedDeps["共享依赖暴露(singleton)
react/react-dom/@tanstack/react-query/zustand/nuqs
ui-components/ui-tokens/contracts/hooks/shared-ts"] Rewrites["rewrites /api/v1/* → api-gateway"] end subgraph RemoteAdmin["admin-portal(Remote 子应用)"] AdminPages[管理场景页面
dashboard/users/roles/permissions/viewports/organization/monitoring] AdminComponents["admin 特有组件
UserManagementTable/RolePermissionMatrix/ViewportConfigEditor/PlatformMonitor"] AdminHooks["admin 业务 Hooks
useUsers/useRoles/usePermissions/useViewports/useMonitoring"] end subgraph Shared["共享层(packages/,由 Shell 暴露)"] UITokens[ui-tokens
三层设计令牌] UIComponents[ui-components
shadcn + A11y + ErrorBoundary + RequirePermission] Contracts[contracts
Permissions 常量 + 类型] Hooks[hooks
usePermission/useAuth/useViewports/useApi] LibTS[shared-ts
ApiClient/Logger/Tracer] end subgraph Gateway["api-gateway"] GW[Gin 路由/鉴权/限流] end subgraph Backend["后端服务"] IAM[iam
用户/角色/权限/视口 CRUD] BFF[teacher-bff
/admin/* 聚合] MSG[msg
/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 通过 `` 渲染管理端视口 | | 路由表 `/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.ts`,admin-portal 通过 useApi 获取 | ### 1.2 MF 配置(admin-portal/next.config.js,Remote 角色) ```javascript // admin-portal/next.config.js(Remote) 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 不暴露任何模块给 Shell(Shell 通过 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 作为 Remote,`name: '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 共享) **Session**、**Viewport**、**Permission** 三个模型与 teacher-portal 完全共享,定义和存储策略见 [teacher-portal 阶段2 §2.1-2.3](../../teacher-portal/docs/02-architecture-design.md#2-领域模型前端视角)。 admin-portal 作为 Remote,通过 Shell 暴露的 `useAuth()` / `usePermission()` / `useViewports('admin')` Hook 消费这些模型,**不重复定义**。 ### 2.2 管理场景视图模型(admin-portal 特有) ```typescript // 用户管理视图模型 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 特有) ```typescript // 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 | 失效策略 | | ----------------------- | ---------------------- | --------------------------- | -------------------------------------------------- | | Session(token + user) | localStorage + Zustand | access 15min / refresh 7day | 401 自动 refresh,refresh 失败跳登录(复用 Shell) | | 权限列表 | TanStack Query cache | 5min | 角色变更主动 invalidate(复用 Shell) | | 视口列表 | TanStack Query cache | 5min | 视口配置变更主动 invalidate | | 用户列表 | TanStack Query cache | 5min | staleTime 5min,mutation 后 invalidate | | 角色列表 | TanStack Query cache | 5min | staleTime 5min,mutation 后 invalidate | | 权限列表(全量) | TanStack Query cache | 30min | staleTime 30min(权限点极少变更) | | 视口配置列表 | TanStack Query cache | 5min | staleTime 5min,mutation 后 invalidate | | 组织树 | TanStack Query cache | 5min | staleTime 5min | | 平台监控指标 | TanStack Query cache | 60s | staleTime 60s(refetchInterval 60s 轮询) | | 用户活动统计 | TanStack Query cache | 5min | staleTime 5min(refetchInterval 5min 轮询) | | URL 状态(分页/筛选) | nuqs | — | 永久(可分享) | | 表单临时态 | react-hook-form | — | 卸载即销毁 | > **缓存策略说明**:管理数据(用户/角色/权限/视口/组织)低频变更,5min 长缓存减少 BFF 压力;监控指标需要相对实时,60s 轮询;权限点常量几乎不变,30min 长缓存。所有 mutation 成功后主动 invalidate 对应 queryKey,确保 UI 立即刷新。 ## 4. API 设计(前端 → 后端) 前端不设计后端 API,仅声明消费的端点。详见 [01-understanding.md §3.1](./01-understanding.md#31-消费的后端-api经-api-gateway-代理)。 ### 4.1 统一 API 请求层(复用 Shell 暴露的 ApiClient) admin-portal **不重复实现** ApiClient,通过 Shell 暴露的 `useApi()` Hook 获取 ApiClient 实例。ApiClient 定义见 [teacher-portal 阶段2 §4.1](../../teacher-portal/docs/02-architecture-design.md#41-统一-api-请求层libapits)。 ```typescript // 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("/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 约定 ```typescript // 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("/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-gateway,HTTP 拉取 | | 长期监控面板 | `