尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

若依多模块 Maven 项目架构实战:从单体到模块化

若依多模块 Maven 项目架构实战:从单体到模块化
📅 发布时间:2026/6/30 22:56:50

若依项目做到第三个月,admin 模块越来越臃肿——启动 40 秒、改一行代码全量编译、两个人改同一个文件天天冲突。这篇文章复盘我是怎么把若依从单模块拆成多模块的:哪个先拆、边界怎么画、依赖怎么管、趟过的坑一个不落。


一、单体项目的"三个月魔咒"

最开始搭若依的时候,目录干净得让人舒服:

ruoyi-admin/ ├── src/main/java/com/ruoyi/ │ ├── controller/ ← 用户、角色、菜单、工单、客户... │ ├── service/ ← 所有业务逻辑 │ ├── mapper/ ← 所有数据库映射 │ └── ... └── pom.xml

前两周没毛病,改什么都快。代码生成器一键出 CRUD,一天能上两张表。

问题从第一个月开始冒头:

症状具体表现谁难受
启动变慢从 8 秒涨到 40 秒每次调试都等得心烦
编译范围大改一行 Service,全量 300+ 类重新编译浪费时间
合并冲突两个人都在admin里加东西,每天 merge 都有冲突协作成本飙升
边界模糊CRM 的 Service 和 MES 的 Service 混在一起,越写越不敢删谁敢重构谁死

到第二个月底,ruoyi-admin已经塞了 CRM 模块和 MES 模块的全部代码——Controller 40 多个,Service 30 多个,Mapper 50 多个。每次 IDE 里打开项目树,要滚好几页才能找到想看的文件。

单模块不坏,但当你开始纠结"这个类该放哪个包"的时候,就是该拆的时候了。


二、若依框架的模块化设计全景

先看若依官方给的标准模块结构,这对理解后面的拆分至关重要:

ruoyi/ ├── ruoyi-common/ # 公共工具类、异常、注解、枚举 ├── ruoyi-framework/ # 框架核心配置(安全、数据源、Swagger) ├── ruoyi-system/ # 系统管理业务(用户、角色、菜单、部门) ├── ruoyi-generator/ # 代码生成器 ├── ruoyi-quartz/ # 定时任务 └── ruoyi-admin/ # 启动模块 + Controller 汇总

2.1 各模块的职责与边界

模块职责可以依赖谁被谁依赖
ruoyi-common工具类、通用异常、基础注解、常量无(最底层)所有模块
ruoyi-framework安全配置、数据源配置、全局拦截器ruoyi-commonruoyi-system、ruoyi-admin
ruoyi-system用户/角色/菜单/部门等系统管理业务ruoyi-common、ruoyi-frameworkruoyi-admin
ruoyi-generator代码生成器ruoyi-commonruoyi-admin
ruoyi-quartz定时任务管理ruoyi-commonruoyi-admin
ruoyi-admin启动类、全局 Controller、静态资源以上所有无(顶层)

2.2 核心原则:依赖只向下

若依的依赖链路是一条单向链:

ruoyi-admin ──→ ruoyi-system ──→ ruoyi-framework ──→ ruoyi-common (顶层) (业务层) (配置层) (基础层)

没有任何一个底层模块依赖上层模块。这是 Maven 多模块拆分的第一铁律。

如果你想不明白这个原则,记住一句土话:common 不能 import 任何业务代码,framework 不关心具体业务,system 只管自己的表。


三、实战第一步:把业务模块从 admin 里拆出来

3.1 拆之前的状态

在我们项目里,ruoyi-admin底下实际长这样:

ruoyi-admin/src/main/java/com/ruoyi/ ├── web/controller/system/ ← 若依自带的系统管理 ├── web/controller/crm/ ← CRM:客户、联系人、商机... ├── web/controller/mes/ ← MES:工单、工序、质检... ├── service/system/ ├── service/crm/ ├── service/mes/ ├── mapper/system/ ├── mapper/crm/ └── mapper/mes/

