# 模块架构设计文档 — 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 拉取 |
| 长期监控面板 | `