
前阵子在某个企业后端平台项目里接了个代号叫“REA”的系统重构任务。一开始大家看到这个名字都挺懵以为是哪个框架的新版本后来开需求会才明白REA 是 Reusable Enterprise Application 的缩写简单说就是“可复用企业级应用”。这个项目要解决的事情很直接把公司内部多个业务系统里重复造轮子的部分抽出来做成一套统一的可复用骨架包括权限、组织架构、基础组件、请求封装、异常处理这些公共能力让新业务系统接入之后只写业务代码不碰基础设施。这篇文章我就完整复盘一下 REA 从设计到落地过程中的关键环节包括架构取舍、核心模块拆分、实操过程和几个特别典型的坑。如果你也在做企业内部中后台系统或者正打算把多个前端项目沉淀成统一工程化底座这篇内容应该能给你一些参考。1. 整体设计与思路拆解为什么非要做可复用企业应用1.1 复用的核心问题不是代码复用而是能力复用很多人一听到“可复用”第一反应就是把公共代码抽出来放到一个公共包里谁要用就引谁。这种思路在早期确实有效但放到企业级环境里很快就会出问题。原因很简单企业内部的中后台系统业务场景高度相似但又有各自的差异化需求如果只是简单抽代码维护成本会转移到依赖关系的协调上。A 系统升级了一个公共组件的版本B 系统还在用旧版两边行为不一致出问题都不知道找谁。REA 项目的核心思路一开始就不在“抽代码”而在“沉淀能力”。所谓能力指的是一个完整的功能闭环。拿权限来说不是提供一个获取用户信息的工具函数而是提供一套包含登录态检测、token 刷新、路由守卫、按钮级权限指令、用户信息缓存在内的完整链路。业务系统接入 REA 之后不需要知道权限是怎么存的、token 刷新是怎么触发的只需要关心自己在页面里怎么调用权限判断的 API。这就是能力复用和代码复用的本质区别。当时我们做了一个挺重要的决策所有公共能力必须自带默认实现但允许业务系统覆盖默认行为。比如消息提示组件默认是 antd 的 message 样式但如果业务系统有自己的设计规范可以传入自己的渲染函数。这种做法既保证了开箱即用又照顾了不同业务线的个性化诉求。1.2 为什么选用微前端与模块联邦这套组合REA 面向的场景是公司内部多系统共存过去每个系统都是独立部署、独立维护前端仓库完全隔离想要统一升级基础能力几乎不可能。讨论方案的时候摆在面前的路线有三条把所有系统合并成一个巨型单页应用SPA维护一套公共依赖包或者引入微前端架构。合并巨型 SPA 的方案第一个被否决了。原因倒不是技术上实现不了而是组织协作上必然出问题——不同团队维护同一个仓库发版节奏互相牵制一个团队的红线改动可能让另一个团队的应用直接挂掉这在内部工具类系统里尤其致命。统一公共依赖包的方式我们也试过但版本漂移问题实在太严重很多老系统停留在旧版本上不愿意升级又退化回了“各自为政”的局面。最终选定的方案是基于模块联邦的微前端架构。每个业务系统作为独立的微应用存在有自己的仓库、自己的构建流程、自己的部署管道。REA 核心作为基座应用负责整体布局、导航、用户会话等基础能力同时通过模块联邦暴露一组共享的运行时依赖。业务系统在构建时可以告诉模块联邦“我需要共用 react 和 react-dom”运行时直接从基座加载不用重复打包。这套组合最大的好处是升级路径平滑。基础能力升级时只需要重新部署基座应用各业务系统不需要改代码只要测试核心流程就行。模块联邦的 shared 机制从运行时层面保证了一些关键依赖的版本一致性从根上消灭了“你用的 React 版本和我用的不一样”这类奇葩 bug。1.3 数据模型设计权限、组织与用户信息怎么建模企业级应用的复用真正复杂的地方不在前端交互而在数据模型。REA 项目里我们把核心数据模型分成三个域用户域、组织域和权限域。用户域比较简单维护的是用户基础信息包括用户ID、姓名、头像、联系方式、账号状态这些字段。组织域要稍微复杂一些因为企业内部的组织架构通常是树形结构而且存在一人多部门、部门多层级、跨部门兼职这类情况。我们设计组织模型的时候特意区分了“主部门”和“兼任部门”两个概念主部门用于默认归属兼任部门用于多维度权限计算。权限域是这三个域里最考验设计的。传统做法是给用户直接绑定角色角色绑定权限点这个模型在用户量小的时候很好用但一旦组织复杂起来权限的维护成本会指数级上升。REA 里采用的方案是 RBAC 数据范围的双层模型。第一层是功能权限也就是你能看到哪些菜单、能点哪些按钮第二层是数据范围也就是你能看到哪些数据行。数据范围用规则表达式描述比如“仅本人”“本部门”“本部门及下级部门”“全部”由后端在执行数据查询前统一拼接条件。这个设计让我们在很多业务系统里做到了权限配置化。业务人员通过管理界面拖拽配置不需要开发介入这在以前几乎是不可想象的。2. 核心模块实现与实操要点2.1 基座应用布局、导航与会话管理的实现顺序基座应用是 REA 的核心载体它承载了用户登录之后看到的第一屏内容主要包括侧边导航、顶部栏、页签切换、用户信息下拉框等企业应用中几乎都会有的界面元素。实现顺序上我们遵循了“先会话后布局再集成”的路线。会话模块是第一个动手的。它要解决的核心问题是用户打开任意一个子应用页面时怎么判断他是否已登录以及登录态失效时怎么处理。实现上我们封装了一个统一的 AuthProvider挂在基座的最顶层。它负责在应用初始化时从 cookie 或者本地缓存读取会话标识调用后端的会话校验接口确认有效性同时监听一个自定义事件用于跨应用登出通知。有几个细节值得说一下。会话校验接口一定要设计成幂等的也就是说重复调用不会产生副作用这样微应用加载时重复触发也不会压垮后端。另外 token 刷新要做到异步串行多个请求同时遇到 401 时只发出一个刷新请求其余请求挂起等待刷新结果避免刷新风暴。这个机制我们在REA 的请求层里用了一个简单的 promise 缓存实现代码量不大但效果非常明显。布局模块相对简单用的是左右结构的经典企业后台布局。左侧导航支持两级菜单第一级是业务域第二级是具体功能页面。这里有一个很容易踩的坑菜单数据不能只存名称和图标必须存完整的路由映射信息。如果菜单数据里没有路由字段前端还得自己做一层路由和菜单的匹配逻辑后续维护时会变得非常痛苦。2.2 通用组件库从设计到发布的完整链路组件库是 REA 复用能力中最直观的部分。我们总结了公司内部十几个业务系统的高频界面模式归纳出 20 多个基础组件包括搜索表单、数据表格、详情描述、弹窗表单、步骤条、批量操作栏等。这些组件不是把第三方组件库简单包一层而是注入企业自身的交互规范和数据口径。以数据表格为例企业内部的数据表格几乎都会用到分页、筛选、排序、列配置、导出、行操作这些功能如果每个系统各自实现不仅工作量大而且交互细节容易不一致。REA 的 ProTable 组件在封装时就定了几条硬规则数据源统一走封装的请求层分页参数统一用 current 和 pageSize列配置支持本地持久化到 localStorage导出动作统一调后端的导出接口并处理下载状态。组件的发布流程我们是走 npm 私有仓库的版本号遵循语义化版本规范也就是主版本.次版本.补丁版本的结构。这里要特别提醒一点组件库的 breaking change 一定要谨慎因为企业内部用户众多任何破坏性变更都可能引发大面积适配。我们内部约定涉及 breaking change 必须先发布候选版本给各业务系统留出至少两周的适配窗口并且在升级文档里写清楚迁移路径。另一个心得是组件库的文档必须和代码同步维护。这个道理大家其实都懂但实际执行起来最难。REA 的做法是在组件源码里直接写 JSDoc 注释自动化工具从注释中提取 API 表格和使用示例生成文档站点最大程度减少文档滞后的问题。2.3 业务接入的标准流程一个系统从零到上线需要几步业务系统接入 REA我们希望能做到“配置优先代码补充”。也就是说大部分基础能力通过配置文件声明即可完成接入只有特殊业务逻辑才需要编写额外代码。整个接入流程大致分为五步。第一步是创建应用配置在配置文件里声明应用名称、路由前缀、菜单结构、所需权限点列表。第二步是在基座应用中注册路由这样导航到对应地址时才不会 404。第三步是处理登录对接把业务系统自身的登录请求替换成 REA 统一的登录入口。第四步是按需引入公共组件替换掉原有的重复实现。第五步是回归测试重点检查会话一致性、权限边界和关键业务流程。这里有个小技巧业务系统接入时尽量让路由前缀和应用名称保持稳定后续升级时可以减少很多麻烦。我们遇到过某个系统上线半年后改了路由前缀结果所有外部分享的链接全部失效只能一个一个找业务方重新生成很折腾。2.4 请求层与异常状态码体系别把接口请求写进业务代码请求层是 REA 里可能最不显眼但实际最提升效率的模块。企业内部系统前后端联调时最烦的事情是每个接口都需要写 loading 状态、错误提示、成功提示、异常处理这些代码塞在业务页面里页面一多就全是重复劳动。REA 的请求层封装了一个统一方法内部完成的事情包括拼接基础路径、自动带上会话凭证、处理请求超时、根据响应状态码统一提示错误信息、处理登录失效自动跳转登录页、支持取消重复请求。业务代码只需要关心接口的入参和返回数据其余全部交给请求层处理。我们定义了一套内部错误码规范。例如 200 表示成功400 表示参数错误但前端不需要特殊处理401 表示未登录或会话过期403 表示权限不足500 表示服务端异常。这套规范明确之后前后端之间很多不必要的扯皮都消失了因为两边对“什么场景返回什么状态码”有了共同语言。还有一点很重要请求层必须支持在微前端场景下工作也就是说它不能依赖全局的 axios 实例而是要在基座和子应用之间共享一个配置来源。REA 的做法是把请求层实例挂到基座的全局对象上子应用通过模块联邦共享机制拿到这个实例保证所有应用发出的请求都走同一套拦截逻辑。3. 实操过程与关键环节实现3.1 工程初始化从零搭建基座应用的全过程REA 基座应用的技术栈选了 React TypeScript Vite。选择 Vite 而不是 Webpack 的理由很简单构建速度快配置直观对模块联邦也有成熟支持。初始化基座的时候第一步不是写代码而是先把项目结构搭清楚。我们的目录结构大致分为 src/app、src/layouts、src/components、src/services、src/utils 几个核心目录每个目录承担明确职责避免后续代码越放越乱。初始化过程中有一个比较关键的配置是路径别名。我们统一用 / 指向 src 根目录用 /components 指向公共组件目录。路径别名配置好之后后续写代码会舒服很多import 路径干净利落也方便做模块重构时统一替换。基座应用的启动流程我建议做成可视化的也就是启动时在控制台输出当前环境的配置信息、远程模块的加载状态、路由注册的数量等关键信息。这个看起来很小的功能在多人协作时特别有用因为成员在本地启动时能快速判断环境是否正常而不是一上来就面对一个空白页面。3.2 权限指令与菜单过滤按钮级权限是怎么落到页面上的权限是 REA 里用户感知最强的一个模块做得不好就会被业务人员频繁投诉。菜单过滤的逻辑很直接拿到当前用户的功能权限点列表遍历菜单配置把没有权限的菜单项过滤掉。但按钮级权限稍微复杂一点因为它不是整页隐藏而是页面上某个按钮根据权限决定显示还是隐藏。REA 在这个环节封装了一个权限指令和一个权限判断函数。权限指令用在模板里写法类似 v-permission“user:create”没有权限时指令会把对应元素从 DOM 中移除。权限判断函数则用在逻辑复杂的场景比如某个按钮的显示条件除了权限还要叠加其他业务条件这时直接调用权限判断函数更灵活。这里要提醒一个容易踩的坑按钮级权限不能只做前端隐藏后端接口必须同步做强校验。前端的隐藏只是改善用户体验后端不做校验的话用户通过控制台手动调用接口一样能执行越权操作。REA 对接的每一个系统我们都会在验收时做越权测试确保前端控制和后端校验一致。3.3 动态路由注册微应用加载时路由是如何合并的微前端架构里路由是最容易出问题的地方。基座应用有自己的路由子应用也有自己的路由如果两边路由冲突就会出现页面错乱。REA 采用的做法是以基座为主子应用的路由在加载时注册到基座的路由表里卸载时移除。这样基座对整体路由有完全的控制权可以统一处理页面标题、菜单高亮、页面缓存这些公共逻辑。路由注册的实现细节里有一个问题需要特别处理子应用内部跳转时浏览器的地址栏会变化但基座导航的高亮状态可能不会同步更新。解决办法是监听路由变化事件根据当前地址计算所属的菜单项手动触发高亮更新。这类问题平时不遇到遇到了排查起来非常费时间所以写在这里希望帮你提前避开。3.4 全局状态管理跨应用的数据共享怎么做才不乱企业内部系统最常见的跨应用共享数据包括用户信息、会话状态、全局设置以及一个应用跳转到另一个应用时需要携带的业务参数。REA 在全局状态管理上没有选择使用大型状态管理库而是用了轻量方案全局事件总线加共享 store。共享 store 的核心是一个响应式对象挂在基座全局子应用通过模块联邦获取引用。任何应用修改 store 里的数据读写操作都会同步到所有应用。这种模式简单直接不引入太重的心智负担。事件总线则用于通知类场景比如用户点退出登录基座通过事件总线通知所有子应用清理本地状态。使用共享 store 有一个务必遵守的规范不允许子应用直接修改 store 里不属于自己的字段。否则不同应用之间会产生隐性的数据耦合出了 bug 很难定位。REA 的做法是给每个子应用划分独立的命名空间应用只能操作自己命名空间内的数据公共数据只能通过基座提供的 API 来修改。4. 常见问题与排查技巧实录4.1 子应用加载后样式丢失页面像没有皮肤的裸奔这个问题出现频率极高。原因通常是子应用的样式没有按预期注入到主文档中。排查时可以先把子应用单独跑起来看样式是否正常如果单独运行没问题那就一定是样式注入机制出了问题。常见的原因包括样式隔离配置错误、CSS 文件引用路径用的是相对路径、以及子应用使用了动态加载的 css 但微前端容器没有接管。REA 的解决方案是让子应用的样式全部走 CSS Modules 或 CSS-in-JS避免裸样式名的全局污染问题。另外在容器加载子应用时主动检查样式节点的挂载数量如果显著低于预期就打印警告日志把问题暴露在开发阶段而不是上线后。4.2 会话过期后跳转登录页但跳转后登录成功还是回到原页面这个问题的表象是用户会话过期请求返回 401前端自动跳到登录页。用户登录成功后本应回到之前访问的页面但实际却回到了应用首页。原因是跳转登录页之前没有把当前地址记录下来登录成功后又没有把地址拼回去。解决思路就两步发起 401 跳转前把当前完整路由地址存入 sessionStorage登录成功后读取这个地址并跳转。关键细节是要区分不同的应用如果基座和子应用跳转登录页的方式不一样记录地址的时机也要对应调整否则记录的是基座地址恢复的却是子应用的页面就会出现跳转不对位的问题。4.3 组件库升级后线上出现两个版本的弹窗样式某个业务系统没有及时升级组件库但基座升级到了新版本两个版本同时在运行时存在页面上就会混用不同样式。这个问题的根源是组件库没有做到平滑升级或者业务系统没有遵循升级流程。REA 的应对措施是双管齐下组件库发布时用工具自动扫描 API 变化生成完整的升级影响面报告作为强制阅读材料同时基座为每个子应用声明了组件库的最低版本要求如果子应用版本过低启动时直接给出警告弹窗。这个机制不能完全杜绝问题但至少能把风险提前暴露出来。4.4 排查技巧微前端场景下怎么快速定位问题归属微前端最大的排查难点是问题可能出现在基座、子应用、公共组件库、后端接口四个位置中的任何一个。REA 的排查套路是“先隔离再二分”。先看问题是否在子应用独立运行时出现独立运行没问题就基本确认与基座相关。如果是与基座相关的问题再看是否公共依赖导致比如 React 实例是否重复加载。这里推荐一个基础设施级的做法基座和子应用都把关键运行信息打印到统一格式的控制台日志里日志中带上应用标识。这样排查问题时通过日志就能快速区分是谁输出的不用在一堆 console 输出里猜来猜去。这个小工具能节省大量排查时间。5. 经验沉淀与后续扩展方向5.1 经历 REA 项目后沉淀的几条工程化心得第一企业内部的前端工程化优先解决“重复”问题永远比解决“复杂”问题更见效。很多团队热衷于引入新框架、新工具来解决所谓的“技术债”但真正让团队疲惫的往往是同一段代码在不同项目里反复写、反复调。REA 项目之所以能很快看到效果就是因为它精准地打在了“重复”这个痛点上。第二可复用能力必须由基础设施团队和业务团队共同定义不能关起门来自己设计。REA 组件库在做需求收集时我们几乎把每个业务系统的主界面都截了图逐块拆解哪些是一样的、哪些是有差异的。这样沉淀出来的组件才是真正被需要的而不是设计者想象中的“通用”。第三复用要有边界强行统一是灾难的开始。不同业务线的交互习惯有时候真的差很多强行让所有系统都用同一套表格、同一个弹窗业务团队会有怨言。REA 的做法是提供基础能力加可配置项在需要统一的底线部分坚持统一在允许变化的部分留足空间。5.2 后续可以继续扩展的方向REA 目前的形态还只是第一阶段的成果。可以继续扩展的方向一个是低代码配置平台让业务人员通过拖拽配置界面来生成简单的管理页面。这不是异想天开因为有了 REA 的组件库和权限体系作为底座配置平台需要解决的技术难题已经降到了可接受的范围内。另一个方向是数据可视化能力把企业内部的常用图表沉淀成 BI 组件直接接入 REA 组件库业务系统做报表时不用再单独引入图表库。我个人在实际操作中最深刻的体会是可复用企业应用不是一锤子买卖而是一个持续演进的过程。它做了发布之后不代表结束后续的维护和迭代才是真正考验耐心的地方。如果你正在着手类似的方向我的建议是先挑一个最疼的业务场景做试点用实际成效说话比写再多文档都管用。等到试点跑顺了再慢慢扩大复用范围这条路会比一次性追求大而全会稳很多。