包名分开了,但物理上全在一个模块。只要改一行,整个模块重新编译。这跟没拆没区别。

3.2 怎么拆

别贪多,一次只拆一个模块。我先拆的是 CRM,因为它和若依自带的系统管理耦合最小。

Step 1:创建新模块

在根pom.xml的<modules>里加一行:

<!-- 根 pom.xml --> <modules> <module>ruoyi-common</module> <module>ruoyi-framework</module> <module>ruoyi-system</module> <module>ruoyi-generator</module> <module>ruoyi-quartz</module> <module>ruoyi-admin</module> <module>ruoyi-crm</module> <!-- 新增 --> </modules>

新建ruoyi-crm目录,里面放一个pom.xml:

<!-- ruoyi-crm/pom.xml --> <parent> <groupId>com.ruoyi</groupId> <artifactId>ruoyi</artifactId> <version>3.3.0</version> </parent> <artifactId>ruoyi-crm</artifactId> <name>ruoyi-crm</name> <dependencies> <!-- 只依赖 common 和 system,不依赖 admin --> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-common</artifactId> </dependency> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-system</artifactId> </dependency> </dependencies>

⚠️ 关键:ruoyi-crm不要依赖ruoyi-admin。admin 是顶层,业务模块不能反向依赖。

Step 2:迁移代码

目录结构对齐到标准的 Maven 模块目录:

ruoyi-crm/ ├── pom.xml └── src/main/java/com/ruoyi/crm/ ├── controller/ ← 从 admin 搬过来的 ├── service/ ├── service/impl/ ├── mapper/ └── domain/ ← entity 放这

按住 IDE 的拖拽,把 CRM 相关的包整个拖到ruoyi-crm/src/main/java/com/ruoyi/crm/下。

注意包路径变了:原来com.ruoyi.web.controller.crm→ 现在com.ruoyi.crm.controller。所有 import 语句需要同步修改,IDE 的批量替换可以搞定大部分。

Step 3:admin 加上依赖

<!-- ruoyi-admin/pom.xml --> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-crm</artifactId> </dependency>

这样ruoyi-admin启动时才会把ruoyi-crm的类扫进 Spring 容器。

MES 模块同理,再建一个ruoyi-mes,操作完全一样。

3.3 第一个坑:Spring Boot 扫描不到 Bean

代码搬过去,依赖加好,启动——报了一堆NoSuchBeanDefinitionException。

原因:若依的启动类在com.ruoyi包下,@SpringBootApplication默认只扫描启动类所在包及其子包。CRM 代码移到了com.ruoyi.crm下,还在com.ruoyi子包里,所以理论上扫得到。

但我踩的坑是:有些代码路径习惯性写成了com.ruoyi.web.controller.crm,搬家后变成了com.ruoyi.crm.controller,中间的web层级没了,包结构发生变化。而原来admin里有些地方的@MapperScan还是指向老路径。

解决:统一把@MapperScan放到各模块各自的配置类里,不要全堆在 admin 中:

// ruoyi-crm 模块中 @Configuration @MapperScan("com.ruoyi.crm.mapper") public class CrmConfig { }

这样每个模块管好自己的 Mapper 扫描,admin 不用操心。


四、实战第二步:公共代码怎么不重复——common 的演进

4.1 什么时候抽到 common

拆完 CRM 和 MES 后,很快发现一个问题:两个模块都需要 Excel 导出工具ExcelUtil。

最蠢的做法:各 copy 一份。等你改了 bug 忘了同步另一份,线上就出灵异事件。

正确做法:抽到ruoyi-common:

ruoyi-common/src/main/java/com/ruoyi/common/utils/ ├── ExcelUtil.java ← CRM 和 MES 都用 ├── PageUtils.java ← 通用分页工具 └── DictUtils.java ← 字典工具

