Loading...
正在加载...
请稍候

RuoYi-Vue-Plus 项目详解:架构、原理与设计思想

✨步子哥 (steper) 2025年10月13日 04:41
RuoYi-Vue-Plus 项目详解:架构、原理与设计思想

RuoYi-Vue-Plus 项目详解:架构、原理与设计思想

项目概述

RuoYi-Vue-Plus 是基于 Vue3 和 SpringBoot 的一款现代化后台管理系统,它对原始的 RuoYi-Vue 进行了全面重构,专为分布式集群和多租户场景设计。项目不仅代码和文档开放且免费,还提供了高度的可定制性和扩展性,适用于商业应用。

infoRuoYi-Vue-Plus 是 dromara 组织下的开源项目,完全遵循 MIT 开源协议,可以免费用于个人及商业用途。

项目定位

RuoYi-Vue-Plus 定位为企业级多租户后台管理系统,旨在提供一个开箱即用、高度可扩展的开发平台,使开发者能够快速构建企业级应用,减少重复开发工作,提高开发效率。

主要特点

  • 前后端分离:采用 Vue3 + SpringBoot 的前后端分离架构,便于团队协作和独立部署
  • 多租户支持:原生支持多租户架构,实现数据隔离和权限控制
  • 插件化设计:核心功能模块化,支持插件式扩展,降低系统耦合度
  • 权限精细控制:基于 Sa-Token 的权限认证和授权,支持数据权限和菜单权限
  • 工作流引擎:集成 Warm-Flow 工作流引擎,支持复杂业务流程设计
  • 代码生成:提供强大的代码生成器,一键生成前后端代码

技术架构

前端技术栈

  • Vue 3:采用 Vue 3 作为前端框架,利用 Composition API 提高代码复用性和可维护性
  • TypeScript:全面使用 TypeScript 增强代码类型安全性和开发体验
  • Element Plus:基于 Element Plus 组件库构建 UI 界面,提供丰富的组件和主题定制能力
  • Vite:使用 Vite 作为构建工具,提供快速的开发服务器和优化的生产构建
  • Pinia:采用 Pinia 作为状态管理库,替代 Vuex,提供更简洁的 API 和更好的 TypeScript 支持

后端技术栈

  • Spring Boot:基于 Spring Boot 3.x 框架,简化应用开发和部署
  • Spring Security:集成 Spring Security 提供安全认证和授权功能
  • MyBatis-Plus:使用 MyBatis-Plus 作为 ORM 框架,简化数据库操作
  • Sa-Token:采用 Sa-Token 替代传统 Shiro,提供更轻量、更强大的权限控制
  • Hutool:集成 Hutool 工具库,提供丰富的 Java 工具方法
  • Warm-Flow:集成 Warm-Flow 工作流引擎,支持复杂业务流程设计

系统架构设计

前端层 (Vue3 + Element Plus)
后端层 (Spring Boot + Sa-Token)
数据层 (MySQL + Redis)

系统采用分层架构设计,清晰划分各层职责,实现高内聚低耦合。前端层负责用户界面展示和交互,后端层负责业务逻辑处理和数据访问,数据层负责数据存储和缓存。

模块划分

项目采用模块化设计,主要包含以下核心模块:

  • ruoyi-admin:系统启动模块,包含系统配置和启动类
  • ruoyi-common:公共模块,包含工具类、通用注解和异常处理等
  • ruoyi-framework:框架核心模块,包含安全、权限、缓存等核心功能
  • ruoyi-system:系统管理模块,包含用户、角色、菜单等系统管理功能
  • ruoyi-job:定时任务模块,提供定时任务调度功能
  • ruoyi-generator:代码生成模块,提供代码生成功能

核心特性

apartment多租户支持

原生支持多租户架构,通过租户 ID 实现数据隔离,每个租户拥有独立的数据空间,确保数据安全。支持动态数据源切换,可配置不同租户使用不同数据库。

security权限管理

基于 Sa-Token 的权限认证和授权,支持 JWT 认证。提供细粒度的权限控制,包括菜单权限、按钮权限和数据权限,满足企业级应用的复杂权限需求。

account_tree工作流引擎

集成 Warm-Flow 工作流引擎,支持可视化流程设计、动态流程调整和复杂分支逻辑。与系统权限体系无缝集成,实现精细化的流程权限控制。

code代码生成

提供强大的代码生成器,支持一键生成前后端代码。可根据数据库表结构生成实体类、Mapper、Service、Controller 以及前端页面,大幅提高开发效率。

数据权限实现

RuoYi-Vue-Plus 5.3.0 版本对数据权限系统进行了彻底重构,采用基于 Mybatis-Plus 插件的数据权限解决方案,实现了权限控制与业务逻辑的解耦。

