RuoYi-Vue 后端笔记

公司要求了解一下,于是就去了解一下,本以为能提升一些生产力,结果一看也不过如此。


使用 RuoYi-Vue,即 RuoYi 的单体,前后端分离的版本,

记录一些重要概念,以及完成一个业务模块的后端,包括打包发布,尽量使用代码生成。注意 RuoYi-Vue 的文档是 http://doc.ruoyi.vip/ruoyi-vue/,其中一些章节引用 http://doc.ruoyi.vip/ruoyi/,后者未被引用的部分不应参照。FAQ 部分也值得阅读,其讲述了一些实际问题的处理方案,如跨域,不登录查看,token 过期时间等。

开发环境部署

  1. 安装 node,jdk,启动 mysql,redis
  2. 执行 git clone https://gitee.com/y_project/RuoYi-Vue
  3. 使用 IDE 打开项目,编辑 ruoyi-admin/src/main/resources/application.yml配置 redis,编辑同目录下 application-druid.yml 配置 mysql 数据源
  4. 启动后端:执行 ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
  5. 访问 localhost:8080,检查有正确响应
  6. 进入 ruoyi-ui,执行 npm i 安装依赖
  7. 执行 npm run dev,启动前端开发服务器
  8. 访问 localhost:80,检查页面正常显示

权限系统

概念:用户,角色,部门,岗位,接口,菜单权限,数据权限。

岗位独立存在,不和其它概念关联(甚至不和部门关联),似乎只有标识作用。

每个用户属于且只属于单个部门,用户可以有多个岗位和多个角色。

角色包含权限(页面中称为菜单权限,后端称为 Permi)和数据权限(后端称为 DataScope)信息,前者标识是否有权看到菜单(和调用接口),后者标识能看到表中的哪些范围的数据(即行权限)

角色的数据权限可以和用户的部门相关,如只能看本部门数据,也可指定能够查看特定部门数据。

菜单和菜单权限是绑定的,每个菜单都使用特定权限标识。后端实现中,可以通过权限和角色来标识用户是否有权调用该接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// 标识在接口上,PreAuthorize 是 Spring Security 的注解
// 属于 user 角色
@PreAuthorize("@ss.hasRole('user')")
// 符合 system:user:list 权限要求
@PreAuthorize("@ss.hasPermi('system:user:list')")
// 符合 system:user:add 或 system:user:edit 权限要求即可
@PreAuthorize("@ss.hasAnyPermi('system:user:add,system:user:edit')")
// 属于 user 或者 admin 之一
@PreAuthorize("@ss.hasAnyRoles('user,admin')")
// 公开访问
@Anonymous

/// 编程方式校验
if (SecurityUtils.hasPermi("sys:user:edit")) {
System.out.println("当前用户有编辑用户权限");
}
if (SecurityUtils.hasRole("admin")) {
System.out.println("当前用户有 admin 角色权限");
}

前后端的鉴权是分开的(实际上它们根本没有被关联上吧?甚至拼写错误都无法避免),菜单需要对应特定权限不代表后端不需要做校验。

数据权限

重申:每个角色都有自己的数据权限,任一角色的数据权限满足约束时用户就能看到该数据。数据权限需要使用@DataScope注解,其定义上有一些问题,注释语焉不详,参照源码大概是下面的语义:

参数名 是否必填 示例 描述
deptAlias Y d 必填,使 SQL 中插入按部门的数据权限校验,SQL 中会查询{deptAlias}.dept_id作为部门字段,检查该字段的值满足约束(如当前部门,当前部门及其子部门等)
userAlias N u 选填,如果填写,则使 SQL 中插入按个人的数据权限校验,SQL 中会查询{userAlias}.user_id作为用户字段,检查其等于当前用户的 ID
permission N system:user:add,system:user:edit 根据权限限定要生成数据域 SQL 的角色 ,只选择包含这些权限中任一的角色去生成 SQL,默认走@PreAuthorize中指定的权限

