第一章. 核心基石:ruoyi-common-core 源码浅析


第一章. 核心基石:ruoyi-common-core 源码浅析

摘要:本章我们将对 RVP 5.x 架构中最重要的基础模块 ruoyi-common-core 进行源码“浅析”。common-core 是被所有业务模块和功能插件所依赖的“地基”。本章将概览其内部的配置、常量、实体、工具类和异常体系,帮助你理解 RVP 框架的底层设计规范。

在第一部分(第 1-23 章)中,我们作为“使用者”,已经全面掌握了 RuoYi-Vue-Plus 5.x 架构的理念、所有核心功能的使用、项目配置(YML)和 Maven 构建(POM)。

从本章开始,我们将转换视角,我们专注于学习后端知识,每一章节都提供一个实操流程作为示例 demo,教您快速上手并读懂 Ruoyi-Vue-Plus 的设计哲学

本章学习路径

我们将按照 ruoyi-common-core 模块的内部包结构,分阶段探索这个核心模块的职责:

ruoyi-common-core 源码浅析


1.1. 依赖与 SPI 启动链路

在深入源码之前,我们首先要解决两个问题:common-core 依赖了哪些“工具”?它自己又是如何被 Spring Boot“启动”的?

1.1.1. pom.xml:模块依赖的“工具箱”

ruoyi-common-core/pom.xml 文件定义了 RVP 框架最基础的依赖集合。它不依赖任何其他的 ruoyi-common-* 模块,是一个纯粹的“地基”模块。

它引入的核心依赖(节选)包括:

  • Spring 框架spring-context-support(上下文支持)、spring-web(Web 支持)、spring-boot-starter-aop(AOP 切面)、spring-boot-starter-validation(校验注解)。
  • 工具包commons-iocommons-collections4cn.hutool:hutool-core (Hutool 工具包)。
  • 代码简化org.projectlombok:lombok (Lombok)。
  • 对象映射io.github.linpeilie:mapstruct-plus-spring-boot-starter (MapStruct-Plus)。
  • IP 地址库org.lionsoul:ip2region (离线 IP 地址定位库)。

1.1.2. AutoConfiguration.imports:SPI 启动链路

ruoyi-common-core 作为一个“插件”,它内部的 @Configuration 配置类是如何被 ruoyi-admin 主启动类自动加载的呢?

答案在于 SPI (Service Provider Interface) 机制,这是 Spring Boot 自动装配的标准实现。

文件路径ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

1
2
3
4
org.dromara.common.core.config.ApplicationConfig
org.dromara.common.core.config.ThreadPoolConfig
org.dromara.common.core.config.ValidatorConfig
org.dromara.common.core.utils.SpringUtils

工作机制

  1. RVP 5.x 遵循 Spring Boot 3.x 的自动装配规范。
  2. ruoyi-admin 启动时,Spring Boot 会扫描所有 jar 包中 META-INF/spring/ 目录下的 ...AutoConfiguration.imports 文件。
  3. Spring Boot 读取到 common-core 模块登记的这 4 个类路径。
  4. Spring Boot 自动将这 4 个类加载到 Spring 容器中,触发它们的自动配置(@AutoConfiguration)和初始化(@Bean),从而完成了 common-core 模块的启动。

1.2. config 包:框架基础配置

在 1.1 节中,我们看到 common-core 通过 SPI 机制自动加载了 3 个核心配置类。config 包是框架启动和运行的基础。

1.2.1. ApplicationConfig:AOP 与异步

文件路径.../config/ApplicationConfig.java

1
2
3
4
5
6
@AutoConfiguration
// 开启 Spring AOP 切面
@EnableAspectJAutoProxy
// 开启 @Async 异步支持
@EnableAsync(proxyTargetClass = true)
public class ApplicationConfig { }

这个配置类的作用非常纯粹,通过 @EnableAspectJAutoProxy 开启 AOP 功能(@Log 切面等的基础),并通过 @EnableAsync 允许在代码中使用 @Async 注解执行异步任务。

1.2.2. ThreadPoolConfig:线程池

文件路径.../config/ThreadPoolConfig.java

