" />

插件化架构

业务实现跑在独立的类加载器与 Spring 子上下文里,平台只负责底座。

读文档 →
  • 插件的契约(实体 / Mapper 接口与 XML / Service 接口)留在平台 reactor 内的 ruoyi--api 模块,宿主的消费方只编译它。
  • 插件的实现(controller / service.impl / listener)在 plugins/ruoyi-不进任何 ,独立构建成 jar。
  • 平台依赖一律 provided —— 这些类由宿主类加载器提供,打进插件就会出现「同一个接口两个 Class 对象」。
  • 每插件一个类加载器,且是 parent-first:卸载才可能,升级才不会撞车。

热插拔,不重启

部署、安装、升级、停用、卸载全部在运行期完成,用户无感。

读文档 →
  • 执行顺序是刻意的:落位 → 变更表结构 → 切换代码。反过来的话,新代码在启动时机读一个还没建的列就会炸。
  • 「落位」与「加载」被拆成两步:安装/升级只落位,加载推迟到迁移之后。
  • 卸载按固定顺序:先摘接口 → 再关上下文 → 最后关类加载器。顺序反了会让飞行中的请求打到已销毁的 bean。
  • 运行时开关 app-market.plugin-runtime.enabled(默认开启)用 JVM 参数切换 —— 启动加载器在 Spring 之前执行,读不到 yml。

应用市场与上架端

上架端维护目录,部署端拉包安装,两边通过一份目录数据衔接。

读文档 →
  • 上架端维护应用、版本、分类与安装包上传;上传只登记(解包读清单 + 落对象存储 + 记校验和),产物释放是另一个动作。
  • 安装时若本机缺产物,按 packageUrl 从市场拉包 → 校验 MD5/SHA256 → 安全检查 → 解包落位。
  • 菜单按应用清单幂等 upsert,同名 menu_key 复用既有 menu_id —— 租户已分配的权限不会失效。
  • 任务中心记录安装/升级/卸载的每一步与日志,边跑边落库,是排查问题的第一站。

包签名校验

应用包的两部分都是「完全可信代码」,所以靠签名建立信任,而不是靠扫描。

读文档 →
  • 后端 jar 是任意 Java 代码,跑在宿主 JVM 里;前端 JS 跑在宿主同源上下文,能拿用户 token 调任何接口。
  • 签名是分离式的:包根一个 signature.txt,覆盖除它自己以外的全部文件(含清单与迁移脚本)。
  • 摘要按路径字符串升序拼接每行的「相对路径 + 内容 SHA-256」,算法 SHA256withRSA;路径参与签名,改名无法逃过校验。
  • 两个校验点缺一不可:上传时尽早告知发布方,下载后才是真正的防线(能挡住"上传之后对象存储里的包被替换")。
  • 三种模式 off / warn / enforce;切 enforce 前用签名就绪度自检接口确认,且必须先配好公钥。

多租户隔离

哪些数据全局共享、哪些按租户隔离,是整套设计的地基。

读文档 →
  • 目录(应用、版本、分类)是平台级数据,全局一份,已排除在租户隔离之外;安装实例迁移记录每个部署各一份。
  • 正因为目录不按租户隔离,才可能把它们集中到一台服务器(不需要传租户参数)。
  • 卸载时有两道安全闸:还有别的租户在用就跳过全局删除;平台表前缀(sys_ / app_ / gen_ …)一律不删。
  • 默认卸载保留数据;「彻底删除」要把应用名完整敲一遍才放行,且只删应用包自己声明过的表。

目录中心化与版本形态

一套代码服务多种部署拓扑:单机自用、官方版 + 多部署、用户版。

读文档 →
  • 安装侧对目录的读取全部收口到一个契约接口。上架端作为目录所有者直接用 Mapper 是正常的 —— 判断标准是「这段代码会跑在别人的部署上吗」。
  • official 官方版:目录在自己库里、含上架端、对外提供目录接口;client 用户版:没有上架端,目录从官方读(必须配 remote)。
  • 用户版不是阉割版:安装、升级、任务中心、菜单保护、签名校验它都有。
  • 刻意不做 fork —— 一个 jar、一个前端、一个开关,改公共逻辑只需改一遍。

版本化迁移与种子数据

表结构和它的初始数据都随应用包走,安装/升级时自动执行。

读文档 →
  • .sql 放进插件目录的 migrations/,打包时自动进包,按文件名排序执行。
  • 四条硬规矩:只做 up 不回滚、脚本必须幂等、已执行过的脚本内容不能改(摘要不一致直接拒绝)、去重判据不带租户。
  • 建表脚本同时是卸载删表的唯一依据 —— 平台从它里面解析 CREATE TABLE 的表名。
  • 字典与站点配置也要随包走,否则全新部署装完是空标签、公开配置接口返回空对象。

前端页面也能热加载

这一条是整套改造的前置:页面不再随宿主一起构建。

读文档 →
  • 菜单里的 component = remote:/<视图键> 只是一个视图键;宿主在运行期问服务端"该加载哪份 JS"。
  • 远程应用是一份独立的 ESM,运行期 import();Vue、Element Plus 等共享依赖被 external 掉,由运行时垫片指回宿主实例。
  • 入口 URL 带 ?v=<版本号> 破浏览器缓存 —— 不带就会出现"升级成功了、页面还是老样子"。
  • 产物目录名必须等于应用编码,这一点在打包脚本、落位目录、宿主 URL 四处是同一个约定。

看看它跑起来是什么样

应用市场里有全部应用的能力清单与接口路径。