mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2025-09-27 05:36:41 +08:00
22 KiB
22 KiB
RuoYi-Vue-Plus项目分析报告
1. 项目概述
RuoYi-Vue-Plus是在RuoYi-Vue基础上进行重构升级的开源项目,它专为分布式集群与多租户场景设计,采用插件化和扩展包的结构形式,极大提高了系统的解耦程度和扩展性。项目使用Spring Boot 3.4、JDK 17/21,基于Vue3+TS+ElementPlus重写前端,并使用Undertow替代Tomcat作为Web容器。
1.1 项目特点
- 采用插件化+扩展包形式,结构解耦,易于扩展
- 严格遵守Alibaba规范,代码格式统一
- 支持多种数据库(MySQL、Oracle、PostgreSQL、SQLServer等)
- 支持多租户架构
- 前端使用Vue3+TS+ElementPlus
- 底层基于Redisson的分布式能力(分布式锁、限流等)
- 使用Sa-Token+JWT实现权限认证
- 使用Mybatis-Plus作为ORM框架
- 内置丰富的功能模块和实用工具
2. 项目架构
2.1 整体架构
RuoYi-Vue-Plus采用前后端分离的架构模式:
- 后端:Spring Boot 3.4 + MyBatis-Plus + Sa-Token
- 前端:Vue3 + TypeScript + ElementPlus
2.2 目录结构
项目主要分为以下几个部分:
2.2.1 后端结构
ruoyi-vue-plus
├── ruoyi-admin -- 启动模块,项目入口
├── ruoyi-common -- 通用模块,各种功能组件
│ ├── ruoyi-common-bom -- 依赖版本管理
│ ├── ruoyi-common-core -- 核心功能
│ ├── ruoyi-common-doc -- 接口文档
│ ├── ruoyi-common-encrypt -- 数据加解密
│ ├── ruoyi-common-excel -- Excel处理
│ ├── ruoyi-common-idempotent -- 幂等处理
│ ├── ruoyi-common-job -- 定时任务
│ ├── ruoyi-common-json -- JSON处理
│ ├── ruoyi-common-log -- 日志处理
│ ├── ruoyi-common-mail -- 邮件处理
│ ├── ruoyi-common-mybatis -- ORM配置
│ ├── ruoyi-common-oss -- 对象存储
│ ├── ruoyi-common-ratelimiter -- 限流处理
│ ├── ruoyi-common-redis -- Redis配置
│ ├── ruoyi-common-satoken -- 认证鉴权
│ ├── ruoyi-common-security -- 安全配置
│ ├── ruoyi-common-sensitive -- 数据脱敏
│ ├── ruoyi-common-sms -- 短信服务
│ ├── ruoyi-common-social -- 社交登录
│ ├── ruoyi-common-sse -- SSE推送
│ ├── ruoyi-common-tenant -- 多租户
│ ├── ruoyi-common-translation -- 数据翻译
│ ├── ruoyi-common-web -- Web功能
│ └── ruoyi-common-websocket -- WebSocket
├── ruoyi-extend -- 扩展模块
│ ├── ruoyi-monitor-admin -- 监控管理
│ └── ruoyi-snailjob-server -- 任务调度
├── ruoyi-modules -- 业务模块
│ ├── ruoyi-demo -- 示例模块
│ ├── ruoyi-generator -- 代码生成
│ ├── ruoyi-job -- 定时任务
│ ├── ruoyi-system -- 系统管理
│ └── ruoyi-workflow -- 工作流
└── ruoyi-plus-soybean -- 前端项目
2.2.2 前端结构
ruoyi-plus-soybean
├── docs -- 文档
├── packages -- 功能包
├── public -- 静态资源
├── src -- 源代码目录
│ ├── assets -- 静态资源
│ ├── components -- 组件
│ ├── constants -- 常量
│ ├── enum -- 枚举
│ ├── hooks -- 钩子函数
│ ├── layouts -- 布局
│ ├── locales -- 国际化
│ ├── plugins -- 插件
│ ├── router -- 路由
│ ├── service -- 服务调用
│ ├── store -- 状态管理
│ ├── styles -- 样式
│ ├── theme -- 主题
│ ├── typings -- 类型定义
│ ├── utils -- 工具类
│ ├── views -- 视图
│ ├── App.vue -- 主组件
│ └── main.ts -- 入口文件
3. 功能模块
3.1 核心功能
基础功能
- 多租户管理:支持租户套餐、过期时间、用户数量等管理
- 用户权限:用户、角色、部门、菜单权限管理
- 系统监控:在线用户、操作日志、登录日志
- 系统管理:参数设置、字典管理、附件管理
- 系统工具:代码生成、表单设计、接口文档
扩展功能
- 工作流:支持复杂审批流程
- 在线构建器:拖拽式表单生成
- 定时任务:任务调度管理
- 系统接口:API文档自动生成
- 服务监控:监控系统资源和性能
- 缓存监控:Redis监控
3.2 技术特性
- 数据权限:基于Mybatis-Plus插件的无感式数据权限过滤
- 数据脱敏:支持注解+Jackson方式的数据脱敏
- 数据加解密:支持数据库字段级加解密
- 接口加密:动态AES+RSA加密请求体
- 数据翻译:注解+序列化期间自动翻译
- 多数据源:支持动态数据源配置和切换
- 分布式锁:基于Redisson的分布式锁
- 分布式任务调度:基于SnailJob的分布式任务调度
- 文件存储:支持MinIO和S3协议的对象存储
4. 代码规范
RuoYi-Vue-Plus项目严格遵循以下规范:
4.1 项目规范
- 严格遵守Alibaba编码规范
- 使用统一的代码格式化配置
- 采用插件化结构,功能模块独立封装
- 按职责分离不同类型的代码(控制器、服务、实体等)
4.2 编码规范
后端规范
-
命名规范:
- 类名:大驼峰命名(如:UserController)
- 方法名/变量名:小驼峰命名(如:getUserInfo)
- 常量:全大写下划线分隔(如:MAX_COUNT)
-
包结构:
- controller:控制器
- service:服务层
- mapper:数据访问层
- domain:实体类(entity、vo、bo、dto等)
- util:工具类
-
注释要求:
- 类注释:说明类的用途
- 方法注释:说明方法功能、参数和返回值
- 关键代码注释:解释复杂逻辑
前端规范
-
命名规范:
- 组件名:大驼峰命名(如:UserForm)
- 文件名:kebab-case(如:user-form.vue)
- 变量/方法:小驼峰命名(如:getUserInfo)
-
目录结构:
- components:组件
- views:页面
- api:接口定义
- utils:工具函数
- store:状态管理
-
编码风格:
- 使用TypeScript进行类型检查
- 组件使用组合式API (Composition API)
- 样式使用SCSS并遵循BEM规范
4.3 常见代码示例
4.3.1 控制器规范示例
/**
* 用户信息控制器
*
* @author ruoyi-vue-plus
*/
@RestController
@RequestMapping("/system/user")
public class SysUserController extends BaseController {
@Autowired
private ISysUserService userService;
/**
* 获取用户列表
*
* @param user 查询参数
* @return 用户列表
*/
@SaCheckPermission("system:user:list")
@GetMapping("/list")
public TableDataInfo<SysUserVo> list(SysUserBo user) {
startPage();
List<SysUserVo> list = userService.selectUserList(user);
return getDataTable(list);
}
/**
* 新增用户
*
* @param user 用户信息
* @return 结果
*/
@SaCheckPermission("system:user:add")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Validated @RequestBody SysUserBo user) {
return toAjax(userService.insertUser(user));
}
}
4.3.2 服务接口和实现规范示例
/**
* 用户服务接口
*
* @author ruoyi-vue-plus
*/
public interface ISysUserService {
/**
* 查询用户列表
*
* @param user 查询参数
* @return 用户列表
*/
List<SysUserVo> selectUserList(SysUserBo user);
/**
* 新增用户
*
* @param user 用户信息
* @return 结果
*/
int insertUser(SysUserBo user);
}
/**
* 用户服务接口实现
*
* @author ruoyi-vue-plus
*/
@Service
public class SysUserServiceImpl implements ISysUserService {
@Autowired
private SysUserMapper userMapper;
/**
* 查询用户列表
*
* @param user 查询参数
* @return 用户列表
*/
@Override
public List<SysUserVo> selectUserList(SysUserBo user) {
return userMapper.selectUserList(user);
}
/**
* 新增用户
*
* @param user 用户信息
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int insertUser(SysUserBo user) {
// 业务逻辑实现
return userMapper.insert(user.toEntity());
}
}
4.3.3 实体类规范示例
/**
* 用户实体类
*
* @author ruoyi-vue-plus
*/
@Data
@TableName("sys_user")
@KeySequence("sys_user_seq")
public class SysUser extends TenantEntity {
/**
* 用户ID
*/
@TableId(value = "user_id", type = IdType.ASSIGN_ID)
private Long userId;
/**
* 用户账号
*/
private String userName;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户类型(sys_user系统用户)
*/
private String userType;
/**
* 手机号码
*/
@Sensitive(strategy = SensitiveStrategy.PHONE)
private String phonenumber;
/**
* 帐号状态(0正常 1停用)
*/
private String status;
}
4.3.4 前端页面示例 (Vue3 + TS)
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form :model="queryParams" ref="queryFormRef" :inline="true">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作按钮区域 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
v-hasPermi="['system:user:add']"
@click="handleAdd"
>新增</el-button>
</el-col>
</el-row>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="userList">
<el-table-column label="用户编号" prop="userId" />
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column label="手机号码" prop="phonenumber" :show-overflow-tooltip="true" />
<el-table-column label="状态" prop="status">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button
type="text"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:user:edit']"
>修改</el-button>
<el-button
type="text"
@click="handleDelete(scope.row)"
v-hasPermi="['system:user:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { listUser, delUser } from '@/api/system/user';
// 定义数据
const loading = ref(false);
const total = ref(0);
const userList = ref([]);
const queryFormRef = ref<any>(null);
// 查询参数
const queryParams = ref({
pageNum: 1,
pageSize: 10,
userName: undefined,
phonenumber: undefined,
});
/** 查询用户列表 */
function getList() {
loading.value = true;
listUser(queryParams.value).then(response => {
userList.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
// 实现新增逻辑
}
/** 修改按钮操作 */
function handleUpdate(row) {
// 实现修改逻辑
}
/** 删除按钮操作 */
function handleDelete(row) {
// 实现删除逻辑
}
onMounted(() => {
getList();
});
</script>
5. 二次开发指南
5.1 开发环境准备
-
基础环境:
- JDK 17/21
- Maven 3.8+
- Node.js 16+
- MySQL 8.0+/Oracle/PostgreSQL/SQLServer
- Redis 5.0+
-
IDE推荐:
- IntelliJ IDEA(后端)
- VSCode(前端)
5.2 开发规范与原则
-
遵循现有架构:
- 保持与现有代码风格一致
- 保持模块的独立性和可插拔性
- 不随意修改核心模块代码
-
扩展而非修改:
- 通过扩展现有组件实现功能
- 避免直接修改框架核心代码
-
注重代码质量:
- 编写单元测试
- 遵循代码规范
- 注释完善
5.3 增加PMS模块实施步骤
以下是添加一个产品管理系统(PMS)模块的具体步骤:
5.3.1 后端开发
-
创建模块结构:
ruoyi-modules └── ruoyi-pms -- PMS模块 ├── src/main/java/org/dromara/pms │ ├── controller -- 控制器 │ ├── domain -- 实体类 │ │ ├── bo -- 业务对象 │ │ ├── entity -- 数据库实体 │ │ └── vo -- 视图对象 │ ├── mapper -- MyBatis接口 │ └── service -- 服务实现 └── src/main/resources ├── mapper -- MyBatis XML └── i18n -- 国际化资源
-
配置模块POM:
- 创建pom.xml,添加必要依赖
- 在父模块中添加新模块引用
-
创建数据库表:
- 设计表结构
- 编写SQL脚本
-
开发核心功能:
- 使用代码生成器生成基础CRUD代码
- 扩展实现具体业务逻辑
- 添加权限控制
- 实现多租户和数据权限
- 添加接口文档注释
5.3.2 前端开发
-
创建模块目录:
ruoyi-plus-soybean/src/views/pms ├── product -- 产品管理 ├── category -- 分类管理 └── inventory -- 库存管理
-
API接口定义:
ruoyi-plus-soybean/src/service/api/pms ├── product.ts -- 产品API ├── category.ts -- 分类API └── inventory.ts -- 库存API
-
配置路由:
- 在router/routes目录下创建pms.ts
- 在router/index.ts中导入并注册路由
-
开发页面组件:
- 列表页面
- 表单页面
- 详情页面
-
添加权限控制:
- 配置菜单与按钮权限
- 实现页面级与按钮级权限控制
5.3.3 集成与测试
-
数据库脚本集成:
- 将建表SQL添加到初始化脚本中
-
菜单配置:
- 通过系统管理-菜单管理添加PMS模块菜单
- 配置菜单权限
-
角色授权:
- 为相关角色分配PMS模块权限
-
单元测试:
- 编写API测试
- 编写服务层测试
-
集成测试:
- 测试完整业务流程
- 测试与其他模块的交互
5.4 使用框架核心功能的最佳实践
5.4.1 使用多租户
- 实体类继承TenantEntity:
@Data
@TableName("pms_product")
public class PmsProduct extends TenantEntity {
// 实体字段定义
}
- 多租户过滤配置:
@Configuration
public class TenantConfig {
/**
* 配置需要进行多租户过滤的表
*/
@Bean
public TenantLineHandler tenantLineHandler() {
return new PlusTenantLineHandler() {
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean ignoreTable(String tableName) {
// 配置不需要过滤的表
return ArrayUtil.contains(IGNORE_TENANT_TABLES, tableName);
}
};
}
}
5.4.2 使用数据权限
- 在Mapper接口上使用注解:
@DataPermission({
@DataColumn(key = "deptName", value = "d.dept_id"),
@DataColumn(key = "userName", value = "u.user_id")
})
public interface SysUserMapper extends BaseMapperPlus<SysUserMapper, SysUser, SysUserVo> {
// 方法定义
}
- 在查询方法上使用注解:
@Override
@DataScope(userAlias = "u", deptAlias = "d")
public List<SysUserVo> selectUserList(SysUserBo user) {
return baseMapper.selectUserList(user);
}
5.4.3 使用数据脱敏
public class UserVo {
// 手机号码脱敏
@Sensitive(strategy = SensitiveStrategy.PHONE)
private String phonenumber;
// 邮箱脱敏
@Sensitive(strategy = SensitiveStrategy.EMAIL)
private String email;
// 身份证脱敏
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
private String idCard;
}
5.4.4 使用Excel导入导出
@Data
@ExcelIgnoreUnannotated
public class UserImportVo {
@ExcelProperty(value = "用户编号")
private Long userId;
@ExcelProperty(value = "用户名称")
@ExcelRequired
private String userName;
@ExcelProperty(value = "用户昵称")
@ExcelRequired
private String nickName;
@ExcelProperty(value = "手机号码")
@ExcelRequired
private String phonenumber;
@ExcelProperty(value = "邮箱")
private String email;
}
@RestController
public class UserController {
/**
* 导出用户
*/
@SaCheckPermission("system:user:export")
@Log(title = "用户管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SysUserBo user) {
List<SysUserVo> list = userService.selectUserList(user);
ExcelUtil.exportExcel(list, "用户数据", SysUserVo.class, response);
}
/**
* 导入用户
*/
@SaCheckPermission("system:user:import")
@Log(title = "用户管理", businessType = BusinessType.IMPORT)
@PostMapping("/importData")
public R<Void> importData(@RequestPart("file") MultipartFile file, boolean updateSupport) throws Exception {
ExcelResult<UserImportVo> result = ExcelUtil.importExcel(file, UserImportVo.class);
userService.importUser(result.getList(), updateSupport);
return R.ok();
}
}
5.4.5 使用接口幂等性控制
@RestController
@RequestMapping("/system/user")
public class SysUserController {
/**
* 新增用户
*/
@SaCheckPermission("system:user:add")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@RepeatSubmit(interval = 5000) // 5秒内不允许重复提交
@PostMapping
public R<Void> add(@Validated @RequestBody SysUserBo user) {
return toAjax(userService.insertUser(user));
}
}
5.4.6 使用Redis缓存
@Service
public class SysConfigServiceImpl implements ISysConfigService {
/**
* 查询参数配置信息
*
* @param configId 参数配置ID
* @return 参数配置信息
*/
@Override
@Cacheable(cacheNames = CacheNames.SYS_CONFIG, key = "#configId")
public SysConfig selectConfigById(Long configId) {
return baseMapper.selectById(configId);
}
/**
* 新增参数配置
*
* @param config 参数配置信息
* @return 结果
*/
@Override
@CachePut(cacheNames = CacheNames.SYS_CONFIG, key = "#config.configId")
public int insertConfig(SysConfig config) {
return baseMapper.insert(config);
}
/**
* 删除参数配置
*
* @param configId 参数ID
* @return 结果
*/
@Override
@CacheEvict(cacheNames = CacheNames.SYS_CONFIG, key = "#configId")
public int deleteConfigById(Long configId) {
return baseMapper.deleteById(configId);
}
}
6. 总结
RuoYi-Vue-Plus是一个功能完善、架构清晰的企业级应用开发框架,其插件化设计和模块化结构使其非常适合二次开发。在进行二次开发时,应当遵循项目的架构设计和编码规范,通过扩展而非修改的方式实现业务需求,确保系统的可维护性和可扩展性。
通过合理利用项目提供的代码生成、多租户、权限控制等特性,可以大幅提高开发效率,专注于业务逻辑的实现,而非底层架构的搭建。
7. 常见问题与解决方案
7.1 多租户问题
问题:如何对特定表或操作排除多租户过滤?
解决方案:
- 全局排除:在TenantConfig中的ignoreTable方法中添加表名
- 局部排除:使用@TenantIgnore注解标注在方法或类上
@TenantIgnore
public List<SysTenant> selectTenantList(SysTenant tenant) {
return baseMapper.selectList(buildQueryWrapper(tenant));
}
7.2 权限问题
问题:如何实现细粒度的数据权限控制?
解决方案:
- 使用@DataScope注解并配置用户、部门别名
- 在Mapper层使用@DataPermission定义权限字段
- 在XML中引用权限过滤片段
7.3 分布式事务问题
问题:如何处理跨服务的分布式事务?
解决方案:
- 对于强一致性需求,使用Seata进行分布式事务管理
- 对于最终一致性需求,考虑使用本地消息表+定时任务或消息队列实现
- 避免长事务,将业务拆分成多个小事务
7.4 性能优化问题
问题:系统运行缓慢,如何优化?
解决方案:
- 使用Redis缓存热点数据,减少数据库访问
- 优化SQL查询,避免全表扫描
- 使用分页查询代替全量查询
- 合理使用索引
- 利用多级缓存(本地缓存+Redis缓存)
- 考虑使用读写分离或分库分表