RVP 框架不推荐开发者在业务代码中随意 new Thread(),而是提供了统一的线程池配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Slf4j
@AutoConfiguration
// 激活 ThreadPoolProperties.java
@EnableConfigurationProperties(ThreadPoolProperties.class)
public class ThreadPoolConfig {

// 核心线程数 = CPU 核心数 + 1
private final int core = Runtime.getRuntime().availableProcessors() + 1;

@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService() {
BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder().daemon(true);
// [RVP 5.x 特性]:判断是否为 JDK 21 虚拟线程环境
if (SpringUtils.isVirtual()) {
builder.namingPattern("virtual-schedule-pool-%d")
.wrappedFactory(new VirtualThreadTaskExecutor().getVirtualThreadFactory());
} else {
builder.namingPattern("schedule-pool-%d");
}
// ...
return new ScheduledThreadPoolExecutor(core, builder.build(), ...);
}
// ... 包含 @PreDestroy 销毁钩子 ...
}
  • 职责:向 Spring 容器注册一个全局的、可调度的线程池 scheduledExecutorService
  • 特性:RVP 5.x 适配了 JDK 21 的虚拟线程。如果检测到虚拟线程环境,线程工厂 (ThreadFactory) 会使用虚拟线程来执行任务,提高并发性能。
  • 销毁:通过 @PreDestroy 钩子,确保在 Spring Boot 应用关闭时,能够“优雅停机”,等待线程池任务执行完毕。

1.2.3. ValidatorConfig:校验器

文件路径.../config/ValidatorConfig.java

此配置类用于自定义 RVP 框架的 参数校验器validation)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.dromara.common.core.config;
@AutoConfiguration(before = ValidationAutoConfiguration.class)
public class ValidatorConfig {

@Bean
public Validator validator(MessageSource messageSource) {
try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
// 国际化
factoryBean.setValidationMessageSource(messageSource);
// 设置使用 HibernateValidator 校验器
factoryBean.setProviderClass(HibernateValidator.class);
Properties properties = new Properties();
// 设置快速失败模式(fail-fast),即校验过程中一旦遇到失败,立即停止并返回错误
properties.setProperty("hibernate.validator.fail_fast", "true");
factoryBean.setValidationProperties(properties);
// 加载配置
factoryBean.afterPropertiesSet();
return factoryBean.getValidator();
}
}

}
  • Fail-Fast 模式hibernate.validator.fail_fast = true 是一个重要的性能优化。它告诉校验器,当校验一个 DTO 时,只要遇到第一个不合法的字段,就立即抛出异常,不再 继续校验剩余字段。
  • 国际化 (i18n)setValidationMessageSource(messageSource) 将校验器的错误信息与 RVP 的国际化资源文件(messages_zh_CN.properties 等)进行绑定。

1.2.4. ThreadPoolProperties:YML 属性 Bean

文件路径.../config/properties/ThreadPoolProperties.java

这个类是 ThreadPoolConfig 的“数据源”,它使用 @ConfigurationProperties 注解来 映射 YML 文件中的属性

1
2
3
4
5
6
7
8
9
10
11
@Data
@ConfigurationProperties(prefix = "thread-pool")
public class ThreadPoolProperties {

/** 是否开启线程池 */
private boolean enabled;
/** 队列最大长度 */
private int queueCapacity;
/** 线程池维护线程所允许的空闲时间 */
private int keepAliveSeconds;
}
  • @ConfigurationProperties(prefix = "thread-pool"):在 application.yml 中定义的 thread-pool.queue-capacity 等属性,会被 Spring Boot 自动注入到这个 Java Bean 的 queueCapacity 字段中。

1.3. constant 包:全局硬编码常量

constant 包的职责是“消除魔法值”。它将 RVP 框架中所有需要“硬编码”的字符串或数字,统一定义为静态常量,便于复用和维护。

  • CacheConstants.java
    定义所有 Redis 缓存键 (Key) 的前缀

    1
    2
    3
    4
    5
    6
    7
    8
    // 在线用户令牌
    String ONLINE_TOKEN_KEY = "online_tokens:";
    // 系统配置
    String SYS_CONFIG_KEY = "sys_config:";
    // 数据字典
    String SYS_DICT_KEY = "sys_dict:";
    // 密码错误次数
    String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
  • Constants.java
    定义最通用的常量,如编码、状态码、默认值等。

    1
    2
    3
    4
    5
    6
    7
    String UTF8 = "UTF-8";
    // 成功标记
    String SUCCESS = "0";
    // 验证码有效期(分钟)
    Integer CAPTCHA_EXPIRATION = 2;
    // 树结构顶级 ParentID
    Long TOP_PARENT_ID = 0L;
  • HttpStatus.java
    统一定义 HTTP 响应状态码。

    1
    2
    3
    4
    5
    6
    7
    8
    // 成功
    int SUCCESS = 200;
    // 客户端错误
    int BAD_REQUEST = 400;
    // 服务端错误
    int ERROR = 500;
    // [RVP 自定义] 警告
    int WARN = 601;
  • TenantConstants.java
    定义多租户相关的常量。

    1
    2
    3
    4
    5
    6
    // 超级管理员的用户ID
    Long SUPER_ADMIN_ID = 1L;
    // 租户管理员的角色 Key
    String TENANT_ADMIN_ROLE_KEY = "admin";
    // 默认租户ID(平台)
    String DEFAULT_TENANT_ID = "000000";

