若依项目学习笔记09——数据权限

1. 数据权限

在管理系统中,往往需要设置用户只能查看哪些部门的数据,这种情况一般称为数据权限。例如销售,财务的数据是非常敏感的,因此要求对数据权限进行控制, 对于基于集团性的应用系统而言,就更多需要控制好各自公司的数据了。如设置子公司只能看本公司、或者本部门的数据,对于特殊的领导,可能需要跨部门的数据, 因此程序不能硬编码那个领导该访问哪些数据,需要进行后台的权限和数据权限的控制;默认系统管理员admin拥有所有数据权限(userId=1),默认角色拥有所有数据权限(如不需要数据权限不用设置数据权限操作)
在系统管理-角色管理-角色操作-数据权限中可以看到对应的数据权限选项,如下五种权限,大家自行测试各个功能

1.1 实现步骤

下面用 system.service.impl.SysUserServiceImpl 中的 selectUserList() 方法来配合讲解

  1. 在需要数据权限控制方法上添加 @DataScope 注解,其中 d 和 u 用来表示表的别名
// 部门数据权限注解
@DataScope(deptAlias = "u")
// 部门及用户权限注解
@DataScope(deptAlias = "d", userAlias = "u")

支持参数如下:

参数 类型 默认值 描述
deptAlias String 部门表的别名
userAlias String 用户表的别名

如:

@Override
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUserList(SysUser user)
{
    return userMapper.selectUserList(user);
}
  1. 在 mybatis 查询底部标签添加数据范围过滤
<!-- 数据范围过滤 -->
${params.dataScope}

如 SysUserMapper.xml 中的 selectUserList语句

其作用就是相当于在一个 select 语句后面拼接一个 and 条件语句,来实现查询限制,例如下面

用户管理(未过滤数据权限的情况)

select u.user_id, u.dept_id, u.login_name, u.user_name, u.email
	, u.phonenumber, u.password, u.sex, u.avatar, u.salt
	, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by
	, u.create_time, u.remark, d.dept_name
from sys_user u
	left join sys_dept d on u.dept_id = d.dept_id
where u.del_flag = '0'

用户管理(已过滤数据权限的情况)

select u.user_id, u.dept_id, u.login_name, u.user_name, u.email
	, u.phonenumber, u.password, u.sex, u.avatar, u.salt
	, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by
	, u.create_time, u.remark, d.dept_name
from sys_user u
	left join sys_dept d on u.dept_id = d.dept_id
where u.del_flag = '0'
	and u.dept_id in (
		select dept_id
		from sys_role_dept
		where role_id = 2
	)

1.2 逻辑实现

逻辑实现代码在 com.ruoyi.framework.aspectj.DataScopeAspect 中
可以看到依旧是一个切面类,首先依旧是通过织入点找到注解为 @DataScope 的点,然后执行下面的方法;其过程为获取注解-获取用户-判断是否是管理员-否-进行数据范围过滤;接下来我们看 dataScopeFilter()数据范围过滤方法,首先是获取用户权限,然后是对权限进行判断(结合上面贴出的权限范围的图)

//管理员?
String dataScope = role.getDataScope();
if (DATA_SCOPE_ALL.equals(dataScope))
{
    sqlString = new StringBuilder();
    break;
}
//自定义?这里是通过拼接sql语句实现的
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{
    sqlString.append(StringUtils.format(
            //这里的 {} 是获取后面的deptAlias,即部门别名,用户别名同理
            " 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(
            //部门以下通过 find_in_set( {} , ancestors ) 来实现,这是MySQL的语句
            " 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
    {
        // 数据权限为仅本人且没有userAlias别名不查询任何数据
        sqlString.append(" OR 1=0 ");
    }
}

角色判断完后,下面对sql进行拼接

if (StringUtils.isNotBlank(sqlString.toString()))
        {
            Object params = joinPoint.getArgs()[0];
            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
            {
                BaseEntity baseEntity = (BaseEntity) params;
                //sql语句需要通过AND来连接,然后put进基类中的 请求参数 属性中;Entity基类
                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
            }
        }

1.3 业务实现

下面演示一下新的操作日志的数据权限业务,比如我们要新增用户和部门的数据权限管理,步骤如下

  1. 修改sql脚本
    我们打开sql脚本,将操作日志记录部分copy到SQLyog中(记得把图中上面的备注删除),然后加入用户和部门的数据权限管理内容,执行
  2. 在实体类中新增对应属性和方法
    打开 system.domain.SysOperLog,分别添加 userId 和 deptId 属性,还有get/set方法(alt+insert)
...

private Long userId;
private Long deptId;

...

public Long getUserId() {
    return userId;
}
public void setUserId(Long userId) {
    this.userId = userId;
}
public Long getDeptId() {
    return deptId;
}
public void setDeptId(Long deptId) {
    this.deptId = deptId;
}
  1. 添加注解
    在操作日志 system.service.impl.SysOperLogServiceImpl 的 selectOperLogList()方法中添加注解
@Override
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysOperLog> selectOperLogList(SysOperLog operLog)
{
    return operLogMapper.selectOperLogList(operLog);
}
  1. 在mybatis查询底部标签添加数据范围过滤
    打开 SysOperLogMapper ,找到 selectOperLogList 语句,把 <where> 改成 where 1=1 防止条件为空

然后在末尾添加数据范围过滤

  1. 添加管理查询
    还是在mapper文件中找到 selectOperLogVo ,然后添加
select o.oper_id, o.user_id, o.dept_id, o.title, o.business_type, o.method, o.request_method, o.operator_type, o.oper_name, o.dept_name, o.oper_url, o.oper_ip, o.oper_location, o.oper_param, o.json_result, o.status, o.error_msg, o.oper_time
        , u.user_id, d.dept_id
        from sys_oper_log o
        left join sys_user u on u.user_id = o.user_id
        left join sys_dept d on u.dept_id = o.dept_id

这时表内容还是空的,所以我们改造一下插入

  1. 改造插入
    打开 framework.aspectj.LogAspect 日志切面类,插入两个字段

  2. 更改插入方法
    回到mapper文件中,更改 insertOperlog 方法

<insert id="insertOperlog" parameterType="SysOperLog">
		insert into sys_oper_log(user_id, dept_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, oper_time)
        values (#{userId},#{deptId},#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, sysdate())
	</insert>      
  1. 效果测试
    运行项目,我们用管理员创建一个用户,隶属于研发部门,能查看系统管理菜单,只能查看本部门及以下,然后登陆这个用户,可以发现,该用户只能查看本部门及以下的数据内容(系统中还有另一个别的部门的用户,但是无法查看)

    接下来我们用这个用户再创建一个新的用户,再删除,然后到数据库中查看 操作日志表

搞定~(●ˇ∀ˇ●)

posted @ 2020-12-02 10:33  刘条条  阅读(1469)  评论(0编辑  收藏  举报