抽取原则很简单:

该放 common不该放 common
所有业务模块都用的(分页、导出、日期工具)只有两个模块共用的业务 DTO
异常类、枚举、基础注解跟某个具体业务强绑定的工具
全局常量临时性、可能随时变的配置

4.2 什么时候不该放 common

CRM 和 MES 之间有一个共享场景:MES 的质检模块需要查 CRM 的客户信息。我们就建了一个CrmCustomerBriefDTO用于跨模块传递。

这个 DTO 如果放ruoyi-common,就会导致 common 开始膨胀——今天塞一个客户 DTO,明天塞一个工单 DTO,最终 common 又变成了大杂烩。

方案:建一个ruoyi-shared-dto模块,只放跨模块共享的数据对象:

<!-- ruoyi-shared-dto/pom.xml --> <dependencies> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-common</artifactId> </dependency> </dependencies>

然后 CRM 和 MES 都依赖它。common 保持干净。

common 的使命是"基础能力",不是"业务共享"。业务共享另有其位。


五、实战第三步:多模块下的配置管理

5.1 每个模块能各配各的吗?

不能。Spring Boot 在启动时只会加载一次application.yml——默认从classpath下找,具体位置是启动类所在模块的resources目录。其他模块的application.yml不会被自动合并。

所以正确的做法是:所有全局配置都放在ruoyi-admin/src/main/resources/下。

5.2 那模块的专属配置怎么办?

用@ConfigurationProperties+ 配置前缀隔离:

// ruoyi-crm 模块中 @Data @Component @ConfigurationProperties(prefix = "crm") public class CrmConfig { /** 客户导入最大行数 */ private int importMaxRows = 5000; /** 商机过期天数 */ private int opportunityExpireDays = 30; }

对应在ruoyi-admin的application.yml中:

# application.yml(只在 admin 中维护) crm: import-max-rows: 5000 opportunity-expire-days: 30 mes: work-order-prefix: "WO" quality-check-levels: IPQC,IPFQC,FQC

每个模块内部注入自己的配置类,互不干扰。

5.3 多环境配置

多模块下的多环境配置跟单模块没区别,仍然用spring.profiles.active:

ruoyi-admin/src/main/resources/ ├── application.yml ← 公共配置 ├── application-dev.yml ← 开发环境 ├── application-test.yml ← 测试环境 └── application-prod.yml ← 生产环境

启动时指定:--spring.profiles.active=dev

核心原则不变:所有 yml 文件都放在ruoyi-admin的 resources 里,业务模块只定义@ConfigurationProperties类。


六、Maven 多模块构建与打包

6.1 统一版本号

拆了多模块后,最怕版本号散落各处。Maven 的<dependencyManagement>是救命稻草:

<!-- 根 pom.xml --> <properties> <ruoyi.version>3.3.0</ruoyi.version> <mybatis-plus.version>3.5.5</mybatis-plus.version> <hutool.version>5.8.28</hutool.version> </properties> <dependencyManagement> <dependencies> <!-- 内部模块版本统一 --> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-common</artifactId> <version>${ruoyi.version}</version> </dependency> <!-- 外部依赖版本统一 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> </dependencies> </dependencyManagement>

子模块引用时不写版本号,由根 pom 统一管控:

<!-- ruoyi-crm/pom.xml --> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-common</artifactId> <!-- 版本号继承根 pom 的 dependencyManagement --> </dependency>

6.2 打包产物

在根目录执行:

mvn clean package

最终在ruoyi-admin/target/下生成一个可执行 jar。所有子模块的 class 都被打包进去了,部署时只需要一个 jar 文件。

6.3 第二个大坑:循环依赖

拆 MES 模块的时候,我遇到过:CRM 需要一个方法来查客户名称 → 我直接在 MES 的 Service 里注入 CRM 的 Service。反过来,CRM 有一段逻辑需要调 MES 的工单状态,我又在 CRM 里注入了 MES 的 Service。

