若依数据权限实现全流程解析

目录

一、一个简单的需求

二、核心实现原理

三、代码实现详解

[1. 用户请求:用户访问用户列表接口 /system/user/list](#1. 用户请求:用户访问用户列表接口 /system/user/list)

[2. Controller:接收请求,调用Service层方法](#2. Controller:接收请求,调用Service层方法)

[3. AOP拦截:发现方法上有DataScope注解,执行切面逻辑](#3. AOP拦截:发现方法上有DataScope注解,执行切面逻辑)

📌AOP切面处理流程详解

[1. 切面入口:Before 注解](#1. 切面入口:Before 注解)

[2. 清空旧权限参数:clearDataScope](#2. 清空旧权限参数:clearDataScope)

[3. 处理数据权限:handleDataScope](#3. 处理数据权限:handleDataScope)

4、权限类型与SQL生成

[4. SQL执行:将权限条件拼接到原始SQL中](#4. SQL执行:将权限条件拼接到原始SQL中)

四、车间设备数据权限

[1、创建数据库表 ------ 设备信息表](#1、创建数据库表 —— 设备信息表)

2、使用若依「代码生成功能」来快速完成设备信息模块

[3、手动增强 ------ 添加数据权限支持](#3、手动增强 —— 添加数据权限支持)

[① 给 Service 方法加上 @DataScope 注解](#① 给 Service 方法加上 @DataScope 注解)

[② 检查 Mapper XML 是否支持 params.dataScope](#② 检查 Mapper XML 是否支持 params.dataScope)

4、配置角色的数据权限

[① 添加用户](#① 添加用户)

[② 角色权限分配](#② 角色权限分配)

5、分别登录三个账号测试:

[① admin------超级管理员](#① admin——超级管理员)

[② ry------普通用户(测试部门)](#② ry——普通用户(测试部门))

[③ lwy------普通用户(财务部门)](#③ lwy——普通用户(财务部门))


一、一个简单的需求

想象一下,你是一家公司的IT管理员,现在需要实现这样的功能:

  • 老板(admin)可以看到公司所有部门的数据
  • 部门经理只能看到自己部门的数据
  • 普通员工只能看到自己的数据

这个功能在若依框架中是如何实现的呢?让我们一步步来解析。

二、核心实现原理

若依通过AOP(面向切面编程)和MyBatis的动态SQL实现了数据权限控制。简单来说,就是在执行数据库查询前,动态地修改SQL语句,添加权限过滤条件。

三、代码实现详解

1. 用户请求 :用户访问用户列表接口 /system/user/list

代码位置ruoyi-module-system/src/main/java/com/ruoyi/web/controller/system/SysUserController.java

2. Controller:接收请求,调用Service层方法

代码位置ruoyi-module-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java

3. AOP拦截:发现方法上有DataScope注解,执行切面逻辑

代码位置

ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java

  1. DataScopeAspect.doBefore() - 观察切面是否被触发
  2. DataScopeAspect.dataScopeFilter() - 观察权限判断逻辑
  3. SysUserMapper.xml中的SQL语句 - 观察最终生成的SQL
java 复制代码
@Aspect
@Component
public class DataScopeAspect {
    // 数据权限过滤关键字
    public static final String DATA_SCOPE = "dataScope";

    /**
     * 数据权限过滤
     */
    @Before("@annotation(controllerDataScope)")
    public void doBefore(JoinPoint point, DataScope controllerDataScope) {
        // 1. 清空之前的数据权限参数,防止重复添加
        clearDataScope(point);
        // 2. 处理数据权限
        handleDataScope(point, controllerDataScope);
    }

    protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) {
        // 3. 获取当前登录用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNotNull(loginUser)) {
            SysUser currentUser = loginUser.getUser();
            // 4. 如果不是管理员,才需要过滤数据
            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) {
                // 5. 数据权限过滤
                dataScopeFilter(joinPoint, currentUser, 
                    controllerDataScope.deptAlias(), 
                    controllerDataScope.userAlias());
            }
        }
    }

    /**
     * 数据范围过滤
     */
    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, 
            String deptAlias, String userAlias) {
        StringBuilder sqlString = new StringBuilder();
        
        // 6. 遍历用户的所有角色
        for (SysRole role : user.getRoles()) {
            // 7. 获取角色的数据范围
            String dataScope = role.getDataScope();
            // 8. 根据数据范围类型拼接SQL
            if (DATA_SCOPE_ALL.equals(dataScope)) {
                // 全部数据权限
                sqlString = new StringBuilder();
                break;
            } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
                // 自定数据权限
                sqlString.append(StringUtils.format(
                    " OR {}.dept_id IN (SELECT dept_id FROM sys_role_dept WHERE role_id = {}) ", 
                    deptAlias, role.getRoleId()));
            } else if (DATA_SCOPE_DEPT.equals(dataScope)) {
                // 本部门数据权限
                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", 
                    deptAlias, user.getDeptId()));
            } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
                // 本部门及以下数据权限
                sqlString.append(StringUtils.format(
                    " OR {}.dept_id IN (SELECT dept_id FROM sys_dept WHERE dept_id = {} OR find_in_set({}, ancestors))",
                    deptAlias, user.getDeptId(), user.getDeptId()));
            } else if (DATA_SCOPE_SELF.equals(dataScope)) {
                // 仅本人数据权限
                if (StringUtils.isNotBlank(userAlias)) {
                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", 
                        userAlias, user.getUserId()));
                } else {
                    // 如果没有设置用户表别名,则不返回任何数据
                    sqlString.append(" OR 1=0 ");
                }
            }
        }

        // 9. 如果有数据权限条件
        if (StringUtils.isNotBlank(sqlString.toString())) {
            // 10. 获取方法参数
            Object params = joinPoint.getArgs()[0];
            if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
                // 11. 将数据权限条件放入params中
                BaseEntity baseEntity = (BaseEntity) params;
                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
            }
        }
    }
}

📌AOP切面处理流程详解

1. 切面入口:Before 注解

作用

  • 在目标方法执行前进行拦截
  • 清空可能存在的旧数据权限参数
  • 调用权限处理方法
2. 清空旧权限参数:clearDataScope
3. 处理数据权限:handleDataScope
4、权限类型与SQL生成

4. SQL执行:将权限条件拼接到原始SQL中

代码位置ruoyi-module-system/src/main/resources/mapper/system/SysUserMapper.xml

  • ${params.dataScope} 是动态SQL片段,会被替换为数据权限条件
  • 例如:AND (d.dept_id = 105)

四、车间设备数据权限

1、创建数据库表 ------ 设备信息表

2、使用若依**「代码生成功能」来快速完成设备信息模块**

3、手动增强 ------ 添加数据权限支持

① 给 Service 方法加上 @DataScope 注解
② 检查 Mapper XML 是否支持 params.dataScope

4、配置角色的数据权限

① 添加用户
② 角色权限分配

5、分别登录三个账号测试:

① admin------超级管理员
② ry------普通用户(测试部门)
③ lwy------普通用户(财务部门)
相关推荐
Knight_AL11 小时前
MySQL 分区表应用案例:优化数据管理与性能
数据库·mysql
老华带你飞12 小时前
考试管理系统|基于java+ vue考试管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
九皇叔叔12 小时前
MySQL 数据库 MVCC 与锁如何联手解决脏读、不可重复读、幻读
数据库·mysql
WZTTMoon12 小时前
Spring Boot OAuth2 授权码模式开发实战
大数据·数据库·spring boot
AI题库12 小时前
PostgreSQL 18 从新手到大师:实战指南 - 1.1 PostgreSQL 18简介
数据库·postgresql
好记忆不如烂笔头abc12 小时前
Ubuntu 20.04.6上实现远程桌面连接
服务器·网络·数据库
今晚务必早点睡12 小时前
Redis——快速入门第七课:Redis 为什么这么快?
数据库·redis·缓存
EterNity_TiMe_13 小时前
从 0 到 1:Llama 3-8B 在昇腾 Atlas 800T 上的推理调优与算力榨干指南
数据库·llama·昇腾·atlas 800t·实战部署
talenteddriver13 小时前
mysql: MySQL中between子句和limit子句的区别
前端·javascript·数据库
不会kao代码的小王13 小时前
BoostKit 数据库优化原理分析从 MySQL 到 MariaDB 的性能突破
数据库·mysql·mariadb