功能
10 个应用、496 条菜单跑在同一套机制上。下面是这套机制的九个关键面。
- 插件的契约(实体 / Mapper 接口与 XML / Service 接口)留在平台 reactor 内的
ruoyi-模块,宿主的消费方只编译它。-api - 插件的实现(controller / service.impl / listener)在
plugins/ruoyi-,不进任何,独立构建成 jar。 - 平台依赖一律
—— 这些类由宿主类加载器提供,打进插件就会出现「同一个接口两个 Class 对象」。provided - 每插件一个类加载器,且是 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 四处是同一个约定。
看看它跑起来是什么样
应用市场里有全部应用的能力清单与接口路径。