Mybatis-Plus之实现数据权限插件使用

需求为部门经理只能看自己部门的数据,管理员可以看全部人的数据,普通员工只能看自己的数据,本文并不能直接复制代码就能用,仅提供核心代码,请结合自己项目的实际情况参考使用。

官方文档:https://baomidou.com/plugins/data-permission/

要实现数据权限相关业务表需要有create_id和dept_id,即数据创建人用户id和创建人所在部门id,也可以采用只有create_id字段,也就是通过create_id查询该用户所在部门下的所有员工用户id。两种方案都可以,本文采用只有create_id字段的方案。

核心代码如下:

在项目中,使用的是 spring security + oauth2安全认证,角色来定义数据权限类型。所以这种方法你只能从token里拿用户相关数据。

一、编写自己业务的handle,实现MultiDataPermissionHandler

注意下面代码中存在关于用户token相关类,包括LoginUser、SysUser、SysRole、SecurityUtils等相关类本文将不提供源码,请自行处理。

DataScopePlusHandler.java
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
import com.risk.control.common.annotation.DataScopePlus;
import com.risk.control.common.constant.DataScopeConstants;
import com.risk.control.common.core.domain.entity.SysRole;
import com.risk.control.common.core.domain.entity.SysUser;
import com.risk.control.common.core.domain.model.LoginUser;
import com.risk.control.common.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 数据权限拼装逻辑处理
 *
 */
@Slf4j
public class DataScopePlusHandler implements MultiDataPermissionHandler {

