介绍
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导入,顺带导入角色,在模板中给用户以提示。