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

671 lines
41 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 模块架构设计文档 — admin-portal
> AIai07TS/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-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.ts`admin-portal 通过 useApi 获取 |
### 1.2 MF 配置admin-portal/next.config.jsRemote 角色)
```javascript
// 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 作为 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 | 失效策略 |
| ----------------------- | ---------------------- | --------------------------- | -------------------------------------------------- |
| 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](./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<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 约定
```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<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.ts`coord 维护。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
```typescript
// 复用 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-vitals``POST /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
```typescript
// 复用 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](../../teacher-portal/docs/02-architecture-design.md#9-设计令牌三层packagesui-tokens待建ai07-维护)。
**强制规则**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-ts`、`packages/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/UPDATE``IAM_ROLE_READ``IAM_PERMISSION_READ``IAM_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 可嵌入 iframe**PlatformMonitor 组件用 `<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 REST**004 §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](../../../docs/standards/full-stack-runbook.md) 端口矩阵对齐。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/users``GET /iam/roles``GET /iam/permissions` | iam | ✅ 已实现 |
| `GET/PUT /iam/viewports``PUT /iam/viewports/:id` | iam | ✅ 已实现 |
| `PUT /iam/roles/:id/permissions` | iam | ⚠️ 待确认(角色-权限矩阵) |
| `GET /admin/dashboard``GET /admin/monitoring/metrics` | teacher-bff | ✅ 已实现(复用) |
| `GET /admin/stats/users``GET /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. **接入 i18n**next-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