结果:Maven 编译直接报错Circular dependency。

正确的解法:

CRM ←→ MES 互调的场景 → 提取共享接口到 ruoyi-common
// ruoyi-common 中定义接口 public interface CustomerBriefService { CustomerBriefDTO getBrief(Long customerId); } // ruoyi-crm 中实现接口 @Service public class CustomerBriefServiceImpl implements CustomerBriefService { // 实现从 CRM 数据库查客户简要信息 } // ruoyi-mes 中依赖接口,不依赖实现 @Service public class QualityCheckService { @Autowired private CustomerBriefService customerBriefService; // 注入接口 }

Spring 会自动找到ruoyi-crm中的实现并注入。MES 只依赖 common 中的接口,不依赖 CRM 模块本身——循环依赖就这样解了。


七、拆分后的最终架构

拆完后的项目结构:

ruoyi/ ├── ruoyi-common/ # 工具类、异常、基础注解 ├── ruoyi-framework/ # 安全、数据源、全局配置 ├── ruoyi-system/ # 系统管理(用户、角色、菜单) ├── ruoyi-crm/ # CRM 业务模块 ├── ruoyi-mes/ # MES 业务模块 ├── ruoyi-shared-dto/ # 跨模块共享 DTO ├── ruoyi-generator/ # 代码生成器 ├── ruoyi-quartz/ # 定时任务 └── ruoyi-admin/ # 启动模块

拆分后的实际收益

维度拆之前拆之后
启动时间~40 秒~18 秒(只改了 CRM 时热加载更快)
编译时间改一行全量 300+ 类只编译当前模块
代码冲突天天 merge 冲突基本不冲突,各改各的模块
新人上手要弄懂整个 admin 才能改读懂一个模块就能开工
删除成本不敢删,怕影响别处Maven 依赖关系图一眼看穿,敢删

八、总结 & 核心原则

回顾这次拆分,我一直遵循三条原则:

① 依赖只向下。底层模块永远不依赖上层,admin 依赖一切,common 不依赖任何业务。

② 公共代码提 common,业务代码放模块。common 是基础能力集,不是垃圾桶。

③ 一次只拆一个模块。不要想着一次拆干净——拆一个,跑通,再拆下一个。稳定压倒一切。

什么时候该拆?

如果你对下面三个问题有两个回答"是",就该动手了:

  • 启动时间超过 30 秒?

  • 两个人以上在同一个模块里写代码?

  • 你开始纠结"这个类到底该放哪个包"?

什么时候先别拆?

  • 项目还在 Demo 阶段,业务都不确定

  • 只有你一个人写,而且一个月内不会加人

  • 代码量不到 100 个类

拆分是为协作和长期维护服务的,不要为了拆而拆。


如果你也在独立开发产品,或者对制造业数字化感兴趣,欢迎关注这个公众号。我会持续分享从代码到产品的全过程——包括成功的经验,也包括踩过的坑。

一个人的产品之路,不孤单。👇

原创作者 MqCode(全栈开发者,印刷包装行业 MES+CRM 系统独立开发),欢迎自由转发。

相关新闻

  • 泰戈尔的诗歌2
  • 终极Unity游戏汉化指南:XUnity自动翻译器让外语游戏无障碍畅玩
  • 浅析NVMe协议:PRP/SGL数据传输格式

最新新闻

  • 降级——“丢卒保车“的艺术
  • 免费开源图片元数据批量编辑终极指南:ExifToolGUI完全教程
  • Codex++ 配置 Codex 模型教程
  • 告别手忙脚乱!SAP EWM RF手持终端从登录到拣货发货的保姆级实操指南
  • 如何3步搞定多GPU服务器监控:Zabbix智能监控方案终极指南
  • 保姆级教程:手把手教你用SurroundOcc跑通NuScenes数据集(从数据加载到可视化全流程)

日新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号