RuoYi 用户数据权限与车间设备数据实现
一、作业要求
- 分析 RuoYi 中 DataScopeAspect 数据权限切面,说明不同数据权限最终拼接的 SQL 以及作用。
- 参考 PPT 的效果,实现“车间设备数据”的代码和数据库建表。设备号使用自己名字的拼音加三位序号,例如 LiuYi_001。
本文基于 RuoYi 单体版后端 + Thymeleaf 页面实现。
二、数据权限的核心思想
RuoYi 的数据权限不是在前端隐藏数据,而是在后端查询 SQL 中动态拼接过滤条件。它的核心流程是:
Controller -> Service(@DataScope) -> DataScopeAspect -> Mapper.java -> Mapper.xml -> SQL
其中最关键的两个位置是:
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java
@DataScope 注解中常用属性如下:
public @interface DataScope { public String deptAlias() default ""; public String userAlias() default ""; public String permission() default ""; }
deptAlias 表示 SQL 中部门表的别名,userAlias 表示用户表的别名。切面会根据当前登录用户角色的数据权限,把 SQL 条件写入查询对象的 params.dataScope 中。
三、DataScopeAspect 的执行过程
切面类中使用 @Before 监听带有 @DataScope 的方法:
@Before("@annotation(controllerDataScope)") public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable { clearDataScope(point); handleDataScope(point, controllerDataScope); }
调用链可以理解为:
doBefore -> handleDataScope -> dataScopeFilter
dataScopeFilter 根据角色的数据权限类型拼接不同 SQL,最后放入:
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
Mapper XML 中再通过下面这一行使用切面拼接好的 SQL:
${params.dataScope}
四、五种数据权限对应的 SQL
假设当前查询使用:
@DataScope(deptAlias = "d", userAlias = "u")
当前用户部门 ID 为 105,用户 ID 为 2。
| 数据权限类型 | 值 | 最终追加 SQL | 作用 |
|---|---|---|---|
| 全部数据权限 | 1 | 不追加过滤 SQL | 可以查看全部数据 |
| 自定义数据权限 | 2 | AND (d.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = 当前角色ID )) | 只能查看角色分配的部门数据 |
| 本部门数据权限 | 3 | AND (d.dept_id = 105) | 只能查看当前用户所属部门数据 |
| 本部门及以下数据权限 | 4 | AND (d.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = 105 or find_in_set(105, ancestors) )) | 可以查看本部门和子部门数据 |
| 仅本人数据权限 | 5 | 有 userAlias 时:AND (u.user_id = 2);没有 userAlias 时:AND (d.dept_id = 0) | 只能查看本人数据;若业务 SQL 没有用户别名,则不返回数据 |
PPT 中普通角色设置为“本部门数据权限”时,后端日志里能看到类似:
AND (d.dept_id = 105)
这说明数据权限的本质就是修改最终执行的 SQL。
五、车间设备业务设计
PPT 要求实现“车间的设备数据”:
一个车间[部门]的负责人[角色]只能查看自己车间的设备数据 两个车间[部门]:测试部门、财务部门 两个车间员工[用户]:ry、lewis 普通角色:数据权限设置为本部门数据权限
因此设备表必须保存所属部门:
workshop_device.dept_id
查询时必须关联部门表并使用别名 d:
from workshop_device t left join sys_dept d on t.dept_id = d.dept_id
Service 层使用:
@DataScope(deptAlias = "d")
Mapper XML 中使用:
${params.dataScope}
这样当 ry 属于测试部门,普通角色数据权限为“本部门数据权限”时,最终 SQL 会追加:
AND (d.dept_id = 105)
ry 只能看到测试部门设备;lewis 属于财务部门时,只能看到财务部门设备;admin 是超级管理员,可以看到全部设备。
六、数据库建表
SQL 文件位置:
sql/workshop_device.sql
建表语句如下:
DROP TABLE IF EXISTS workshop_device; CREATE TABLE workshop_device ( device_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '设备ID', device_no VARCHAR(64) NOT NULL COMMENT '设备号', device_name VARCHAR(100) NOT NULL COMMENT '设备名称', dept_id BIGINT(20) NOT NULL COMMENT '所属车间/部门ID', status CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)', create_by VARCHAR(64) DEFAULT '' COMMENT '创建者', create_time DATETIME DEFAULT NULL COMMENT '创建时间', update_by VARCHAR(64) DEFAULT '' COMMENT '更新者', update_time DATETIME DEFAULT NULL COMMENT '更新时间', remark VARCHAR(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (device_id), UNIQUE KEY uk_workshop_device_no (device_no), KEY idx_workshop_device_dept_id (dept_id) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='车间设备表';
测试数据:
INSERT INTO workshop_device (device_no, device_name, dept_id, status, create_by, create_time, remark) VALUES ('LiuYi_001', '测试部门-数控机床', 105, '0', 'admin', NOW(), '测试部门设备,普通角色为本部门数据权限时测试部门用户可见'), ('LiuYi_002', '测试部门-检测仪', 105, '0', 'admin', NOW(), '测试部门设备,普通角色为本部门数据权限时测试部门用户可见'), ('LiuYi_003', '财务部门-票据扫描仪', 106, '0', 'admin', NOW(), '财务部门设备,普通角色为本部门数据权限时财务部门用户可见');
如果本地 sys_dept 中测试部门和财务部门 ID 不同,需要把 105、106 改成实际部门 ID。
七、后端代码实现
新增文件:
ruoyi-system/src/main/java/com/ruoyi/system/domain/WorkshopDevice.java ruoyi-system/src/main/java/com/ruoyi/system/mapper/WorkshopDeviceMapper.java ruoyi-system/src/main/java/com/ruoyi/system/service/IWorkshopDeviceService.java ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WorkshopDeviceServiceImpl.java ruoyi-system/src/main/resources/mapper/system/WorkshopDeviceMapper.xml ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/WorkshopDeviceController.java
1. 实体类
public class WorkshopDevice extends BaseEntity { private Long deviceId; private String deviceNo; private String deviceName; private Long deptId; private String deptName; private String status; }
实体类继承 BaseEntity,这样切面才能把数据权限 SQL 写入 params.dataScope。
2. Service 层数据权限注解
@Override @DataScope(deptAlias = "d") public List<WorkshopDevice> selectWorkshopDeviceList(WorkshopDevice workshopDevice) { return workshopDeviceMapper.selectWorkshopDeviceList(workshopDevice); }
这里的 deptAlias = "d" 必须和 Mapper XML 中的 sys_dept d 保持一致。
3. Mapper XML 查询
<sql id="selectWorkshopDeviceVo"> select t.device_id, t.device_no, t.device_name, t.dept_id, d.dept_name, t.status, t.create_by, t.create_time, t.update_by, t.update_time, t.remark from workshop_device t left join sys_dept d on t.dept_id = d.dept_id </sql> <select id="selectWorkshopDeviceList" parameterType="WorkshopDevice" resultMap="WorkshopDeviceResult"> <include refid="selectWorkshopDeviceVo"/> <where> <if test="deviceNo != null and deviceNo != ''"> and t.device_no like concat('%', #{deviceNo}, '%')</if> <if test="deviceName != null and deviceName != ''"> and t.device_name like concat('%', #{deviceName}, '%')</if> <if test="deptId != null"> and t.dept_id = #{deptId}</if> <if test="status != null and status != ''"> and t.status = #{status}</if> ${params.dataScope} </where> order by t.device_id </select>
这段代码是本次功能的关键。没有 ${params.dataScope},数据权限不会生效;没有 sys_dept d,切面拼出来的 d.dept_id 也无法执行。
4. Controller 层
@RequiresPermissions("system:device:list") @PostMapping("/list") @ResponseBody public TableDataInfo list(WorkshopDevice workshopDevice) { startPage(); List<WorkshopDevice> list = workshopDeviceService.selectWorkshopDeviceList(workshopDevice); return getDataTable(list); }
Controller 和普通 RuoYi CRUD 一样,负责列表、新增、修改、删除、导出等入口。
八、前端页面实现
新增页面:
ruoyi-admin/src/main/resources/templates/system/device/device.html ruoyi-admin/src/main/resources/templates/system/device/add.html ruoyi-admin/src/main/resources/templates/system/device/edit.html ruoyi-admin/src/main/resources/templates/system/device/deptTree.html
列表页支持设备号、设备名称、状态查询,并支持新增、修改、删除、导出。
设备号在新增和修改页面中做了格式校验,要求类似:
LiuYi_001 LiuYi_002 LiuYi_003
九、菜单权限 SQL
sql/workshop_device.sql 中加入菜单和按钮权限:
INSERT INTO sys_menu VALUES (2100, '车间设备', 1, 9, '/system/device', '', 'C', '0', '0', 'system:device:view', 'fa fa-industry', 'admin', NOW(), '', NULL, '车间设备菜单'); INSERT INTO sys_menu VALUES (2101, '车间设备查询', 2100, 1, '#', '', 'F', '0', '0', 'system:device:list', '#', 'admin', NOW(), '', NULL, ''); INSERT INTO sys_menu VALUES (2102, '车间设备新增', 2100, 2, '#', '', 'F', '0', '0', 'system:device:add', '#', 'admin', NOW(), '', NULL, ''); INSERT INTO sys_menu VALUES (2103, '车间设备修改', 2100, 3, '#', '', 'F', '0', '0', 'system:device:edit', '#', 'admin', NOW(), '', NULL, ''); INSERT INTO sys_menu VALUES (2104, '车间设备删除', 2100, 4, '#', '', 'F', '0', '0', 'system:device:remove', '#', 'admin', NOW(), '', NULL, ''); INSERT INTO sys_menu VALUES (2105, '车间设备导出', 2100, 5, '#', '', 'F', '0', '0', 'system:device:export', '#', 'admin', NOW(), '', NULL, '');
管理员角色默认授权:
INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 2100); INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 2101); INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 2102); INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 2103); INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 2104); INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 2105);
普通角色需要在“系统管理 -> 角色管理”中勾选“车间设备”菜单,并将数据权限设置为“本部门数据权限”。
十、测试步骤
- 执行 sql/workshop_device.sql。
- 重启 RuoYi 后端服务。
- 登录 admin,进入“系统管理 -> 车间设备”,应该能看到全部设备。
- 给普通角色分配“车间设备”菜单权限,并设置数据权限为“本部门数据权限”。
- 登录 ry,如果 ry 属于测试部门,只能看到 dept_id = 105 的设备。
- 登录 lewis,如果 lewis 属于财务部门,只能看到 dept_id = 106 的设备。
- 查看后端 SQL 日志,应该能看到类似 AND (d.dept_id = 105) 的数据权限条件。
十一、总结
RuoYi 数据权限的本质是通过 AOP 动态修改 SQL。业务对象只要满足三个条件,就可以复用这套能力:
- 实体类继承 BaseEntity。
- Service 查询方法添加 @DataScope。
- Mapper XML 中关联部门表并使用 ${params.dataScope}。
本次车间设备功能中,设备数据通过 dept_id 绑定到部门。普通角色设置为“本部门数据权限”后,系统会自动拼接 AND (d.dept_id = 当前用户部门ID),从而实现不同车间负责人只能查看自己车间设备数据的效果。
更多推荐




所有评论(0)