1.4. domain 包:核心数据模型

domain 包定义了在 RVP 框架各个模块之间流转的核心数据结构,包括统一响应体、登录实体和 DTO。

1.4.1. R.java:统一响应体

R.java 是 RVP 框架 API 响应的 统一包装器。所有 Controller 返回给前端的数据,都必须封装在 R 对象中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class R<T> implements Serializable {
// 状态码
private int code;
// 消息
private String msg;
// 数据
private T data;

// 静态工厂方法:成功
public static <T> R<T> ok(T data) {
return restResult(data, HttpStatus.SUCCESS, "操作成功");
}
// 静态工厂方法:失败
public static <T> R<T> fail(String msg) {
return restResult(null, HttpStatus.ERROR, msg);
}
// 静态工厂方法:警告
public static <T> R<T> warn(String msg) {
return restResult(null, HttpStatus.WARN, msg);
}
// ...
}

设计目的:确保所有 API 返回给前端的数据结构(code, msg, data)完全一致,便于前端进行统一的响应拦截和处理。

1.4.2. LoginUser.java:登录会话

LoginUser 是 RVP 中 最核心 的实体之一。它代表一个用户登录成功后的“会话上下文”,Sa-Token 会将这个对象存储到 Redis 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LoginUser implements Serializable {
private Long userId;
private Long deptId;
// [核心] 租户ID
private String tenantId;
private String username;
private String token;
// 客户端 Key
private String clientKey;
// 设备类型 (pc, app)
private DeviceType deviceType;
// [核心] 菜单权限标识
private Set<String> menuPermission;
// [核心] 角色信息
private List<RoleDTO> roles;

// ...
}

设计目的:当用户请求 API 时,LoginHelper.getLoginUser() 会从 Redis(通过 Sa-Token)中反序列化出此对象。后续的“权限校验”、“数据权限”等操作,全部依赖于 LoginUser 中存储的 menuPermissionrolesdeptId 等信息。

1.4.3. LoginBody 系列:登录参数 DTO

RVP 5.x 支持多种登录方式,common-core 模块为每种方式都定义了专属的 DTO (Data Transfer Object) 来接收前端参数。

登录 DTO关键字段适用场景
LoginBodyclientId, tenantId, uuid所有登录方式的“基类”
PasswordLoginBodyusername, password(该类在 domain.model 中)
SmsLoginBodyphonenumber, smsCode短信验证码登录
EmailLoginBodyemail, emailCode邮箱验证码登录
SocialLoginBodysource, socialCodeGitee/GitHub 等三方登录
XcxLoginBodyappid, xcxCode微信小程序登录

1.4.4. DTO 系列:数据传输对象

DTO (Data Transfer Object) 用于 模块与模块之间 的数据传输。

设计目的common-core 定义了 UserDTORoleDTO 等 DTO 接口,用于在 ruoyi-systemruoyi-demo 等模块间传递数据。这实现了模块解耦:ruoyi-demo 只需依赖 common-core 中的 UserDTO,而 无需 依赖 ruoyi-system 模块中的 SysUser 实体,避免了模块间的强耦合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// .../domain/dto/UserDTO.java
public class UserDTO implements Serializable {
private Long userId;
private String userName;
private String status;
// ...
}

// .../domain/dto/RoleDTO.java
public class RoleDTO implements Serializable {
private Long roleId;
private String roleKey;
private String dataScope;
// ...
}

1.5. enums 包:通用枚举

enums 包提供了 RVP 框架中 最常用 的几种枚举,用于替代“魔法值”(如 0, 1)。

  • DeviceType.java:设备类型(PC, APP, XCX 小程序, SOCIAL 三方)。
  • LoginType.java:登录类型(PASSWORD, SMS, EMAIL, XCX)。
  • UserStatus.java:用户状态(OK("0", "正常"), DISABLE("1", "停用"))。
  • UserType.java:用户类型(SYS_USER("sys_user"), APP_USER("app_user"))。

