Skip to content

总体架构

一句话

把 RuoYi-Vue-Plus 从「单体系统」改造成「平台 + 应用」:平台提供多租户底座, 业务能力做成可以后装、不重启、从市场拉下来直接跑的应用。

为什么需要改造:起点约束

改造前的结构是:所有模块最终打成 ruoyi-admin 一个 jar。这是起点,也是最大的约束。

而前端的页面是构建期用 glob 静态打进来的:

ts
import.meta.glob('./../../views/**/*.vue')

后端 sys_menu.component 只能指向构建时已存在.vue 文件。 所以在原有架构下,第三方页面在物理上无法热加载 —— 必须先改造前端运行时, 让菜单能指向一份"运行期才知道在哪"的独立 ESM。

这条约束决定了整个改造的分界线:

只要还是"一套系统服务一个客户",单体结构没问题; 一旦要后装应用开放第三方,前端运行时改造(远程应用)是绕不过去的前置。

分层

┌──────────────────────────────────────────────────────────────┐
│  平台(宿主)                                                  │
│                                                                │
│  用户 / 角色 / 菜单 / 租户 / 部门 / 岗位 / 字典 / 参数           │  ← 底座
│  文件存储(OSS) / 短信 / 邮件 / 任务调度 / 工作流 / 代码生成       │
│                                                                │
│  ┌────────────────── 应用市场(ruoyi-app-market) ──────────┐  │
│  │  目录(catalog)   │  安装编排    │  插件运行时            │  │
│  │  上架端(console) │  任务中心    │  远程应用运行时        │  │
│  │  包安全(签名)    │  菜单同步    │  版本迁移              │  │
│  └──────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘
              ▲ 依赖契约(编译期)        │ 反查实现(运行期)
              │                          ▼
┌──────────────────────────────────────────────────────────────┐
│  应用(插件)                                                  │
│                                                                │
│  契约 ruoyi-<name>-api     实现 plugins/ruoyi-<name>           │
│  ─ 实体 / BO / VO          ─ controller                        │
│  ─ Mapper 接口 + XML       ─ service.impl                      │
│  ─ Service 接口            ─ listener                          │
│  ─ 常量 / 事件 / SPI 实现                                       │
│                                                                │
│  清单 app.json   +   前端远程 ESM   +   migrations/*.sql        │
└──────────────────────────────────────────────────────────────┘

平台负责什么 / 应用负责什么

平台(宿主)应用(插件)
用户与权限用户、角色、部门、岗位、菜单、租户自己的权限标识(写进清单的 menus[].perms
菜单幂等 upsert、归属保护、按角色授权声明菜单树
数据表平台表(sys_* / app_* / gen_* …),并保护它们不被误删自己的业务表,通过 migrations/ 声明
加载与隔离类加载器、子 Spring 上下文、路由注册、拦截器链只写正常的 Spring 代码
生命周期部署 / 安装 / 升级 / 卸载 / 停用启用的编排与任务记录无感知
前后端运行时import map 垫片、运行时注入、视图键解析产出符合契约的独立 ESM
包安全产物白名单 + 签名校验发布时签名
前端页面一层壳 + 布局 + 工作台自己的视图

契约与实现分离

这是整套结构里最需要记住的工程约定。

放什么放哪为什么
实体 / BO / VO / 枚举 / 事件ruoyi-modules/ruoyi-<name>-api宿主消费方要在编译期看到这些类型
Mapper 接口与 XML同上宿主注册 Mapper bean;XML 必须让宿主的类加载器扫到
Service 接口同上消费方要注入或代理它
平台 SPI 的实现同上消费方可能是另一个应用 —— 插件只能看见父上下文(宿主),看不见兄弟插件
controller / service.impl / listenerplugins/ruoyi-<name>独立构建成 jar,装载进插件运行时

最隐蔽的坑:Mapper XML 放错位置

宿主的 mapperLocations: classpath*:mapper/**/*Mapper.xml 是用宿主类加载器扫描的。 XML 若留在插件 jar 里,在「每插件独立类加载器」模式下宿主根本扫不到所有 SQL 静默失效,而且报错信息完全指不到这里。

两个仓库

仓库内容作用
RuoYi-Vue-Plus后端(Java / Spring Boot)+ 插件工程 + SQL 脚本平台与后端应用
plus-ui前端(Vue 3 / Vite)+ 远程应用工程前端壳与远程应用

两个仓库没有共同 git 根,各自提交、各自推送。改一个功能经常要两边都提交。

应用市场模块的包结构

ruoyi-modules/ruoyi-app-market 是整套机制的核心,按职责分包:

职责
catalog/目录访问的唯一入口:IAppCatalogService + Local / Remote 两种实现 + 服务间接口
edition/版本形态开关(official / client)、按路径前缀的形态闸门、启动期自洽校验
plugin/插件类加载器、子上下文、独立 HandlerMapping、运行时管理器、事件中继
security/包摘要规范化、产物白名单 + 签名校验、离线签名工具、签名就绪度自检
service/安装编排、菜单计划与同步、菜单归属、迁移执行、落位、卸载清理、清单校验
spi/安装器 SPI(module / plugin / deploy)与生命周期监听、扩展点
state/应用生命周期状态机
support/任务进度落库、卡死任务清扫
controller/市场浏览端、上架端、版本端、安装包端、安装端、运行时答复、工作台

相关阅读

基于 MIT 协议开源 · 文档与官网由源码生成