Pro React Admin v5 架构解析:从项目构建到一次受权限保护的数据请求
一个后台应用刚开始可能只有一个入口。随着业务增加,团队会希望把演示页面和正式管理页面分开,把不同客户的前端独立构建,让通知或管理能力能按需组合,还要让同一套组件可以单独预览。此时最值得先回答的问题是:哪些东西应该复用,哪些东西应该隔离,以及这些选择应该发生在什么时候?
Pro React Admin v5 把这些问题落到了代码结构和构建流程里。本文沿着一个具体过程展开:构建系统如何选中企业入口,应用如何组装用户管理模块,一次列表请求如何经过权限和数据 Provider,最终到达可选的企业 API。

先把架构看成三个选择时点
项目把变化拆成三类,并在各自合适的时点处理:
- 构建时选择项目 :从
default、enterprise、独立项目或微前端 Shell 中选一个入口,生成对应产物。 - 应用启动时选择模块:决定当前入口包含哪些业务能力,并据此组装路由、Provider 和页面插槽。
- 请求发生时选择数据实现:页面使用稳定的 Provider 契约,底层可以连接演示数据,也可以连接 REST 服务。
text
src/projects/registry.json + config/project-registry.mjs
│
项目 CLI / Webpack
│ 构建时选择入口和产物
┌─────────────────┼──────────────────────┐
default enterprise projectA / projectB / shell
└─────────────────┴──────────────────────┘
│
app/bootstrap
┌──────────┴──────────┐
应用壳与生命周期 项目模块清单
Store / Layout / Tabs routes / Provider / 插槽
│
features/* 页面
│
platform 权限 / DataProvider / 请求
┌──────────┴──────────┐
演示 Provider REST Provider
│
Express → 业务服务 → SQLite
shared:跨业务 UI 与工具 packages/wui-react:独立 UI 包
Shell / Remote:由 Module Federation 单独连接
这些目录对应明确的依赖方向:业务页面依赖平台提供的契约,平台不反向依赖具体页面;通用 UI 不依赖业务,独立组件包也不导入应用源码。导入边界和路由契约检查会持续约束这些关系,让层与层之间的依赖保持清楚。
接下来就沿着用户管理页从构建到取数的过程,展开这三层选择。
第一步:构建系统选择一个项目入口
项目清单位于 src/projects/registry.json,登记入口、路由、输出目录和 Remote 配置;config/project-registry.mjs 负责统一解析项目并生成构建计划。Webpack、开发命令、预览、清理、脚手架和部署组合都读取同一份解析结果,项目配置和产物定位保持一致。
当前工作区包含完整展示应用 default、精简业务入口 enterprise、普通独立项目 projectA 和 projectB,以及用于加载 Remote 的 shell。PROJECT 在构建时决定所选项目,每个入口因此拥有自己的路由和产物;运行时应用只加载目标项目。
新增项目时,可以先让脚手架展示计划,再生成入口文件:
bash
pnpm run project:create customer --dry-run
pnpm run project:create customer
pnpm run project:dev customer
脚手架创建 entry、首页和 router,并同步项目注册信息;创建 Remote 时还会生成暴露组件和本地 Provider。新入口复用已有应用装配与平台服务,但正式业务仍需明确加入认证和页面权限。
第二步:应用启动时组装业务模块
选定入口后,app/bootstrap 创建 React root,装配布局、路由、Store 和业务 Provider。页面放在 src/features/<name>,按领域组织 routes、API、model 和 hooks。一个 feature 通过模块契约告诉应用它提供什么:
ts
export const administrationModule: ApplicationModule = {
id: 'administration',
contractVersion: 1,
routes: administrationRoutes,
resources: administrationResources,
Provider: AdministrationProvider,
}
应用模块清单选中需要的 feature 后,注册表会验证模块版本、依赖、重复路由和权限策略,再按依赖顺序组装 routes 与 Provider。组织管理和通知已经接入这套机制:default 选择两者,enterprise 只选择组织管理。公共 Header 通过模块插槽呈现通知入口,因此企业入口没有启用通知时,不需要让 Header 再判断项目名称。
这是一套编译期声明、启动时装配的模块机制。新增业务可以按相同契约接入;目前的可组合点发生在构建和应用启动阶段,运行期插件下载、动态重建路由和插件版本协商尚未实现。
路由结构与权限策略分开维护
页面组件和路由模块负责表达"有哪些页面";config/route-policy.json 负责页面标题、翻译键、菜单层级、排序、图标和权限。导航树根据当前项目实际注册的路由和当前用户权限生成,所以一个页面没有注册到企业入口时,不会只因为共享了 Layout 就出现在企业菜单里。
权限状态由 PermissionService 按身份维护,并通过订阅快照通知路由守卫、菜单和按钮。登录状态变化、权限同步或撤权后,界面使用同一份最新快照更新。前端负责按权限呈现交互,真实 API 则在服务端重新验证当前用户和角色,两者共同形成访问控制。
第三步:每个应用根拥有自己的状态和生命周期
每个应用根都会创建独立的 Zustand Store 和业务 Provider 树。壳 Store 只保存侧栏偏好,按 projectId 命名;业务数据和登录身份不会混进这个持久化状态。这样同一页面被两个 Remote 同时挂载时,它们的业务状态也不会因为共享模块文件而变成一个全局单例。
Tabs 背后的 KeepAliveProvider 缓存当前应用根里的页面实例,最多保留 5 个非持久页面,采用 LRU 淘汰旧页;它用于保留短暂离开页面时的表单草稿,不承担长期数据存储。这个页面缓存与后面提到的服务端数据缓存是两件不同的事。
用户切换账号或租户时,SessionScope 会销毁旧页面树、Tabs、KeepAlive 和业务 Provider,再创建新的工作区。权限和资源缓存也绑定当前会话,旧请求即使晚到,也不能把之前身份的数据重新写回当前缓存。这样前一个工作区的页面状态和业务数据不会带入新的身份上下文。目前一个 origin 共享一个活动身份;多身份并行需要独立 origin 或扩展会话模型。
第四步:页面通过 Provider 读取和修改数据
以用户列表为例,页面通过 administration feature 的 DataProvider 发起数据操作。platform/data/provider.ts 定义了分页列表、单条读取、创建、更新、删除和取消请求等操作;资源定义提供路径、响应 parser 和只读标记。REST adapter 复用平台请求层,会处理会话凭据、错误和 AbortSignal;领域 parser 再验证服务端返回的数据形状。
把这条路径落到实际代码上:/administration/users 由管理模块注册,路由策略要求 user:read。页面加载列表时调用 Provider 的 list('users', query, options);资源定义将 users 映射到 /api/admin/users 并指定 parseUser。REST 模式会发送带会话凭据的请求,企业 API 再按当前用户权限和租户范围读取数据;演示模式则从应用实例的内存数据读取。两种模式共用页面的查询、分页和展示逻辑。
演示数据刷新后会重置。真实 REST 服务可以使用仓库附带的企业 API,也可以替换成团队已有的业务服务。
对于服务器数据,平台还提供会话级内存缓存:有效期 15 秒、最多 100 项;相同查询可以共享一次请求,但不同消费者可以分别取消。成功写入会清理相关列表和审计数据;身份变化或缓存失效后的迟到响应不会重新填充缓存。缓存限于浏览器内存,目前没有后台刷新、实时订阅或分布式缓存。
从职责上看,页面关心领域记录和交互,feature 关心数据解析与业务动作,platform 关心通用传输、错误、取消和缓存。这种分层使替换 API 时通常不必重写页面。
可选企业 API:授权在服务端落地
仓库附带的 api/enterprise 是可选后端,使用 Node 24 原生 TypeScript、Express 和 SQLite,适合单实例自托管。它把 HTTP adapter、业务授权与会话、SQLite 存储分开组织。
真实请求的大致路径是:浏览器发送 Bearer 会话,服务端查找有效会话和用户,读取当前角色权限,再按该会话所属的 tenant_id 访问业务记录。租户由服务端会话确定,不由请求 Header 选择;每次请求都会重新读取用户与角色状态,不缓存授权结论。密码使用带随机盐的 scrypt,随机会话 token 只在登录时返回,数据库保存其 SHA-256 摘要。
用户、角色或会话发生写入时,业务修改、会话撤销和审计记录在同一 SQLite 事务中提交;审计 API 为只读,数据库触发器拒绝更新和删除。这让失败的审计写入也能回滚对应业务操作。拥有数据库文件权限的管理员仍可直接修改文件,因此审计日志不满足 WORM 或外部合规归档要求。
后端当前覆盖租户隔离、RBAC、用户与角色持久化、会话撤销和事务审计。生产接入仍需结合实际环境设计 OIDC/SSO、多组织成员关系、数据库迁移、备份恢复和多实例扩容。
Module Federation:远程部署仍需要明确协议
shell 是 Host,projectA 和 projectB 是 Remote。它们既可以分别启动开发,也可以构建成独立 Remote,再由 Shell 加载。Host 在注入 remoteEntry.js 前会先读取同目录 remote-manifest.json,检查 schema、协议版本、Remote 名称和共享依赖;加载失败会重试,最终显示 fallback。
跨应用状态通过版本化 Bridge 明确传递;React 等 shared singleton 用于依赖复用,身份和授权仍由当前会话与服务端完成。样式和权限隔离由应用部署边界承担;不可信的 Remote 应放在独立 origin 或 iframe 中。Module Federation 处理 Remote 的构建和加载,部署时仍需单独设计信任边界。
组件预览与交付
packages/wui-react 是可单独构建的 UI 包,输出 ESM、CJS、CSS 和 TypeScript 声明,不依赖应用源码。Storybook 从当前源码读取组件示例;公共包组件、共享 UI 和应用 UI 入口合计有 157 个直接预览入口。
构建流水线按 default、enterprise、Storybook、组件包和微前端组合分别处理产物。GitHub Pages 提供应用入口导航和 Storybook 预览;应用也支持 Vercel 和 Nginx 静态部署。部署组合根据项目注册信息放置 Shell 和 Remote,HTML、manifest 和 Remote entry 及时更新,带内容 hash 的静态资源可以长期缓存。
跑起来,再按需要深入
要求 Node.js >=24.15.0 <25 和 pnpm 11.5.2。克隆仓库后,在根目录安装并启动完整展示应用:
bash
git clone https://github.com/wkylin/pro-react-admin.git
cd pro-react-admin
pnpm install --frozen-lockfile
pnpm start
浏览器打开 http://localhost:8080/#/signin。想单独体验精简企业入口,可以停止当前服务并运行:
bash
pnpm run start:enterprise
若想看 Host/Remote 的实际协作,可分别在三个终端运行 pnpm run start:mf:projectA、pnpm run start:mf:projectB 和 pnpm run start:mf:shell,再打开 http://localhost:8080/#/portal。这些演示使用公开示例,不需要真实数据库。要运行持久化 API,则需要另外配置环境并显式初始化租户。
最后回到最初的问题
这套架构的组织方式可以概括为:项目注册表处理"要构建哪个应用",模块契约处理"这个应用包含哪些业务能力",Provider 处理"这些页面从哪里读写数据"。路由策略和服务端授权规定"当前身份能访问什么";Store、Provider 和缓存的生命周期则规定"状态属于哪个应用根和哪个会话"。
当变化发生在入口层,就新增项目配置;当变化发生在业务能力层,就实现 feature 并加入模块清单;当变化发生在后端服务层,就替换 Provider adapter。把变化放到清楚的边界里,是 Pro React Admin v5 这次架构整理的核心。