1.6. exception 包:统一异常体系

RVP 框架的异常处理是“集中式”的(由 ruoyi-common-web 中的 GlobalExceptionHandler 捕获)。common-coreexception 包则负责定义“抛出什么”。

1.6.1. BaseException:异常基类

文件路径.../exception/base/BaseException.java

RVP 中所有自定义异常的“根”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class BaseException extends RuntimeException {
// 所属模块
private String module;
// 错误码 (对应 i18n key)
private String code;
// 错误码参数
private Object[] args;
// 默认错误信息
private String defaultMessage;

@Override
public String getMessage() {
String message = null;
if (!StringUtils.isEmpty(code)) {
// [核心]:调用工具类,从 i18n 资源文件(如 messages_zh_CN.properties)中
// 根据 code 和 args 动态获取格式化后的错误信息
message = MessageUtils.message(code, args);
}
// ...
return message == null ? defaultMessage : message;
}
}

设计目的BaseException 的核心是通过重写 getMessage() 方法,将“异常”与“i18n 国际化”绑定。当抛出一个带 code 的异常时,框架会自动去 messages.properties 文件中查找对应的错误文案。

1.6.2. ServiceException:业务异常

文件路径.../exception/ServiceException.java

这是 RVP 框架中 最常用 的异常类。当业务逻辑校验失败时(例如“用户名已存在”),应抛出此异常。

1
2
3
4
5
6
7
8
9
public class ServiceException extends RuntimeException {
// ...
public ServiceException(String message, Object... args) {
// [核心]:使用 Hutool 的 StrFormatter.format
// 允许使用 {} 占位符
this.message = StrFormatter.format(message, args);
}
// ...
}

应用场景
throw new ServiceException("用户ID {} 不存在", userId);
GlobalExceptionHandler 会捕获这个异常,并将其 message 封装到 R.fail() 中返回给前端。

1.6.3. 其他异常

  • FileException:继承 BaseExceptionmodule 固定为 file,用于文件上传相关错误。
  • CaptchaException:继承 UserExceptionuser 模块),专用于验证码错误(code 固定为 user.jcaptcha.error)。

1.7. factory 包:YML 加载器

factory 包提供了 RVP 5.x 配置加载的关键工具。

文件路径.../factory/YmlPropertySourceFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class YmlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
String sourceName = (name != null) ? name : resource.getResource().getFilename();
// [核心]:判断文件是否为 .yml 或 .yaml
if (StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) {
// 使用 YamlPropertiesFactoryBean 将 YML 语法转为 Properties 键值对
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return new PropertiesPropertySource(sourceName, factory.getObject());
}
// ...
return new ResourcePropertySource(name, resource);
}
}

设计目的:Spring 的 @PropertySource 注解默认只认识 .properties 文件。YmlPropertySourceFactory 的作用就是“翻译”,它告诉 @PropertySource 如何读取 .yml 文件并将其转换为 Spring 能理解的 PropertySource 对象。


1.8. service 包:“接口下沉”模式

这是 RVP 5.x 架构为了 解决“循环依赖”而设计的最核心的模式

1.8.1. 痛点:模块间的“循环依赖”

在 4.x 架构中,模块职责不清,很容易出现:

  1. ruoyi-demo(模块 A)需要调用“获取用户信息”的功能。
  2. ruoyi-system(模块 B)实现了“获取用户信息”的功能。
  3. 因此,ruoyi-demo 必须在 pom.xml 中依赖 ruoyi-system
  4. 问题出现:如果 ruoyi-system 模块(例如工作流)也需要调用 ruoyi-demo 中的某个服务,就产生了 A -> B -> A 的循环依赖,导致 Maven 构建失败。

1.8.2. RVP 5.x 解决方案:“接口下沉”

RVP 5.x 将这种“跨模块”需要调用“公共服务”“接口 (Interface),全部“下沉”到了最底层的 ruoyi-common-core 模块中。

文件路径.../service/UserService.java (接口)

