Files
Edu/apps/admin-portal/docs/01-understanding.md
SpecialX b3511910d1 feat(admin-portal): 完整实现 admin-portal 管理端微前端
包含 src 全部实现、Dockerfile、配置文件等
2026-07-10 19:09:12 +08:00

285 lines
21 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
> AIai16TS/React · 管理场景域前端 remote
> 阶段:阶段 1 交付物(仲裁后修订版 v2
> 日期2026-07-10
> 关联:
>
> - [004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md) §1.1a/1.1b/§5.4
> - [AI 分配方案](../../../docs/architecture/ai-allocation.md) §5 ai16
> - [总统最终裁决](../../../docs/architecture/president-final-rulings.md) §5.1-5.5ISSUE-044~048
> - [admin-portal 对接契约](../../architecture/issues/contracts/admin-portal_contract.md)
> - [admin-portal 工作线](../../architecture/issues/worklines/admin-portal_workline.md)
> - [known-issues §2.16](../../../docs/troubleshooting/known-issues.md)
---
## 0. 仲裁结果摘要ARB-001~006
| 编号 | 议题 | 裁决 |
| -------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| ARB-001 | BFF 归属ISSUE-044 | 复用 teacher-bff GraphQL endpoint不新建 admin-bffai03 预留 `admin.*` 命名空间 |
| ARB-002 | 错误码前缀ISSUE-045 | `ADMIN_` 前缀(合并 `SCHOOL_`/`ORG_``ADMIN_`i18n key `error.admin.*` |
| ARB-003 | 权限点命名ISSUE-046 | admin 自身资源用 `ADMIN_*`;跨服务资源保留原前缀(`IAM_USER_READ` 等) |
| ARB-004 | GraphQL 客户端ISSUE-047 | MF 模式复用 Shell 暴露的 GraphQL client 单例standalone 模式自建 urql client |
| ARB-005 | 审计日志归属ISSUE-048 | 审计事件归 iam 发布到 `edu.iam.audit.created`teacher-bff 消费聚合admin-portal 经 GraphQL `auditLogs` Query 消费 |
| ARB-006 | WebSocket 实时通知 | 接入 push-gateway `GET /ws`(审计告警/异常登录/系统异常),不再用轮询 |
| 端口分配 | admin-portal dev server | **4003**(非旧文档的 3003 |
---
## 1. 我在架构中的位置
- **层级**L2 微前端层004 §3.1 六层架构中的前端层)
- **MF 角色****Remote 子应用**,挂载到 teacher-portal Shell主应用端口 4000
- **上游(谁调用我)**:浏览器(系统管理员 / 校管理员)
- **下游(同步)**api-gatewayGraphQL `POST /api/admin/graphql` + REST `/api/auth/login`
- **下游(推送)**push-gateway `GET /ws`WebSocket 实时通知ARB-006
- **BFF 对接**:复用 teacher-bff GraphQL admin 命名空间ARB-001不新建 admin-bff
- **通信方式**GraphQL业务数据+ HTTP登录+ WebSocket实时通知
- **不直连**:前端不直连任何业务服务或 BFF 后端实例,全部经 api-gateway 代理
**说明**
- admin-portal 作为 Remote 子应用,挂载到 teacher-portal Shell
- Shell 暴露 `GraphQLProvider` / `useGraphQLClient` / `AppShell` / `useAuth` / `usePermission`admin-portal MF 模式复用standalone 模式自建
- 路由前缀 `/admin/*` 由 teacher-portal Shell 动态加载 admin-portal Remote 模块(暴露 `./AdminApp`
- 场景域 BFF 复用策略004 §5.4 + ARB-001复用 teacher-bff GraphQL endpointadmin 命名空间(`admin.*` Query/Mutation
## 2. 我的限界上下文
### 2.1 我负责的聚合 / 实体(前端视图模型)
- 用户、角色、权限、视口(管理场景域前端视图,调 iam 经 GraphQL
- 组织(学校/年级/班级层级)
- 学校设置system 路由)
- 班级/教师/学生全局管理admin 视角,只读 + 状态管理)
- 审计日志(只读,经 teacher-bff GraphQL `auditLogs` Query 消费)
- 管理仪表盘(聚合统计 + 服务健康)
- 实时通知WebSocket 推送:审计告警/异常登录/系统异常)
### 2.2 业务领域
- **管理场景域**(前端场景域:管理场景域,对应后端 iam 限界上下文 + teacher-bff admin 命名空间)
### 2.3 不负责
- 教学业务编排(班级/考试/作业/成绩 CRUD归 teacher-portal
- 学生作答界面(归 student-portal
- 家长多子女切换(归 parent-portal
- 教师仪表盘(归 teacher-portaladmin-portal 仅有管理仪表盘)
- 审计事件发布(归 iamadmin-portal 仅消费)
### 2.4 数据范围
- DataScope L3-L5校管理员 L3 学校 / 区教研员 L4 / 系统管理员 L5 全平台)
- 不出现 L1班级/ L2年级级别的教师数据视角
## 3. 我与外部的契约
### 3.1 消费的后端 API经 api-gateway 代理)
| 路径 | Method | 下游 BFF/服务 | 用途 |
| ------------------------- | ------ | ----------------------------- | ------------------------------------------------- |
| `/api/admin/graphql` | POST | teacher-bffadmin 命名空间) | 全部业务 GraphQL 查询ARB-001 |
| `/api/auth/login` | POST | api-gateway → iam | 登录standalone 模式用MF 模式复用 Shell 登录) |
| `GET /ws`push-gateway | WS | push-gateway | 实时通知(审计告警/异常登录/系统异常ARB-006 |
### 3.2 GraphQL Operationscontract §2.4
admin-portal 消费 teacher-bff admin 命名空间的 18 个 GraphQL operations
| Operation | 类型 | 用途 |
| ------------------------------------------------ | -------------- | ------------------------------- |
| `currentUser` | Query | 当前管理员信息 |
| `adminUsers` / `adminUser(id)` | Query | 用户列表 / 详情 |
| `createUser` / `updateUser` / `toggleUserStatus` | Mutation | 用户 CRUD |
| `adminRoles` | Query | 角色列表(含权限) |
| `createRole` / `updateRolePermissions` | Mutation | 角色 CRUD + 权限矩阵 |
| `adminPermissions` | Query | 全量权限点(按 resource |
| `adminViewports(scope)` | Query | 视口配置列表 |
| `updateViewport` | Mutation | 视口配置更新 |
| `adminOrganization(parentId)` | Query | 组织树 |
| `adminClasses` | Query | 班级管理(全局) |
| `adminTeachers` | Query | 教师管理 |
| `adminStudents` | Query | 学生管理 |
| `auditLogs(filter)` | Query | 审计日志(聚合 iam AuditEvent |
| `adminDashboard` | Query | 管理员仪表盘聚合 |
| `systemSettings` / `updateSystemSettings` | Query/Mutation | 学校设置 |
### 3.3 推送契约ARB-006
admin-portal 接入 push-gateway `GET /ws`,消费 3 类实时通知:
| 通知类型 | severity | 用途 |
| ---------------- | -------- | ------------------------- |
| `audit_alert` | warning | 审计告警(敏感操作触发) |
| `abnormal_login` | error | 异常登录(异地/异常时段) |
| `system_error` | error | 系统异常(服务降级/宕机) |
> **不再用轮询**:旧文档的"60s 轮询监控 + 5min 轮询统计"策略已被 ARB-006 替换为 WebSocket 实时推送。开发期用 mock-socket + 30s 定时器模拟推送。
### 3.4 proto 不直接消费
前端不调用 gRPCteacher-bff 把 gRPC 聚合为 GraphQL 暴露给前端。前端仅消费 `packages/contracts/src/permissions.ts` 中的权限点常量TS 文件,非 proto 生成)。
## 4. 我的技术栈
| 维度 | 选型 | 说明 |
| ---------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| 框架 | Next.js 14+App Router | server components 默认client components 按需 |
| 语言 | TypeScript 5.5+strict | 沿用 tsconfig.base.json |
| 微前端 | Module Federation 2.0@module-federation/nextjs-mf | admin-portal = **Remote**,端口 4003 |
| 数据层 | urql GraphQL client | standalone 自建 / MF 复用 Shell 单例ARB-004 |
| 样式 | Tailwind CSS 3.4+ | 配合设计令牌三层(复用 Shell |
| UI 组件库 | shadcn/ui 风格(复用 Shell `@edu/ui-components` | admin-portal 内部 `ui.tsx` 封装 PaperCard/Button/Table 等纸面组件 |
| 表单 | react-hook-form + zod | 用户表单/学校设置 |
| 拖拽排序 | @dnd-kit/sortable | 视口配置拖拽排序 |
| 图表 | recharts | 仪表盘趋势表(实际以纯表格为主,纸面风格) |
| i18n | 轻量 i18n`messages` 字典 + `t()` 函数) | 命名空间 `admin.*`,不依赖 next-intl |
| Mock | MSWMock Service Worker+ mock-socket | 开发期拦截全部 GraphQL/HTTP + WebSocket mock |
| Web Vitals | web-vitals v4 | LCP/CLS/FCP/INP/TTFB 采集,`navigator.sendBeacon` 上报 |
| A11y | eslint-plugin-jsx-a11yerror 级) | WCAG 2.2 AA |
| 字体 | Intersans/ Frauncesserif/ JetBrains Monomono | RootLayout 加载standaloneMF 模式复用 Shell |
| 测试 | Vitest + @testing-library/react | jsdom 环境,覆盖率 ≥ 80% |
| ESLint | ESLint 9 flat config | jsx-a11y + tseslint + prettier`@next/eslint-plugin-next` 14.x 与 ESLint 9 不兼容,已移除) |
> **技术栈差异**vs teacher-portalMF 角色Remote、GraphQL 为主(非 REST、消费 WebSocket 推送(非 SSE、纸面 UI 风格、数据范围 L3-L5。
## 5. 我的阶段归属
- **阶段**P6硬化阶段批次 5
- **当前状态**:✅ **已实现**P2-P6 全阶段交付完成,含 11 路由 + MSW mock + A11y + Web Vitals + Dockerfile
- **依赖上游阶段**P6依赖 teacher-portal Shell 已就绪 + teacher-bff GraphQL admin 命名空间 + iam 已实现用户/角色/权限/视口 CRUD + push-gateway WebSocket
## 6. 路由表11 路由contract §1.2
| 路由 | 页面 | 权限点 | 数据范围 |
| --------------------- | ---------------- | ---------------------- | -------- |
| `/admin/dashboard` | 管理员仪表盘 | `ADMIN_DASHBOARD_VIEW` | L3-L5 |
| `/admin/users` | 用户管理 | `IAM_USER_READ` | L3-L5 |
| `/admin/roles` | 角色权限管理 | `IAM_ROLE_READ` | L3-L5 |
| `/admin/permissions` | 权限点管理 | `IAM_PERMISSION_READ` | L5 |
| `/admin/viewports` | 视口配置 | `IAM_VIEWPORT_READ` | L5 |
| `/admin/organization` | 组织管理 | `ADMIN_ORG_MANAGE` | L3-L5 |
| `/admin/system` | 学校设置 | `ADMIN_SYSTEM_MANAGE` | L5 |
| `/admin/classes` | 班级管理(全局) | `ADMIN_CLASS_READ` | L3-L5 |
| `/admin/teachers` | 教师管理 | `ADMIN_TEACHER_READ` | L3-L5 |
| `/admin/students` | 学生管理 | `ADMIN_STUDENT_READ` | L3-L5 |
| `/admin/audit-logs` | 审计日志 | `ADMIN_AUDIT_READ` | L5 |
> 权限点前缀规则ARB-003admin 自身资源用 `ADMIN_*`;跨服务资源保留原前缀(如 `IAM_USER_READ`)。`ADMIN_*` 常量由 coord 维护于 `packages/contracts/src/permissions.ts`admin-portal 引用 `src/lib/permissions.ts`,不硬编码。
## 7. 黄金模板对齐清单(前端等价)
| 对齐项 | 后端等价 | admin-portal 实现 | 状态 |
| --------------------- | ------------------------------------- | ------------------------------------------------------------- | ---- |
| 权限校验 | `@RequirePermission(Permissions.XXX)` | `usePermission().hasPermission("XXX")` Hook + `AuthGuard` | ✅ |
| 错误码前缀 | `IAM_*` / `ADMIN_*` | `CONSUMED_ERROR_PREFIXES` 常量 + i18n 路由 | ✅ |
| logger | pino | console + 结构化(生产 → 上报) | ✅ |
| metrics | prom-client `/metrics` | Web Vitals v4 → `navigator.sendBeacon` 上报 | ✅ |
| /health + /ready | `GET /healthz` `GET /readyz` | Next.js Route Handler `/api/health` + `/api/ready` | ✅ |
| 优雅关闭 | SIGTERM handler | N/ANext.js 无长连接) | ✅ |
| 测试覆盖率 ≥ 80% | Vitest | Vitest + @testing-library/react | ⚠️ |
| Dockerfile 多阶段构建 | builder + runtime | `node:22-alpine` 多阶段standalone 输出EXPOSE 4003 | ✅ |
| Zod 输入验证 | class-validator + Zod schema | react-hook-form + zod | ✅ |
| GlobalErrorFilter | NestJS 全局异常过滤器 | React ErrorBoundary + Toast 错误处理 | ✅ |
| 设计令牌三层 | — | `globals.css`primitive/semantic+ tailwind-theme | ✅ |
| A11y 工具集 | — | eslint-plugin-jsx-a11yerror 级)+ skip-link + focus-visible | ✅ |
| Module Federation | — | `next.config.js` Remote 角色,`NEXT_PUBLIC_MF_ENABLED` 控制 | ✅ |
| MSW mock | — | 18 GraphQL operation + `/api/auth/login` mock | ✅ |
---
## 附admin-portal 实现清单v2仲裁后修订
### 文件结构
```
apps/admin-portal/
├─ package.json # 依赖urql/msw/@dnd-kit/web-vitals/eslint-plugin-jsx-a11y
├─ next.config.js # MF Remote 配置 + rewritesGraphQL 代理)
├─ tsconfig.json # extends tsconfig.base.json
├─ tailwind.config.js # paper/ink/accent/rule 色板
├─ eslint.config.js # ESLint 9 flat configjsx-a11y + tseslint + prettier
├─ Dockerfile # 多阶段构建node:22-alpinestandalone
├─ vitest.config.ts # jsdom + coverage ≥80%
├─ .env.example # 环境变量模板
├─ public/mockServiceWorker.js # MSW worker
└─ src/
├─ app/
│ ├─ globals.css # 设计令牌 + A11y 样式
│ ├─ layout.tsx # RootLayout字体加载
│ ├─ page.tsx # 根页面redirect /admin/dashboard
│ ├─ admin-app.tsx # MF Remote 入口exposes ./AdminApp
│ ├─ login/page.tsx # 登录页standalone mock 登录)
│ ├─ admin/
│ │ ├─ layout.tsx # admin 路由组 layoutProvider 链 + AuthGuard + AdminShell
│ │ ├─ dashboard/page.tsx # 管理仪表盘
│ │ ├─ users/page.tsx # 用户管理
│ │ ├─ roles/page.tsx # 角色权限
│ │ ├─ permissions/page.tsx # 权限点管理
│ │ ├─ viewports/page.tsx # 视口配置(@dnd-kit 拖拽)
│ │ ├─ organization/page.tsx # 组织管理
│ │ ├─ system/page.tsx # 学校设置
│ │ ├─ classes/page.tsx # 班级管理
│ │ ├─ teachers/page.tsx # 教师管理
│ │ ├─ students/page.tsx # 学生管理
│ │ └─ audit-logs/page.tsx # 审计日志CSV 导出)
│ └─ api/
│ ├─ health/route.ts # Liveness 检查
│ └─ ready/route.ts # Readiness 检查
├─ components/
│ ├─ ui.tsx # 通用 UIPaperCard/Button/Input/Select/Badge/Table/Pagination
│ ├─ admin-shell.tsx # AdminShell左栏 11 项导航 + 主内容 + 跳过链接)
│ ├─ user-management-table.tsx # 用户管理表格
│ ├─ user-form-modal.tsx # 用户表单弹窗
│ ├─ role-permission-matrix.tsx# 角色权限矩阵
│ ├─ organization-tree.tsx # 组织树A11y treeitem
│ ├─ notification-panel.tsx # 通知面板WebSocket
│ ├─ msw-initializer.tsx # MSW 初始化
│ └─ web-vitals-initializer.tsx# Web Vitals 初始化
├─ hooks/
│ ├─ use-graphql.ts # useGraphQuery + useGraphMutation
│ ├─ use-users.ts # 用户 CRUD
│ ├─ use-roles.ts # 角色 CRUD
│ ├─ use-permissions.ts # 权限点查询
│ ├─ use-viewports.ts # 视口配置
│ ├─ use-organization.ts # 组织树
│ ├─ use-classes.ts # 班级
│ ├─ use-teachers.ts # 教师
│ ├─ use-students.ts # 学生
│ ├─ use-audit-logs.ts # 审计日志 + CSV 导出
│ ├─ use-dashboard.ts # 仪表盘
│ ├─ use-system-settings.ts # 学校设置
│ └─ use-websocket.ts # WebSocket 实时通知
├─ providers/
│ ├─ graphql-provider.tsx # GraphQLProviderstandalone 自建 urql client
│ ├─ auth-provider.tsx # AuthProvider + useAuth + usePermission
│ └─ toast-provider.tsx # ToastProvider + useToast
├─ lib/
│ ├─ graphql-client.ts # urql client + 18 个 GraphQL operations
│ ├─ auth.ts # 认证工具
│ ├─ permissions.ts # 权限常量ADMIN_ + IAM_
│ ├─ i18n.ts # 轻量 i18n
│ └─ web-vitals.ts # Web Vitals 采集
├─ types/
│ └─ view-models.ts # 视图模型类型定义
└─ mocks/
├─ browser.ts # MSW Browser Worker
├─ server.ts # MSW Node Servervitest
├─ fixtures.ts # mock 数据库
└─ handlers.ts # MSW handlers18 GraphQL + login
```
### 质量校验
- `pnpm run typecheck` → ✅ 通过0 errors
- `pnpm run lint` → ✅ 通过0 errors
---
**AI Agent**: ai16 (admin-portal remote)
**Branch**: admin-portal-arbitration-done-TaFkyb
**Coordinator**: coord-ai