本文最后更新于 2025年2月5日
1.引言
芋道(又名yudao,ruoyi-vue-pro)是一个基于spring-boot框架的单体Java后端开源项目,拥有基于RBAC模型的组织架构管理、CRM、ERP、商城、代码生成、AI等多个功能模块。封装了多租户、数据权限、工作流、OAuth,邮件、短信、定时任务、日志、链路追踪等多种技术和业务组件。其在GitHub上的地址是:https://github.com/YunaiV/ruoyi-vue-pro
因工作中会用到这个开源项目,为了更好的定制和更新功能,所以决定把它的源码核心部分都通读并运行调试一遍,并把过程通过博客记录下来,内容持续更新,边学习,边输出,做知识积累整理。
本文基于2.4.0-jdk8-SNAPSHOT版本的源码。
2.项目总体结构
项目基于传统的maven构建,大致结构如下,整个项目是多模块结构,分为1个父模块和多个子模块。
3.模块的结构,功能和依赖关系
3.1 root
最外层的/pom.xml作为root模块的配置,通过<modules/>
包含了yudao-framework,yudao-module-xxxxxx,yudao-server,yudao-dependencies等众多模块。
root模块通过引用负责统一依赖版本的模块yudao-dependencies来将依赖的版本号传递给所有子模块,从而统一整个项目的依赖版本
root模块使用<version>${revision}</version>
来设置自身的版本号,子模块的<version/>
如果也设置为${revision}
的话,就继承了root模块的版本号了,子模块的子模块也是一样的道理,这样整个工程所有子孙模块的版本号就都统一起来了,需要升级版本时,只需要在root模块的pom.xml文件中,把<properties/>
里面的版本号一改,整个工程所有子孙模块的版本号便全部跟着变了。
例:
/pom.xml
yudao-module-system/pom.xml
yudao-module-system/yudao-module-system-api/pom.xml
通过插件org.codehaus.mojo:flatten-maven-plugin
来防止项目拆分后版本号无法传递的问题
如果将此项目进行拆分,将业务模块(yudao-module-xxxxx)和框架模块(yudao-framework)等进行切割分离,形成两个或更多个独立的maven工程,就会出现版本号无法通过${revision}
传递的问题。
例如A工程的pom.xml通过${revision}
将版本同步给A工程的子模块B,新建的另一个独立工程C如果依赖了B会报错找不到B的父工程A,因为C工程会将"${revision}"
这个字符串作为A的版本号,而不是取${revision}
代表的值,所以如果要把该项目拆分成一个个单独的服务,A工程需要引入org.codehaus.mojo:flatten-maven-plugin
插件进行特殊处理,引入这个插件后,执行mvn install
命令时,打入本地仓库的坐标版本号就是${revision}
代表的值,mvn deploy
到远程仓库同理。
导致这个问题出现的原因是拆分后Maven依赖的优先级发生了改变,如果不拆分,在同一个工程下的话,依赖会从工程自身的模块开始查找,找到了就不再访问本地仓库和远程仓库,这时由于是同一个工程下${revision}
是能互相访问到的。但是一旦被拆分,被拆分出去的工程无法在工程自身的模块中查找依赖了(因为根本没有),便会用本地或远程仓库寻找依赖,被依赖的模块没有使用flatten-maven-plugin插件的话,其mvn install
, mvn deploy
到仓库时,版本号就是错的,无法被正确引用。
3.2 yudao-dependencies
这个模块内仅有一个pom.xml文件,该模块的作用仅仅是统一整个项目的依赖版本,因为yudao-dependencies模块没有指定<parent/>
,因此不能从父(即root)模块继承${revision}
,需要在自己的<properties/>
里面维护自己的${revision}
版本供自己引用,版本号的值一般要与root模块中的版本号要保持一致。
严格来讲,yudao-dependencies模块并不是root模块的子模块,因为如果root模块成了yudao-dependencies的父模块的同时还引用了子模块yudao-dependencies的话,就会导致循环引用,因此yudao-dependencies没有指定<parent/>
,只是由root模块通过<modules/>
包含进去进行代管,root模块构建时,yudao-dependencies会一并构建,但不能继承root的</properties>
,<version>${revision}</version>
,</dependency>
和</plugin>
等。
yudao-dependencies里面只有一个pom.xml文件,其使用<dependencyManagement/>
声明了整个项目所需要的依赖,并被root模块引入,从而统一整个工程的依赖版本。
yudao-dependencies不仅通过引用springframework,spring-boot-dependencies等type为pom的依赖项来继承第三方框架的版本,还规定了项目自身封装的一些框架(yudao-framework)的版本号。
同理,需要通过插件org.codehaus.mojo:flatten-maven-plugin
来防止项目拆分后版本号无法传递的问题
3.3 yudao-framework
该模块内主要是需要用到的公共依赖和一些对常用框架和功能组件的封装,大致结构如下
yudao-framework下没有其他依赖,只是简单的将所有封装的组件聚合起来
yudao-framework/pom.xml
yudao-common模块封装了一些项目公共的枚举类,异常类,公共的实体类,和一些工具类,在这个项目中通常会被其他组件模块(yudao-spring-boot-starter-xxxx)和业务模块的api模块(yudao-module-xxxxx-api)所引用。
除了yudao-common外其余的都是封装的框架功能模块,模块名格式为yudao-spring-boot-starter-xxxx,分为业务组件和技术组件。技术组件模块名中没有biz,业务组件是有的。业务组件通常会引用业务模块的api模块(yudao-module-xxxxx-api)
例如数据权限yudao-spring-boot-starter-biz-data-permission组件依赖了系统管理业务模块的api:yudao-module-system-api
该模块下的包名都以cn.iocoder.yudao.framework开头,后面是组件名称,然后再往下大多又分成config和core两个包,config包下是spring-boot的配置类,与组件本身的配置有关,core包下是组件具体功能的实现代码,需要注意的是config包下的配置类会配合resources/META-INF.spring下的org.springframework.boot.autoconfigure.AutoConfiguration.imports文件使用,配置类的类路径只有配在这个文件中,才会被spring扫描到,然后将组件注入spring容器中,供其他业务模块使用。
framework模块之间也可以引用,例如yudao-spring-boot-starter-biz-data-permission就依赖了yudao-spring-boot-starter-security,yudao-spring-boot-starter-mybatis和yudao-spring-boot-starter-test,但不能互相引用,因为必然导致maven循环引用错误。
3.4 yudao-module-xxxxx
yudao-module-xxxxx模块是实现具体业务的模块,具体结构如下:
(xxxxx为业务名,aaa,bbb为业务下的具体功能名)
整个项目的Controller, Service, Mapper都封装在业务模块里,业务模块是根据具体的业务来建立的。
每个业务模块都由yudao-module-xxxxx-api和yudao-module-xxxxx-biz两个子模块组成。yudao-module-xxxxx-api模块中是开放给其他业务模块或业务组件调用的接口代码和一些公共的枚举和常量,yudao-module-xxxxx-biz模块中是具体业务的实现代码,因为api定义的接口是biz实现的,因此biz模块首先要依赖它自己要实现的api模块。
模块内包名都是固定前缀cn.iocoder.yudao加module再加业务模块名的形式,例如:cn.iocoder.yudao.module.xxxxx,在此基础上根据所属层级建立下一级包名,例如cn.iocoder.yudao.module.xxxxx.controller.admin,cn.iocoder.yudao.module.xxxxx.service,然后根据具体业务功能再建立更深层级的包名和包下的类,例如:cn.iocoder.yudao.module.xxxxx.controller.admin.aaa.vo。
包名解释:
yudao-module-xxxxx-api
yudao-module-xxxxx-biz
cn.iocoder.yudao.module.xxxxx.api 包存放对api模块定义的接口类的实现(***ApiImpl),实现类为Spring容器管理,被Spring注入到调用者引用的Api接口上,ApiImpl和Controller一样,接收到调用后再调用业务层Service代码。
cn.iocoder.yudao.module.xxxxx.controller 分为admin和app两个子包,分别放置管理员接口和会员接口,包中存放Controller类及接收和生成JSON的实体类VO,接收http请求并返回数据。
cn.iocoder.yudao.module.xxxxx.service 包下是具体的Service业务接口和实现类。
cn.iocoder.yudao.module.xxxxx.dal 包是负责数据库访问的DAO层,分为dataobject和mysql两个包,dataobject包内存放的是DO对象,mysql包内存放的是Mybatis/Mybatis-Plus的Mapper类,Java代码无法实现的复杂SQL,可在resources文件夹内定义”*Mapper.xml”文件实现。
cn.iocoder.yudao.module.xxxxx.convert 包功能比较简单,用于存放mapstruct转换器类,用于各种不同类型的实体类对象之间的深拷贝互相转换。
cn.iocoder.yudao.module.xxxxx.mq 消息发送接收。
cn.iocoder.yudao.module.xxxxx.job 定时任务。
cn.iocoder.yudao.module.xxxxx.framework 配合yudao-framework模块封装的框架和功能来实现一些更高级的功能,例如文档生成,数据权限等等。
……
业务模块biz之间是相互独立的,如biz模块间要相互调用,只要互相引用对方的api模块坐标到自己biz的pom.xml即可,这样的模块依赖方式完美遵循依赖倒置原则,如果是biz直接引用biz不但违背依赖倒置原则,而且可能还会导致maven构建时报出循环引用的错误。本项目中后续还会出现业务组件框架模块(yudao-spring-boot-starter-biz-xxxxxxxx)依赖具体业务模块的情况,同样也是需要引用业务模块的api。
例:
3.5 yudao-server
yudao-server是启动项目的模块,里面有spring-boot主启动类cn.iocoder.yudao.server.YudaoServerApplication,缺省的请求处理类cn.iocoder.yudao.server.controller.DefaultController,不同环境的配置文件application-*.yml,还有一个logback的日志配置文件logback-spring.xml。
yudao-server模块汇聚了所有的业务模块,打包上线的可执行jar包就是这个模块编译而成的,该模块聚合了所有的业务模块的biz模块(yudao-module-***-biz)以及一些需要直接引用的starter,需要启用哪个业务模块就可以按需引入哪个业务模块。
/yudao-server/pom.xml中,引入了项目最核心的两个业务模块:系统管理yudao-module-system-biz和服务保障yudao-module-infra-biz,默认不引入其他业务模块从而加快编译速度,还引入了一些其他的starter,最后通过spring-boot-maven-plugin插件将此模块代码打包为可执行的jar包,从而启动整个项目。
cn.iocoder.yudao.server.YudaoServerApplication是整个项目的主启动类,通过注解@SpringBootApplication(scanBasePackages = {"${yudao.info.base-package}.server", "${yudao.info.base-package}.module"})
将cn.iocoder.yudao.module下的包列入Spring扫描范围,用于实例化module模块中的类,并纳入Spring容器管理,这也是业务模块(yudao-module-xxx-xxx)下的子包和类必须放在cn.iocoder.yudao.module包下的原因。
controller包下定义了一个缺省的cn.iocoder.yudao.server.controller.DefaultController类,如果被调用的接口所在的模块没有被yudao-server引入,就会被这个类中带着路径通配符的接口方法“兜底”,给出对应的错误提示,这个也是芋道源码中比较精巧的设计之一。
3.6 模块间关系图
1.依赖关系,箭头由被引用模块指向引用模块
2.继承关系,箭头由父级指向子级
开篇写完,已经是除夕前两天,源码解析部分要等到年后了[手动滑稽]
4.源码解析
序号 |
文章 |
主要涉及模块 |
概述 |
1 |
数据权限的实现 |
yudao-spring-boot-starter-biz-data-permission |
数据权限是系统的基本功能之一,实现不同角色的用户看到的数据范围是不一样的。 |
2 |
多租户的实现 |
yudao-spring-boot-starter-biz-tenant |
实现多个组织架构共享系统资源,不同组织的用户和数据以及各种后台功能相互独立的多租户功能。 |