	/**
	 * 获取数据权限 SQL 片段。
	 * <p>旧的 {@link MultiDataPermissionHandler#getSqlSegment(Expression, String)} 方法第一个参数包含所有的 where 条件信息,如果 return 了 null 会覆盖原有的 where 数据,</p>
	 * <p>新版的 {@link MultiDataPermissionHandler#getSqlSegment(Table, Expression, String)} 方法不能覆盖原有的 where 数据,如果 return 了 null 则表示不追加任何 where 条件</p>
	 *
	 * @param table             所执行的数据库表信息,可以通过此参数获取表名和表别名
	 * @param where             原有的 where 条件信息
	 * @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法
	 * @return JSqlParser 条件表达式,返回的条件表达式会拼接在原有的表达式后面(不会覆盖原有的表达式)
	 */
	@Override
	public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
		try {
			Class<?> mapperClazz = Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(".")));
			String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);
			/**
			 * DataScope注解优先级:【类上 > 方法上】
			 */
			// 获取 DataScope注解
			DataScopePlus dataScopeAnnotationClazz = mapperClazz.getAnnotation(DataScopePlus.class);
			if (ObjectUtils.isNotEmpty(dataScopeAnnotationClazz) && dataScopeAnnotationClazz.enabled()) {
				return buildDataScopeByAnnotation(dataScopeAnnotationClazz);
			}
			// 获取自身类中的所有方法,不包括继承。与访问权限无关
			Method[] methods = mapperClazz.getDeclaredMethods();
			for (Method method : methods) {
				DataScopePlus dataScopeAnnotationMethod = method.getAnnotation(DataScopePlus.class);
				if (ObjectUtils.isEmpty(dataScopeAnnotationMethod) || !dataScopeAnnotationMethod.enabled()) {
					continue;
				}
				if (method.getName().equals(methodName) || (method.getName() + "_COUNT").equals(methodName) || (method.getName() + "_count").equals(methodName)) {
					return buildDataScopeByAnnotation(dataScopeAnnotationMethod);
				}
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}


	/**
	 * DataScope注解方式,拼装数据权限
	 *
	 * @param controllerDataScope
	 * @return
	 */
	private Expression buildDataScopeByAnnotation(DataScopePlus controllerDataScope) {
		// 获取当前的用户
		LoginUser loginUser = SecurityUtils.getLoginUser();
		if (com.risk.control.common.utils.StringUtils.isNotNull(loginUser))
		{
			SysUser currentUser = loginUser.getUser();
			// 如果是超级管理员,则不过滤数据
			if (com.risk.control.common.utils.StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
			{
				List<SysRole> roleList = currentUser.getRoles();
				if(roleList.isEmpty()){
					return null;
				}
				//List<Long> dataScopeDeptIds = new ArrayList<>();
				List<Long> createIdList = new ArrayList<>();
				Long dataScopeCreateId = null;
				// 目前用户只能是一种角色
				SysRole role = roleList.get(0);
				String dataScope = role.getDataScope();
				if(DataScopeConstants.DATA_SCOPE_ALL.equals(dataScope)){
					return null;
				}
				// 处理所在本部门的数据
				if(DataScopeConstants.DATA_SCOPE_DEPT.equals(dataScope)){
					createIdList = loginUser.getCreateIdList();
				}
				// 仅查询自己创建的数据
				if(DataScopeConstants.DATA_SCOPE_SELF.equals(dataScope)){
					dataScopeCreateId = currentUser.getUserId();
				}
				Expression expression = dataScopeFilter(controllerDataScope.tableAlias(),controllerDataScope.oneselfScopeName(),
						controllerDataScope.oneselfScopeName(),createIdList,dataScopeCreateId);
				log.info("【DataScopeHandlerPlus】数据权限处理的sql语句:" + expression.toString());
				return dataScopeFilter(controllerDataScope.tableAlias(),controllerDataScope.oneselfScopeName(),
						controllerDataScope.oneselfScopeName(),createIdList,dataScopeCreateId);
			}
		}
		return null;
	}



	/**
	 * 拼装数据权限
	 *
	 * @param tableAlias        表别名
	 * @param deptScopeName     部门限制范围的字段名称
	 * @param oneselfScopeName  本人限制范围的字段名称
	 * @param createIdList  该用户部门下创建人id集合
	 * @param dataScopeCreateId 数据权限本人ID
	 * @return
	 */
	private Expression dataScopeFilter(String tableAlias, String deptScopeName, String oneselfScopeName, List<Long> createIdList, Long dataScopeCreateId) {
		/**
		 * 构造部门in表达式。
		 */
		InExpression deptIdInExpression = null;
		if (CollectionUtils.isNotEmpty(createIdList)) {
			deptIdInExpression = new InExpression();
			ExpressionList deptIds = new ExpressionList(createIdList.stream().map(LongValue::new).collect(Collectors.toList()));
			// 设置左边的字段表达式,右边设置值。
			deptIdInExpression.setLeftExpression(buildColumn(tableAlias, deptScopeName));
			//deptIdInExpression.setRightExpression(new Parenthesis(deptIds));
			deptIdInExpression.setRightItemsList(deptIds);

		}

		/**
		 * 构造本人eq表达式
		 */
		EqualsTo oneselfEqualsTo = null;
		if (dataScopeCreateId != null) {
			oneselfEqualsTo = new EqualsTo();
			oneselfEqualsTo.withLeftExpression(buildColumn(tableAlias, oneselfScopeName));
			oneselfEqualsTo.setRightExpression(new LongValue(dataScopeCreateId));
		}

		if (deptIdInExpression != null && oneselfEqualsTo != null) {
			return new OrExpression(deptIdInExpression, oneselfEqualsTo);
		} else if (deptIdInExpression != null && oneselfEqualsTo == null) {
			return deptIdInExpression;
		} else if (deptIdInExpression == null && oneselfEqualsTo != null) {
			return oneselfEqualsTo;
		}
		return null;
	}

	/**
	 * 构建Column
	 *
	 * @param tableAlias 表别名
	 * @param columnName 字段名称
	 * @return 带表别名字段
	 */
	public static Column buildColumn(String tableAlias, String columnName) {
		if (StringUtils.isNotBlank(tableAlias)) {
			columnName = tableAlias + "." + columnName;
		}
		return new Column(columnName);
	}


}

DataScopePlus.java

import java.lang.annotation.*;

