# 模块架构设计文档 — admin-portal > AI:ai16(TS/React · 管理场景域前端 remote) > 阶段:阶段 2 交付物(仲裁后修订版 v2) > 日期:2026-07-10 > 关联: > > - [阶段 1 理解确认书](./01-understanding.md) > - [004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md) §5.4 > - [总统最终裁决](../../../docs/architecture/president-final-rulings.md) §5.1-5.5(ISSUE-044~048) > - [admin-portal 对接契约](../../architecture/issues/contracts/admin-portal_contract.md) > - [pending-features P6](../../../docs/architecture/roadmap/pending-features.md) > - [known-issues §2.16](../../../docs/troubleshooting/known-issues.md) > 状态:已实现(P2-P6 全阶段交付完成) --- ## 1. 模块内部分层图(admin-portal Remote + Shell 引用 + Gateway + push-gateway) ```mermaid graph TB subgraph Browser["浏览器(系统/校管理员)"] URL[URL 路由 /admin/*] end subgraph Shell["teacher-portal(Shell 宿主,端口 4000)"] AppShell[AppShell
左栏导航 + 主内容区
按 scope=admin 过滤视口] RootLayout[RootLayout
字体/令牌/i18n Provider] Router[Next.js App Router
动态加载 admin Remote] SharedDeps["共享依赖暴露(singleton)
react/react-dom/urql/graphql
@edu/ui-tokens/@edu/ui-components/@edu/hooks/@edu/contracts"] GraphQLProviderShell[GraphQLProvider
Shell 暴露 urql client 单例] end subgraph RemoteAdmin["admin-portal(Remote 子应用,端口 4003)"] AdminApp[AdminApp
MF Remote 入口 exposes ./AdminApp] AdminPages[管理场景页面
dashboard/users/roles/permissions/viewports/organization
classes/teachers/students/audit-logs/system] AdminComponents["admin 特有组件
UserManagementTable/RolePermissionMatrix
ViewportConfigEditor/OrganizationTree/NotificationPanel"] AdminHooks["admin 业务 Hooks
useUsers/useRoles/usePermissions/useViewports
useOrganization/useClasses/useTeachers/useStudents
useAuditLogs/useDashboard/useSystemSettings/useWebSocket"] end subgraph Gateway["api-gateway"] GW[Gin 路由/鉴权/限流] end subgraph PushGateway["push-gateway"] WS[WebSocket :8081/ws] end subgraph Backend["后端服务"] IAM[iam
用户/角色/权限/视口 CRUD + AuditEvent 发布] BFF[teacher-bff
GraphQL admin 命名空间 聚合] end Browser --> URL URL --> RootLayout RootLayout --> AppShell AppShell --> Router Router -->|动态加载 /admin/*| AdminApp AdminApp --> AdminPages AdminPages -->|消费 singleton| SharedDeps SharedDeps --> GraphQLProviderShell AdminPages --> AdminComponents AdminPages --> AdminHooks AdminHooks -->|useGraphQuery/useGraphMutation| GraphQLProviderShell GraphQLProviderShell -->|POST /api/admin/graphql| GW GW --> BFF BFF --> IAM AdminHooks -->|useWebSocket| WS IAM -.->|Kafka edu.iam.audit.created| BFF AppShell -->|fetch /api/auth/login| GW ``` ### 1.1 Remote 与 Shell 的职责边界 | 职责 | 归属 | 说明 | | -------------------------------- | -------------------- | ------------------------------------------------------------------------------------------ | | RootLayout(字体/令牌/Provider) | teacher-portal Shell | admin-portal MF 模式复用;standalone 模式自建 | | AppShell(左栏 + 主内容区) | admin-portal 内部 | `admin-shell.tsx`,11 项导航 + 跳过链接 A11y | | 路由表 `/admin/*` | admin-portal | `src/app/admin/*/page.tsx` | | 登录页 | admin-portal | standalone 模式 `src/app/login/page.tsx`(mock 登录);MF 模式复用 Shell `/login` | | rewrites `/api/admin/graphql` | admin-portal | `next.config.js` rewrites 代理到 api-gateway | | GraphQLProvider | Shell 暴露 / 自建 | MF 模式复用 Shell 单例(ARB-004);standalone 模式 `graphql-provider.tsx` 自建 urql client | | 管理场景页面 | admin-portal Remote | `/admin/*` 下的所有 page.tsx | | 管理特有组件 | admin-portal Remote | UserManagementTable 等 6 个 | | 管理业务 Hooks | admin-portal Remote | 13 个业务 Hook + useWebSocket | | 共享组件库 | Shell 暴露 | `@edu/ui-components` | | 共享 Hooks | Shell 暴露 | `@edu/hooks` | ### 1.2 MF 配置(admin-portal/next.config.js,Remote 角色) ```javascript const NextFederationPlugin = require("@module-federation/nextjs-mf"); const nextConfig = { reactStrictMode: true, transpilePackages: ["@edu/ui-tokens", "@edu/ui-components", "@edu/hooks"], async rewrites() { const gatewayUrl = process.env.API_GATEWAY_URL || "http://localhost:8080"; return [ { source: "/api/admin/graphql", destination: `${gatewayUrl}/api/admin/graphql`, }, { source: "/api/:path*", destination: `${gatewayUrl}/api/:path*` }, ]; }, webpack(config, { isServer }) { if (process.env.NEXT_PUBLIC_MF_ENABLED === "true") { config.plugins.push( new NextFederationPlugin({ name: "admin_app", filename: "static/chunks/remoteEntry.js", exposes: { "./AdminApp": "./src/app/admin-app.tsx" }, remotes: { teacher: `teacher_app@http://localhost:4000/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`, }, shared: { react: { singleton: true, requiredVersion: "^18.3.0" }, "react-dom": { singleton: true, requiredVersion: "^18.3.0" }, urql: { singleton: true }, graphql: { singleton: true }, "@edu/ui-tokens": { singleton: true }, "@edu/ui-components": { singleton: true }, "@edu/hooks": { singleton: true }, }, extraOptions: { exposePages: false }, }), ); } return config; }, }; module.exports = nextConfig; ``` > **说明**: > > - `NEXT_PUBLIC_MF_ENABLED` 环境变量控制 MF 启用(standalone 模式默认 `false`,MF 模式设 `true`) > - admin-portal 暴露 `./AdminApp` 模块(非旧文档的 `./pages`),供 Shell 动态 import > - `shared` 全部 `singleton: true`,确保 urql/graphql/react 单例(避免多实例报错) > - admin-portal 自身实现 `rewrites`(代理 `/api/admin/graphql` 到 api-gateway),standalone 模式可独立运行 ## 2. 领域模型(前端视角) 前端不持有业务聚合根,仅持有"视图模型"(ViewModel)和"会话状态"。定义于 `src/types/view-models.ts`。 ### 2.1 视图模型清单 ```typescript // 数据范围 type DataScope = "ALL" | "SCHOOL" | "GRADE" | "CLASS" | "DISTRICT"; // 用户管理 interface UserViewModel { id: string; email: string; name: string; roles: { id: string; name: string; code: string }[]; status: "active" | "disabled" | "locked"; dataScope: DataScope; organizationId: string | null; schoolName?: string; lastLoginAt: number | null; createdAt: number; updatedAt: number; } // 角色管理 interface RoleViewModel { id: string; name: string; code: string; description: string; permissions: PermissionViewModel[]; userCount: number; dataScope: DataScope; isSystem: boolean; createdAt: number; updatedAt: number; } // 权限管理 interface PermissionViewModel { id: string; code: string; name: string; description: string; resource: string; action: string; isSystem: boolean; } // 视口配置 interface ViewportConfigViewModel { id: string; key: string; label: string; route: string; icon: string | null; sortOrder: number; requiredPermission: string | null; scope: "teacher" | "student" | "parent" | "admin"; isVisible: boolean; } // 组织树 interface OrganizationNode { id: string; name: string; type: "school" | "grade" | "class"; parentId: string | null; childrenCount: number; path: string; } // 学校设置 interface SystemSettingsViewModel { schoolName: string; schoolYear: string; semester: string; timezone: string; locale: string; // ... 更多设置项 } // 班级/教师/学生(admin 全局视角) interface AdminClassViewModel { id: string; name: string; grade: string; headTeacher: string; studentCount: number; status: string; } interface AdminTeacherViewModel { id: string; email: string; name: string; subjects: string[]; classes: string[]; status: string; } interface AdminStudentViewModel { id: string; email: string; name: string; className: string; grade: string; status: string; } // 审计日志 interface AuditLogViewModel { id: string; userId: string; userName: string; action: string; resourceType: string; resourceId: string; beforeState: string | null; afterState: string | null; ip: string; userAgent: string; occurredAt: number; } // 管理仪表盘 interface AdminDashboardViewModel { totalUsers: number; totalTeachers: number; totalStudents: number; totalClasses: number; activeSessions: number; serviceHealth: { serviceName: string; status: "healthy" | "degraded" | "down"; latencyMs: number; }[]; recentActivity: { timestamp: number; action: string; user: string; resource: string; }[]; } // WebSocket 通知 interface WsNotification { id: string; type: "audit_alert" | "abnormal_login" | "system_error" | "info"; severity: "info" | "warning" | "error"; title: string; message: string; timestamp: number; } ``` ### 2.2 当前用户与会话 ```typescript interface CurrentUser { id: string; email: string; name: string; roles: string[]; permissions: string[]; dataScope: DataScope; schoolId: string | null; schoolName: string | null; } ``` 会话状态由 `AuthProvider` 管理,token 存 `localStorage`(`edu_access_token`),用户信息存 `localStorage`(`edu_user`)。 ## 3. 数据模型(前端缓存层) admin-portal 无数据库,仅有 urql GraphQL client 缓存层。 | 数据类型 | 存储 | 缓存策略 | | ----------------------- | ---------------------------- | --------------------------------------------- | | Session(token + user) | localStorage + React Context | access token 持久化;用户信息持久化;登出清除 | | GraphQL 查询缓存 | urql document cache | 默认缓存,mutation 后手动 invalidate | | 视口配置(拖拽态) | React useState | 本地编辑,保存时批量 mutation | | 表单临时态 | react-hook-form | 卸载即销毁 | | WebSocket 通知 | React useState(限 20 条) | 新通知头部插入,超 20 条裁剪 | > **缓存策略说明**:admin 数据低频变,urql 默认 document cache 已满足;监控指标经 WebSocket 实时推送(不再轮询);视口配置支持本地编辑 + 批量保存。 ## 4. API 设计(GraphQL 为主) ### 4.1 GraphQL Client(urql,ARB-004) **standalone 模式**(`src/lib/graphql-client.ts`): ```typescript import { createClient, fetchExchange, type Client } from "@urql/core"; const GRAPHQL_ENDPOINT = "/api/admin/graphql"; export function createGraphQLClient(): Client { return createClient({ url: GRAPHQL_ENDPOINT, exchanges: [fetchExchange], fetchOptions: () => { const token = typeof window !== "undefined" ? localStorage.getItem("edu_access_token") : null; return token ? { headers: { Authorization: `Bearer ${token}` } } : {}; }, }); } ``` **MF 模式**:复用 Shell 暴露的 `GraphQLProvider` + `useGraphQLClient`(singleton,ARB-004)。 **urql 类型断言技巧**:urql 泛型与纯字符串 query 兼容性有限,用 `as unknown as` 从 unknown 转换: ```typescript // use-graphql.ts const [executeMutation] = useMutation(mutation) as unknown as [ (variables: V) => { toPromise: () => Promise> }, unknown, ]; ``` ### 4.2 通用 GraphQL Hooks(`src/hooks/use-graphql.ts`) ```typescript export function useGraphQuery>( query: string, variables?: V, ): QueryResult { ... } export function useGraphMutation( mutation: string, ): [MutationFn, MutationResult] { ... } ``` ### 4.3 业务 Hooks 清单 | Hook | 职责 | | ---------------------------- | ------------------------------------------------ | | `useUsers(filter)` | 用户列表查询(含筛选/分页) | | `useUser(userId)` | 用户详情 | | `useCreateUser()` | 创建用户 mutation | | `useUpdateUser()` | 更新用户 mutation | | `useToggleUserStatus()` | 启用/禁用用户 mutation | | `useUserFilter()` | 用户筛选状态(搜索/角色/状态/组织) | | `useRoles()` | 角色列表查询 | | `useCreateRole()` | 创建角色 mutation | | `useUpdateRolePermissions()` | 更新角色权限 mutation(RolePermissionMatrix 用) | | `usePermissions()` | 全量权限列表 | | `useViewports()` | 视口配置列表 | | `useUpdateViewport()` | 更新视口配置 mutation | | `useOrganization(parentId)` | 组织树查询(按 parentId 递归) | | `useClasses(filter)` | 班级列表查询 | | `useClassFilter()` | 班级筛选状态 | | `useTeachers(filter)` | 教师列表查询 | | `useTeacherFilter()` | 教师筛选状态 | | `useStudents(filter)` | 学生列表查询 | | `useStudentFilter()` | 学生筛选状态 | | `useAuditLogs(filter)` | 审计日志查询 | | `useAuditLogFilter()` | 审计日志筛选状态 | | `exportAuditLogsCsv(logs)` | 审计日志 CSV 导出(BOM + UTF-8) | | `useDashboard()` | 管理仪表盘聚合查询 | | `useSystemSettings()` | 学校设置查询 | | `useUpdateSystemSettings()` | 学校设置更新 mutation | | `useWebSocket(maxItems)` | WebSocket 实时通知(mock 模式 30s 定时推送) | ## 5. 事件设计 ### 5.1 WebSocket 实时通知(ARB-006) admin-portal 接入 push-gateway `GET /ws`,消费 3 类通知: ```typescript // src/hooks/use-websocket.ts export function useWebSocket(maxItems = 20) { // 真实模式:连接 push-gateway NEXT_PUBLIC_WS_URL // mock 模式:30s 定时器模拟推送 // 返回:{ notifications, connected, dismiss, clear } } ``` | 通知类型 | severity | 触发场景 | | ---------------- | -------- | ----------------------------- | | `audit_alert` | warning | 敏感操作(权限变更/批量删除) | | `abnormal_login` | error | 异地登录/异常时段登录 | | `system_error` | error | 服务降级/宕机 | ### 5.2 不消费 Kafka admin-portal 不直接订阅 Kafka。审计日志经 teacher-bff 聚合后通过 GraphQL `auditLogs` Query 消费(ARB-005): **链路**:iam → Kafka `edu.iam.audit.created` → **teacher-bff 消费** → GraphQL `auditLogs` Query → admin-portal > **不再用轮询**:旧文档的"60s 轮询监控 + 5min 轮询统计"已被 ARB-006 的 WebSocket 推送替换。 ## 6. 横切关注点对齐清单 ### 6.1 权限(前端等价,ARB-003) 权限点常量定义于 `src/lib/permissions.ts`: - `ADMIN_PERMISSIONS`:admin 自身资源(`ADMIN_DASHBOARD_VIEW` / `ADMIN_SYSTEM_MANAGE` / `ADMIN_AUDIT_READ` / `ADMIN_CLASS_READ` / `ADMIN_TEACHER_READ` / `ADMIN_STUDENT_READ` / `ADMIN_ORG_MANAGE` 等) - `IAM_PERMISSIONS`:跨服务资源(`IAM_USER_READ` / `IAM_ROLE_READ` / `IAM_PERMISSION_READ` / `IAM_VIEWPORT_READ` 等) - `ROUTE_PERMISSIONS`:11 路由 → 权限点映射 权限校验:`usePermission().hasPermission("XXX")` Hook + `AuthGuard` 组件(路由级)。 ### 6.2 错误码清单(前端 i18n 路由,ARB-002) | 前缀 | 来源服务 | 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_` | admin 域 | `error.admin.*` | ### 6.3 Web Vitals(`src/lib/web-vitals.ts`) | 指标 | 类型 | 上报方式 | | ---- | -------------- | ---------------------- | | LCP | 最大内容绘制 | `navigator.sendBeacon` | | CLS | 累积布局偏移 | 同上 | | FCP | 首次内容绘制 | 同上 | | INP | 交互到下一绘制 | 同上 | | TTFB | 首字节时间 | 同上 | 仅 production 启用(`WebVitalsInitializer` 组件控制)。 ### 6.4 健康检查 | 端点 | 用途 | 实现 | | ----------------- | --------- | --------------------------------------------------- | | `GET /api/health` | Liveness | 返回 `200 { status: "ok" }` | | `GET /api/ready` | Readiness | 检查 gateway 可达性(2s 超时);mock 模式直接 ready | ### 6.5 A11y(WCAG 2.2 AA) - `eslint-plugin-jsx-a11y`(error 级) - skip-link(`admin-shell.tsx` 跳过到主内容区) - `focus-visible` 样式(`globals.css`) - 组织树:`