一些细节如下:

  1. 管理员拥有所有所有数据权限
  2. 如果用户有多个角色,(筛选后的,见第 5 条)每个角色都会根据其的数据权限生成对应的 SQL 并OR 连接
  3. userAlias 只在用户的角色的数据权限设定为仅本人且 userAlias 被填写时才被使用,未填写 userAlias 时,仅本人的约束不起作用
  4. 如果方法上有多个参数,需要第一个参数为BaseEntity类型及其子类,生成后的 sql 会赋给其的 paramsdataScope
  5. permission 字段根据权限去限定用户的参与数据域检查的角色,如果 permission 未填写,则默认走@PreAuthorize("@ss.hasAnyPermi(..)")@PreAuthorize("@ss.hasPermi(..)")中指定的权限(底层是在 ss 的两个方法调用中把参数设定到上下文中,在 DataScope 的逻辑中取它)。如果PreAuthorize中检查的是用户的角色而非权限,那就不做角色上的限定;相关代码见 com.ruoyi.framework.aspectj.DataScopeAspect#dataScopeFilter 的 75,103 行和 com.ruoyi.framework.web.service.PermissionService#hasPermi 的 38 行

在方法上定义注解后,还是需要手动在 XML 中引用其生成的 SQL 代码,详细见 http://doc.ruoyi.vip/ruoyi/document/htsc.html#数据权限。下面是一个完整例子,其中方法上的注解为@DataScope(deptAlias = "d"),它使用左连接把部门表引入进来并重命名为d来用来筛选,也有骚操作是直接把当前的表重命名为 d,使它引用当前表的dept_id字段;注意这里虽然没有使用到sys_user但还是把它引入进来了,因为需要通过用户才能找到用户的部门:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<sql id="selectRoleVo">
select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly,
r.status, r.del_flag, r.create_time, r.remark
from sys_role r
left join sys_user_role ur on ur.role_id = r.role_id
left join sys_user u on u.user_id = ur.user_id
left join sys_dept d on u.dept_id = d.dept_id
</sql>