1
2
3
4
5
6
7
8
9
// 位于 common-core (最底层)
public interface UserService {
// 示例:根据用户ID查询昵称 (供 translation 模块使用)
String selectNicknameByIds(String userIds);
// 示例:根据角色ID查询用户 (供 system 模块内部使用)
List<UserDTO> selectUsersByRoleIds(List<Long> roleIds);
// 示例:根据用户ID批量查询用户名 (供 demo 模块使用)
Map<Long, String> selectUserNamesByIds(List<Long> userIds);
}

文件路径.../service/DictService.java (接口)

1
2
3
4
5
6
7
// 位于 common-core (最底层)
public interface DictService {
// 示例:根据字典类型和值,获取标签 (供 excel 模块使用)
String getDictLabel(String dictType, String dictValue, String separator);
// 示例:根据字典类型获取数据 (供 system 模块使用)
List<DictDataDTO> getDictData(String dictType);
}

common-core 中还定义了 DeptServiceConfigServiceOssService 等多个下沉接口。

1.8.3. 实现类:SysUserServiceImpl

接口在 common-core 中定义,但 实现在 ruoyi-system 模块 中。

文件路径ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 位于 ruoyi-system (业务层)
@Service
public class SysUserServiceImpl implements ISysUserService, UserService { // [核心] 实现了 UserService 接口

// ... 注入了 SysUserMapper 等 ...

// [实现] 对 common-core 接口的实现
@Override
@Cacheable(cacheNames = CacheNames.SYS_USER_NAME, key = "#userId")
public String selectUserNameById(Long userId) {
// ...
}

// [实现] 对 common-core 接口的实现
@Override
public Map<Long, String> selectUserNamesByIds(List<Long> userIds) {
// ...
}
}

最终效果(依赖解除)

  1. ruoyi-system 依赖 common-core (实现 UserService)。
  2. ruoyi-demo 依赖 common-core (调用 UserService)。
  3. ruoyi-demoruoyi-system 之间 没有任何 Maven 依赖关系
  4. ruoyi-demo 调用 UserService 接口时,Spring 的 IoC 容器会自动注入 ruoyi-system 中的 SysUserServiceImpl 实例,成功实现跨模块调用,彻底解决了循环依赖

1.9. utils 包:核心工具类

utils 包提供了大量静态工具类,是 RVP 框架功能实现的核心“齿轮”。

工具类核心职责示例
SpringUtils.java[核心] Spring 容器持有器。在非 Spring 管理的类中,获取 Bean 或激活 profile。getBean(Class<T> clazz)
isVirtual() (判断是否虚拟线程)
MessageUtils.java国际化 (i18n) 工具。用于从 messages_zh_CN.properties 中获取文案。message(String code, Object... args)
StringUtils.java字符串工具(扩展 Hutool)。isMatch() (Ant 路径匹配)
splitTo(String str, ...) (带转换的分割)
DateUtils.java日期工具(扩展 Hutool)。validateDateRange(start, end, max) (校验日期范围)
AddressUtils.javaIP 地址工具。resolverIPv4Region(ip) (使用 ip2region 库解析 IP 归属地)
ReflectUtils.java反射工具。invokeGetter(obj, field)
invokeSetter(obj, field, value)
SqlUtil.javaSQL 工具。filterKeyword(value) (基础 SQL 注入关键词过滤)
TreeBuildUtils.java树结构工具。build(list, parser) (基于 Hutool 的 TreeUtil 封装)
ValidatorUtils.java校验工具。validate(object, groups) (手动触发 Bean 校验)

1.10. validate 包:校验分组

最后,validate 包配合 ValidatorConfigValidatorUtils,提供了“分组校验”的能力。

文件路径.../validate/AddGroup.java

1
public interface AddGroup { }

文件路径.../validate/EditGroup.java

1
public interface EditGroup { }

文件路径.../validate/QueryGroup.java

1
public interface QueryGroup { }

设计目的:这些 空接口(标记接口),用于在 DTO/BO 中对校验规则进行分组。

应用场景:在 SysUserBo(用户 BO)中:

1
2
3
4
5
6
7
8
9
10
11
// 示例
public class SysUserBo {

// 只有“新增时”(AddGroup) 才校验非空
@NotBlank(message = "密码不能为空", groups = { AddGroup.class })
private String password;

// “新增”和“修改时”(EditGroup) 都校验
@NotBlank(message = "昵称不能为空", groups = { AddGroup.class, EditGroup.class })
private String nickName;
}

当 Controller 中使用 @Validated(AddGroup.class) 时,框架 只校验 passwordnickName;当使用 @Validated(EditGroup.class) 时,框架 只校验 nickName忽略 password