/**
 * @author: 
 * @date: 
 * @Description: 数据权限注解。仅在Mapper层使用
 * 可以使用在类上,也可以使用在方法上。
 * - 如果 Mapper类加上注解,表示 Mapper提供的方法以及自定义的方法都会被加上数据权限
 * - 如果 Mapper类的方法加在上注解,表示该方法会被加上数据权限
 * - 如果 Mapper类和其方法同时加上注解,优先级为:【类上 > 方法上】
 * - 如果不需要数据权限,可以不加注解,也可以使用 @DataScopePlus(enabled = false)
 * - 如果使用的是mybatis-plus自带的查询,需要在Mapper层重写对应的方法,然后在方法上加注解,
 *   也可以不重写,在整个Mapper类上加注解,但是会影响所有方法包括baseMapper里的。
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScopePlus {

    /**
     * 是否生效,默认true-生效
     */
    boolean enabled() default true;

    /**
     * 表别名 自定义sql语句有表别名就设置
     */
    String tableAlias() default "";

    /**
     * 用户表的别名
     */
    //public String userAlias() default "";

    /**
     * 部门限制范围的字段名称
     */
    //String deptScopeName() default "dept_id";

    /**
     * 本人限制范围的字段名称
     */
    String oneselfScopeName() default "create_id";


}

二、在MybatisPlusConfig.java配置文件中注册拦截器

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor()
    {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 数据权限插件
        interceptor.addInnerInterceptor(new DataPermissionInterceptor(new DataScopePlusHandler()));

    }

三、在Mapper层使用注解

public interface TestMapper extends BaseMapper<Test> {

    @DataScopePlus
    List<Test> selectList();

}

四、Mybatis-Plus自带的方法

如果在Service层用是plus自带的方法,则需要在Mapper重写该方法,并且使用权限注解

public interface TestMapper extends BaseMapper<Test> {

    @DataScopePlus
    List<Test> selectList();

    @Override
    @DataScopePlus
    List<Report> selectList(Wrapper<Report> queryWrapper);

}

也可以直接在Mapper层类上使用权限注解,那么所有方法都将进行数据权限过滤。(不推荐)

@DataScopePlus
public interface TestMapper extends BaseMapper<Test> {

    List<Test> selectList();

}

其他

常量类

DataScopeConstants.java

/**
 * @Author: 
 * @Time: 
 * @Description: 数据权限常量类
 */
public class DataScopeConstants {

    /**
     * 全部数据权限
     */
    public static final String DATA_SCOPE_ALL = "1";

    /**
     * 自定数据权限
     */
    public static final String DATA_SCOPE_CUSTOM = "2";

    /**
     * 部门数据权限
     */
    public static final String DATA_SCOPE_DEPT = "3";

    /**
     * 部门及以下数据权限
     */
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";

    /**
     * 仅本人数据权限
     */
    public static final String DATA_SCOPE_SELF = "5";

    /**
     * 数据权限过滤关键字
     */
    public static final String DATA_SCOPE = "dataScope";

}

主要依赖

<!-- mybatis-plus 依賴 -->
<dependency>
   <groupId>com.baomidou</groupId>
   <artifactId>mybatis-plus-boot-starter</artifactId>
   <version>3.5.5</version>
</dependency>

<!-- JSQLParser 4.6 对应 mybatis-plus 3.5.5-->
<dependency>
   <groupId>com.github.jsqlparser</groupId>
   <artifactId>jsqlparser</artifactId>
   <version>4.6</version>
</dependency>

<dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.12.0</version>
</dependency>

延伸

有几个需要注意的点:

1.这种实现方法注解只能在Mapper层使用,service层使用无效。

2.如果注解了BaseMapper自带的方法,那么该方法任何地方使用都会进行数据权限过滤,这个地方可以会发生其他方法调用该方法时并不需要数据过滤的时候过滤了。

3.这种实现方法不管Mapper的实现是不是在对应的xml里进行了sql语句都管用,也就是均适用于mybatis-plus和原生mybatis。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值