(该文章整理于网络,仅用于学习记录,如有侵权,请联系删除)
介绍
RuoYi-Vue 是一个 Java EE 企业级快速开发平台,基于经典技术组合(Spring Boot、Spring Security、MyBatis、Jwt、Vue),内置模块如:部门管理、角色用户、菜单及按钮授权、数据权限、系统参数、日志管理、代码生成等。在线定时任务配置;支持集群,支持多数据源,支持分布式事务。比较好用。
快速了解
可参考官网。
环境部署
部署Java:多模块springboot ,通过宝塔创建springboot项目,再通过nginx反向代理即可。
部署Vue :打包部署。
war部署方式ruoyi/pom.xml
中的packaging
修改为war
,放入tomcat
服务器webapps
SpringBoot
去除内嵌Tomcat
(PS:此步骤不重要,因为不排除也能在容器中部署war
)
项目介绍
1. 注解:需要通过反射来进行解析,参考excle注解。如果没解析器就没有任何意义。
2. ruoyi配置,通过该类读取yml配置文件:(
@Component
@ConfigurationProperties(prefix = "ruoyi")
3. constant常量
4. core: 基本的control 封装公共。
5.公共的实体:如loginbody.
6.封装的page.
7. redis操作的方法。
bbbub{},“aaa”====》
debug讲解
登陆讲解:
获取验证码,获取配置默认的验证码类型。
验证码存放到redis ,并且返回一个uuuid到前台用于验证(有时效),如果要使用,可以临时去取。
登陆逻辑梳理:
public void validateCaptcha(String username, String code, String uuid)
{
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String captcha = redisCache.getCacheObject(verifyKey);
redisCache.deleteObject(verifyKey);
if (captcha == null)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
throw new CaptchaException();
}
}
登陆debug:
注意是封装的第三方组件。
模拟登陆若依
通过API POST 或者 IDEA 的HTTP客户端发起请求,获取图片后 ,解析到答案。
或者直接根据UUID去redis查询到结果:
-
获取验证码,得到UUID
-
向后端发送login请求,带上username,password,uuid,code(验证码)
-
登录成功 获取TOKEN token解密后,并缓存到redis.
4. 发起其他请求,需要带上token进行认证。(两种均可)
后台手册
复习反射注解
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
内置的注解
Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。
作用在代码的注解是
-
@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
-
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
-
@SuppressWarnings - 指示编译器去忽略注解中声明的警告。
作用在其他注解的注解(或者说 元注解)是:
-
@Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
-
@Documented - 标记这些注解是否包含在用户文档中。
-
@Target - 标记这个注解应该是哪种 Java 成员。
-
@Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
从 Java 7 开始,额外添加了 3 个注解:
-
@SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
-
@FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
-
@Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
获取并设置字段
获取注解,比如或用户名称上的注解信息:(注意soutv的使用)
通过反射获取构造函数并实例化,获取成员方法并调用:如下:
导入导出
-
导入
-
导出
分页
-
前端实现
// 一般在查询参数中定义分页变量 queryParams: { pageNum: 1, pageSize: 10 }, // 页面添加分页组件,传入分页变量 <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" /> // 调用后台方法,传入参数 获取结果 listUser(this.queryParams).then(response => { this.userList = response.rows; this.total = response.total; } );
-
后端实现
@PostMapping("/list") @ResponseBody public TableDataInfo list(User user) { startPage(); // 此方法配合前端完成自动分页 List<User> list = userService.selectUserList(user); return getDataTable(list); }
上传下载
-
上传
drop table if exists sys_file_info; create table sys_file_info ( file_id int(11) not null auto_increment comment '文件id', file_name varchar(50) default '' comment '文件名称', file_path varchar(255) default '' comment '文件路径', primary key (file_id) ) engine=innodb auto_increment=1 default charset=utf8 comment = '文件信息表';
<el-upload ref="upload" :limit="1" accept=".jpg, .png" :action="upload.url" :headers="upload.headers" :file-list="upload.fileList" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false"> <el-button slot="trigger" size="small" type="primary">选取文件</el-button> <el-button style="margin-left: 10px;" size="small" type="success" :loading="upload.isUploading" @click="submitUpload">上传到服务器</el-button> <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div> </el-upload>
import { getToken } from "@/utils/auth";
// 上传参数 upload: { // 是否禁用上传 isUploading: false, // 设置上传的请求头部 headers: { Authorization: "Bearer " + getToken() }, // 上传的地址 url: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的文件列表 fileList: [] },
handleAdd() { ... this.upload.fileList = []; } handleUpdate(row) { ... this.upload.fileList = [{ name: this.form.fileName, url: this.form.filePath }]; }
// 文件提交处理 submitUpload() { this.$refs.upload.submit(); }, // 文件上传中处理 handleFileUploadProgress(event, file, fileList) { this.upload.isUploading = true; }, // 文件上传成功处理 handleFileSuccess(response, file, fileList) { this.upload.isUploading = false; this.form.filePath = response.url; this.msgSuccess(response.msg); }
-
下载
<template> <div class="app-container"> <el-row :gutter="20"> <el-col xl="24" md="24" lg="24" sm="24"> <el-button size="mini" type="text" icon="el-icon-edit" @click="handleDownload" >下载 </el-button> </el-col> </el-row> </div> </template> <script> export default { name: "index", methods: { // 文件下载处理 handleDownload() { var name = "/profile/upload/2021/01/18/4d6b8f1f-5b10-4e76-b5b4-675bd90856b6.jpg"; var url = "http://localhost:8080/profile/upload/2021/01/18/4d6b8f1f-5b10-4e76-b5b4-675bd90856b6.jpg"; var suffix = url.substring(url.lastIndexOf("."), url.length); const a = document.createElement('a') a.setAttribute('download', name + suffix) a.setAttribute('target', '_blank') a.setAttribute('href', url) a.click() } } } </script> <style scoped> </style>
权限控制
-
授之以渔。
-
菜单-角色-用户-权限:
1、xxx:xxx:list意思是能不能进入这个菜单进行对表增删改查,如果没有这个权限,前端不能进入菜单。 2、xxx:xxx:add,remove,edit,query,意思是对前端的增删改查实现,往往会在前端的组件多加一个指令v-hasPermi(若依封装),如果没有这个权限,则组件将不会显示。 3、后端执行某个controller若需要某个权限才能执行,则需要加上注解@PreAuthorize("@ss.hasPermi('system:user:list')") 这个注解是若依+SpringSecurity框架共同实现的,它会先去执行方法ss.hasPermi('system:user:list')来判断是否能去执行相关的controller。 4、在登录的时候,每次点击全局页面刷新的时候,都会执行getInfo方法去获取权限,这个权限会被vuex进行管理,从而方便在每一个界面判断是否展示某个组件/按钮。
-
数据权限 ---留到后面再讲
-
所谓数据权限,就是某个用户能查询哪些数据,来看一下若依给我们的解释:
在实际开发中,需要设置用户只能查看哪些部门的数据,这种情况一般称为数据权限。 例如对于销售,财务的数据,它们是非常敏感的,因此要求对数据权限进行控制, 对于基于集团性的应用系统而言,就更多需要控制好各自公司的数据了。如设置只能看本公司、或者本部门的数据,对于特殊的领导,可能需要跨部门的数据, 因此程序不能硬编码那个领导该访问哪些数据,需要进行后台的权限和数据权限的控制。
-
事务管理
同SpringBoot,主要注解:@Transactional
异常处理
全局异常处理。通过一个全局异常处理类来进行处理GlobalExceptionHandler,这里面有两个重要的注解叫:@RestControllerAdvice,@ExceptionHandler(BaseException.class),用AOP的方法去增强方法,若发生异常将由ExceptionHandler注解的方法去处理这个异常,一般都是给前端返回一个错误的信息。这里BaseException.class就是指定某个异常,当这个异常发生,就会执行相关方法,例如:
/** * 基础异常 */ @ExceptionHandler(BaseException.class) public AjaxResult baseException(BaseException e) { return AjaxResult.error(e.getMessage()); }
这个方法就会去处理发生了BaseException的控制器。
系统日志
-
登录日志(需要线程池知识)
-
登录日志带大家debug,在com/ruoyi/framework/web/service/SysLoginService.java这个类中的login方法中能找到相关的日志打印信息,例如:
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
-
-
控制器操作日志
-
AOP切面实现逻辑-debug。
-
案例-自定义自己的操作日志。
-
数据权限
在实际开发中,需要设置用户只能查看哪些部门的数据,这种情况一般称为数据权限。 例如对于销售,财务的数据,它们是非常敏感的,因此要求对数据权限进行控制, 对于基于集团性的应用系统而言,就更多需要控制好各自公司的数据了。如设置只能看本公司、或者本部门的数据,对于特殊的领导,可能需要跨部门的数据, 因此程序不能硬编码那个领导该访问哪些数据,需要进行后台的权限和数据权限的控制。
针对角色设置的-去角色管理进行设置每一个角色的数据权限。
这个数据权限是AOP来实现的,主要注解是@Before(前置切入),通过对维护的param属性对应的HashMap中dataScope健存入一段SQL,这段SQL最终将加在查询语句的后边,mybatis中引用:${param.dataScope}。如何拼装的SQL在切面类(DataScopeAspect)中debug可以看到。
多数据源
前置知识 -> 如何实现多数据源?,实现多数据源的ThreadLocal类作用是什么呢?
主要通过继承AbstractRoutingDataSource类来实现。
ThreadLocal是存储线程独立局部变量用的,多个线程之间互不干扰。
前端手册
组件文档
项目扩展
视频教程
更新日志
其它
公共基础:
获取当前用户逻辑
-
前端获取方法:var userName = this.$store.state.user.name; 那么 什么时候这里面有值的呢?
-
后端获取方法
-
方法一:工具类获取 String userName = SecurityUtils.getUsername();
-
@Autowired private TokenService tokenService; LoginUser loginUser = tokenService.getLoginUser(); // 获取当前的用户名称 String userName = loginUser.getUsername();
-
前端获取路由逻辑
Excel导入逻辑 * 讲过
Excel导出逻辑 不讲
登录逻辑 * 讲过
鉴权(权限)逻辑 -- 讲了
sys_config表是个什么玩意?
对应前端参数设置的值。
redis存的一些值是什么玩意?怎么项目一启动就有值的?
值来自sys-config表
作业:
Excel导入,顺带导入角色,在模板中给用户以提示。