一、作业要求

  1. 分析 RuoYi 中 DataScopeAspect 数据权限切面,说明不同数据权限最终拼接的 SQL 以及作用。
  2. 参考 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);

普通角色需要在“系统管理 -> 角色管理”中勾选“车间设备”菜单,并将数据权限设置为“本部门数据权限”。

十、测试步骤

  1. 执行 sql/workshop_device.sql。
  2. 重启 RuoYi 后端服务。
  3. 登录 admin,进入“系统管理 -> 车间设备”,应该能看到全部设备。
  4. 给普通角色分配“车间设备”菜单权限,并设置数据权限为“本部门数据权限”。
  5. 登录 ry,如果 ry 属于测试部门,只能看到 dept_id = 105 的设备。
  6. 登录 lewis,如果 lewis 属于财务部门,只能看到 dept_id = 106 的设备。
  7. 查看后端 SQL 日志,应该能看到类似 AND (d.dept_id = 105) 的数据权限条件。

十一、总结

RuoYi 数据权限的本质是通过 AOP 动态修改 SQL。业务对象只要满足三个条件,就可以复用这套能力:

  1. 实体类继承 BaseEntity。
  2. Service 查询方法添加 @DataScope。
  3. Mapper XML 中关联部门表并使用 ${params.dataScope}。

本次车间设备功能中,设备数据通过 dept_id 绑定到部门。普通角色设置为“本部门数据权限”后,系统会自动拼接 AND (d.dept_id = 当前用户部门ID),从而实现不同车间负责人只能查看自己车间设备数据的效果。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