Java
/**
 * 数据权限注解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
    /**
     * 表别名
     */
    String tableAlias() default "";
    
    /**
     * 部门ID字段名
     */
    String deptId() default "";
    
    /**
     * 用户ID字段名
     */
    String userId() default "";
}

通过 @DataScope 注解,可以在 Mapper 接口上声明数据权限规则,Mybatis-Plus 插件会自动根据当前用户的角色数据权限,动态生成对应的 WHERE 条件,实现数据权限的无感过滤。

多数据源支持

系统支持多数据源配置,可以同时连接多种类型的数据库,实现读写分离和分库分表。通过动态数据源路由,可以根据业务需求自动切换数据源。

YAML
spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/ry-vue
          username: root
          password: password
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave:
          url: jdbc:mysql://localhost:3307/ry-vue
          username: root
          password: password
          driver-class-name: com.mysql.cj.jdbc.Driver

设计思想

架构思想

RuoYi-Vue-Plus 遵循以下架构思想:

  • 分层架构:采用经典的分层架构,清晰划分各层职责,实现高内聚低耦合
  • 模块化设计:将系统功能划分为多个独立模块,每个模块负责特定功能,便于维护和扩展
  • 插件化扩展:核心功能采用插件化设计,支持动态加载和卸载,提高系统灵活性
  • 前后端分离:采用前后端分离架构,前端负责界面展示和用户交互,后端负责业务逻辑和数据处理

开发模式

项目采用以下开发模式:

  • 约定优于配置:遵循约定优于配置的原则,减少配置工作,提高开发效率
  • 面向接口编程:采用面向接口编程的方式,定义清晰的接口规范,降低模块间耦合
  • 领域驱动设计:在核心业务模块采用领域驱动设计思想,以业务领域为中心组织代码
  • 测试驱动开发:鼓励编写单元测试和集成测试,确保代码质量和系统稳定性

设计原则

项目遵循以下设计原则:

  • 单一职责原则:每个类和模块只负责一项功能,保持职责单一
  • 开闭原则:对扩展开放,对修改关闭,通过扩展而非修改来增加新功能
  • 里氏替换原则:子类必须能够替换其基类,保持系统的稳定性
  • 接口隔离原则:使用多个专门的接口,而不是使用单一的总接口
  • 依赖倒置原则:依赖于抽象而非具体实现,降低模块间耦合

原理分析

权限控制原理

RuoYi-Vue-Plus 采用 Sa-Token 作为权限控制框架,其核心原理如下:

Java
/**
 * 权限服务接口
 */
public interface PermissionService {
    /**
     * 获取角色数据权限
     *
     * @param roleId 角色ID
     * @return 数据权限范围
     */
    Set<Long> getRoleDataScope(Long roleId);
    
    /**
     * 获取菜单数据权限
     *
     * @param userId 用户ID
     * @return 菜单权限标识集合
     */
    Set<String> getMenuPermission(Long userId);
}

权限控制的核心是通过 PermissionService 接口实现的,该接口定义了获取角色数据权限和菜单权限的方法。系统在用户登录时,会加载用户的权限信息并缓存,后续请求通过拦截器进行权限校验。

多租户实现原理

多租户是 RuoYi-Vue-Plus 的核心特性之一,其实现原理如下:

Java
/**
 * 多租户拦截器
 */
public class TenantInterceptor implements InnerInterceptor {
    
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,
                           RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        // 获取租户ID
        Long tenantId = TenantHelper.getTenantId();
        if (tenantId == null) {
            return;
        }
        
        // 修改SQL,添加租户条件
        String originalSql = boundSql.getSql();
        String newSql = originalSql + " AND tenant_id = " + tenantId;
        ReflectUtil.setFieldValue(boundSql, "sql", newSql);
    }
}

多租户实现的核心是通过 Mybatis-Plus 的拦截器机制,在 SQL 执行前动态添加租户条件,实现数据隔离。系统通过 TenantHelper 工具类获取当前租户 ID,并在 SQL 中自动添加租户条件,确保每个租户只能访问自己的数据。

工作流引擎原理

RuoYi-Vue-Plus 集成了 Warm-Flow 工作流引擎,其核心原理如下:

Java
/**
 * 工作流服务接口
 */
public interface IFlwCommonService {
    /**
     * 构建工作流用户
     *
     * @param userId 用户ID
     * @return 工作流用户对象
     */
    User buildWorkFlowUser(Long userId);
    
    /**
     * 启动流程
     *
     * @param processKey 流程定义键
     * @param businessKey 业务键
     * @param variables 变量
     * @return 流程实例ID
     */
    String startProcess(String processKey, String businessKey, Map<String, Object> variables);
}

工作流引擎的核心是通过流程定义、流程实例和任务三个核心概念实现的。流程定义定义了业务流程的结构和规则,流程实例是流程定义的一次执行,任务是流程实例中的具体工作项。系统通过工作流服务接口提供流程启动、任务处理、流程查询等功能。

