若依项目做到第三个月,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-systemruoyi-admin
ruoyi-system用户/角色/菜单/部门等系统管理业务ruoyi-commonruoyi-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-adminapplication.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 系统独立开发),欢迎自由转发。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