<select id="selectRoleList" parameterType="SysRole" resultMap="SysRoleResult">
<include refid="selectRoleVo"/>
where r.del_flag = '0'
<if test="roleName != null and roleName != ''">
AND r.role_name like concat('%', #{roleName}, '%')
</if>
<if test="status != null and status != ''">
AND r.status = #{status}
</if>
<!-- 数据范围过滤 -->
${params.dataScope}
order by r.role_sort
</select>

代码生成

参考 https://blog.csdn.net/m0_70208154/article/details/127471641

RuoYi 支持生成从 XML 到前端 vue 的一系列代码,支持单表、树表、主子表(一对多关系)的生成,其中主子表只能有一种子表。以及多对多关系是不支持的。使用文件上传控件,接受文件 ID 这样的需求同样不被支持。在代码生成且进行过编辑后,再想要迭代的话就无法再直接利用上代码生成了。我不如用 Mybatis Plus 反而更舒服。

代码生成的步骤是:

  1. 若有需要的话,在 ruoyi-generator/src/main/resources/generator.yml 中配置表前缀
  2. 如果还未创建业务代码项目,建立代码所在 Maven 项目,该项目parent指定为ruoyi并作为ruoyi-admin的依赖,该项目应当依赖ruoyi-common
  3. 建字典,建表,点击 系统工具-代码生成-导入 导入所生成的表,并点击编辑按需修改
  4. 点击生成代码(主子表只需生成主表的),下载 ZIP,执行其中的菜单 SQL,拷贝 main 文件夹的内容到后端项目的src/main中,拷贝 vue 文件夹的内容到ruoyi-ui/src
  5. 重启后端(前端能热更新)

代码生成使用 apache velocity 作为模板语言,可以进行自定义,路径为 ruoyi-generator/src/main/resources/vm

RuoYi 生成的 Entity 会继承com.ruoyi.common.core.domain.BaseEntity,该 Entity 中包含创建、更新时间等字段,若表中包含(下划线分割的)同名字段则不会在子类上定义相应字段,这些字段分别为:

  • create_by
  • create_time
  • update_by
  • update_time
  • remark

考虑建表时主动定义这些字段以避免后续使用时混淆。

代码生成表配置

导入后,点击编辑去配置表,其包含三个界面,各界面的一些可能不清楚的点如下:

字段信息中,编辑这一列似乎没有任何影响。此外对于主键以及 createTime,createBy,updateTime,updateBy 四个字段,添加和编辑这两列是否选中都不会让它被显示出来

此外,生成主表时,关于子表信息,似乎是只有选中了“列表”才会显示在主表实体的添加、编辑页面中……这是 bug 吗?

待办清单示例

整个待办清单作为示例,有如下需求:

  1. 有两种角色——employee 和 employer,前者只能看到自己的待办清单,后者看到本部门的待办清单,但只能操作自己的待办
  2. 待办包含类型(走字典),内容,是否完成,附件,创建时间等信息
  3. 待办可以记进度,一条待办可以记录多个进度,查看待办详情时应当也能看到进度列表
  4. 能查询是否完成,的待办,能按创建时间区间做查询,列表按截止时间升序排列

预计一级模块名为 work,二级模块名为 todolist,业务名为待办清单,包名为com.ruoyi.work.todolist(如果不以com.ruoyi起头的话就需要编辑 Bean 扫描的包了,或者把所有项目的名字全都改掉)。

RuoYi 没有提供文件管理功能,但提供了相应接口去做上传下载,接口见 <com.ruoyi.web.controller.common.CommonController>,前端见 http://doc.ruoyi.vip/ruoyi-vue/document/htsc.html#上传下载

建立 work 模块

新建子模块的流程见 http://doc.ruoyi.vip/ruoyi/document/htsc.html#新建子模块

  1. 建立 maven 项目置于 ruoyi-admin 同级,指定父项目为 ruoyigroupIdcom.ruoyiartifactIdwork

此时ruoyi/pom.xml中的 modules 部分应当也引用 work

  1. 编辑ruoyi/pom.xml,指定 work 的版本:
1
2
3
4
5
6
7
8
9
10
<dependencyManagement>
<dependencies>
<!-- ...... -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>work</artifactId>
<version>${ruoyi.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
  1. work 添加 ruoyi-common 依赖,其中包含 spring 和 ruoyi 相关依赖:
1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
</dependency>
</dependencies>
  1. ruoyi-admin 依赖 work
1
2
3
4
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>work</artifactId>
</dependency>

定义字典

  1. 定义字典类型

  1. 定义字典项

定义表

这里的需求是主子表,主表是待办,子表是待办进度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
create table todo
(
id int auto_increment comment '主键',
todo_type smallint not null comment '待办类型',
todo_content longtext null comment '待办内容',
attachs varchar(256) null comment '待办附件',
is_done char(1) default 0 comment '已完成',
create_time datetime null comment '创建时间',
create_by varchar(32) null comment '创建者',
update_time datetime null comment '更新时间',
update_by varchar(32) null comment '更新者',
remark varchar(512) null comment '备注',
constraint todo_pk
primary key (id)
)
comment '待办表';

create table todo_progress
(
id int auto_increment comment '主键',
todo_id int not null comment '待办 ID',
progress_content longtext null comment '进度内容',
create_time datetime null comment '创建时间',
create_by varchar(32) null comment '创建者',
update_time datetime null comment '更新时间',
update_by varchar(32) null comment '更新者',
remark varchar(512) null comment '备注',
constraint todo_progress_pk
primary key (id)
)
comment '待办进度表';

代码生成

进入系统工具-代码生成,点击导入,导入两表:

根据需求编辑主表信息:

编辑进度表信息:

进度表只选中进度内容的“列表”(见上面关于代码生成的说明)。进度表的生成信息不需要编辑,因为不需要生成它,只需要生成主表信息。

点击主表的生成代码按钮,下载代码,复制到相应位置。

后端修改

默认只自动维护了 createTime 和 updateTime,需要添加维护 createBy 和 updateBy 的代码,同时进度需要维护 createTime:

最终效果

前端还需要手动编码详情页面,以及一个完成功能。

打包,发布

在项目根目录,执行mvn clean package打包后端,结果为ruoyi-admin/target/ruoyi-admin.jar

进入ruoyi-ui,执行npm run build:prod打包前端,结果在ruoyi-ui/dist

后端使用java -jar直接启动,前端走nginx,见 http://doc.ruoyi.vip/ruoyi-vue/document/hjbs.html#nginx配置