插件化设计原理

RuoYi-Vue-Plus 采用插件化设计,其核心原理如下:

Java
/**
 * 插件接口
 */
public interface Plugin {
    /**
     * 插件名称
     */
    String getName();
    
    /**
     * 插件版本
     */
    String getVersion();
    
    /**
     * 插件描述
     */
    String getDescription();
    
    /**
     * 初始化插件
     */
    void initialize();
    
    /**
     * 启动插件
     */
    void start();
    
    /**
     * 停止插件
     */
    void stop();
}

插件化设计的核心是通过定义统一的插件接口,实现功能的动态加载和卸载。系统通过插件管理器负责插件的加载、初始化、启动和停止,实现功能的动态扩展。插件可以独立开发和部署,不影响系统的稳定性。

代码示例

配置示例

YAML
# 项目配置
ruoyi:
  # 名称
  name: RuoYi-Vue-Plus
  # 版本
  version: 5.3.0
  # 版权年份
  copyrightYear: 2023
  # 实例演示开关
  demoEnabled: true
  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
  profile: D:/ruoyi/uploadPath
  # 获取ip地址开关
  addressEnabled: false
  # 验证码类型 math 数组计算 char 字符验证
  captchaType: math

# 开发环境配置
server:
  # 服务器的HTTP端口,默认为8080
  port: 8080
  servlet:
    # 应用的访问路径
    context-path: /
  tomcat:
    # tomcat的URI编码
    uri-encoding: UTF-8
    # tomcat最大线程数,默认为200
    max-threads: 800
    # Tomcat启动初始化的线程数,默认值25
    min-spare-threads: 30

核心类示例

Java
/**
 * 登录服务实现
 */
@Service
public class SysLoginService {
    @Autowired
    private SysUserMapper userMapper;
    
    @Autowired
    private SysPermissionService permissionService;
    
    /**
     * 登录验证
     *
     * @param username 用户名
     * @param password 密码
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid) {
        // 验证码校验
        validateCaptcha(username, code, uuid);
        
        // 登录前置校验
        loginPreCheck(username, password);
        
        // 用户验证
        SysUser user = loadUserByUsername(username);
        
        if (!SecurityUtils.matchesPassword(password, user.getPassword())) {
            throw new UserPasswordNotMatchException();
        }
        
        if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            throw new UserBlockedException();
        }
        
        // 生成token
        return createToken(user);
    }
    
    /**
     * 创建令牌
     */
    public String createToken(SysUser user) {
        // 生成token
        String token = IdUtil.fastSimpleUUID();
        
        // 缓存用户信息
        LoginUser loginUser = new LoginUser();
        loginUser.setUserId(user.getUserId());
        loginUser.setUsername(user.getUserName());
        loginUser.setPermission(permissionService.getMenuPermission(user.getUserId()));
        
        // 缓存登录用户到Redis
        RedisUtils.setCacheObject(CacheConstants.LOGIN_TOKEN_KEY + token, loginUser, 
            CacheConstants.LOGIN_TOKEN_TTL, TimeUnit.MINUTES);
        
        return token;
    }
}

接口设计示例

Java
/**
 * 用户管理接口
 */
@RestController
@RequestMapping("/system/user")
public class SysUserController extends BaseController {
    
    @Autowired
    private ISysUserService userService;
    
    /**
     * 获取用户列表
     */
    @SaCheckPermission("system:user:list")
    @GetMapping("/list")
    public TableDataInfo<SysUserVo> list(SysUserBo user, PageQuery pageQuery) {
        return userService.selectPageUserList(user, pageQuery);
    }
    
    /**
     * 根据用户编号获取详细信息
     */
    @SaCheckPermission("system:user:query")
    @GetMapping("/{userId}")
    public R<SysUserVo> getInfo(@PathVariable Long userId) {
        userService.checkUserDataScope(userId);
        return R.ok(userService.selectUserById(userId));
    }
    
    /**
     * 新增用户
     */
    @SaCheckPermission("system:user:add")
    @Log(title = "用户管理", businessType = BusinessType.INSERT)
    @PostMapping
    public R<Void> add(@Validated @RequestBody SysUserBo user) {
        if (!userService.checkUserNameUnique(user.getUserName())) {
            return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
        }
        return toAjax(userService.insertUser(user));
    }
    
    /**
     * 修改用户
     */
    @SaCheckPermission("system:user:edit")
    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
    @PutMapping
    public R<Void> edit(@Validated @RequestBody SysUserBo user) {
        userService.checkUserAllowed(user);
        userService.checkUserDataScope(user.getUserId());
        if (!userService.checkUserNameUnique(user.getUserName())) {
            return R.fail("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
        }
        return toAjax(userService.updateUser(user));
    }
}

讨论回复

0 条回复

还没有人回复,快来发表你的看法吧!