引言
Mybatis Plus对Mybatis做了无侵入的增强,非常的好用,今天就给大家介绍它的其中一个实用功能:数据权限插件。
数据权限插件的应用场景和多租户的动态拦截拼接SQL一样。建议点赞+收藏+关注,方便以后复习查阅。
依赖
首先导入Mybatis Plus的maven依赖,我使用的是3.5.3.2版本。
<properties><mybatis-plus.version>3.5.3.2</mybatis-plus.version>
</properties><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version>
</dependency>
数据权限拦截器
写一个自定义的权限注解,该注解用来标注被拦截方法,注解上可以配置数据权限的表别名和表字段,它们会在拼接sql的时候用到。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyDataScope {/*** 表别名设置*/String alias() default "";/*** 数据权限表字段名*/String dataId() default "";
}
接下来就是写最核心的拦截器的处理逻辑了。创建一个接口实现类,实现Mybatis Plus的DataPermissionHandler接口。DataPermissionHandler的接口方法getSqlSegment有两个参数。
- Expression where。where参数是mapper接口在xml中定义的sql的where条件表达式,在拦截处理器中我们可以给where条件表达式添加一些 and 或 or 的条件。
- String mappedStatementId。mappedStatementId参数是mapper接口方法的全限定名,通过它我们可以得到mapper接口的Class类名以及接口方法名。
DataPermissionHandler的接口方法getSqlSegment会返回一个Expression类型的结果,即通过拦截器方法我们将原始的where条件表达式做了修改之后返回给Mybatis Plus并在代码运行时生效。
在拦截器方法中还使用到了一开始我们自定义的MyDataScope注解,没有被MyDataScope注解标注过的mapper方法我们直接返回原始的where条件表达式即可。
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.itguoguo.annotation.MyDataScope;
import com.itguoguo.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;import java.lang.reflect.Method;
import java.util.Objects;import static com.itguoguo.utils.LoginUserUtils.getLoginUser;@Slf4j
public class MyDataScopeHandler implements DataPermissionHandler {/*** 获取数据权限 SQL 片段表达式* @param where 待执行 SQL Where 条件表达式* @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法* @return 数据权限 SQL 片段表达式*/@Overridepublic Expression getSqlSegment(Expression where, String mappedStatementId) {try {String className = mappedStatementId.substring(0, mappedStatementId.lastIndexOf("."));String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);Method[] methods = Class.forName(className).getMethods();for (Method m : methods) {if (StrUtil.isBlank(m.getName()) || !m.getName().equals(methodName)) {continue;}MyDataScope annotation = m.getAnnotation(MyDataScope.class);if (Objects.isNull(annotation)) {return where;}String sqlSegment = getSqlSegment(annotation);return StrUtil.isBlank(sqlSegment) ? where : getExpression(where, sqlSegment);}} catch (ClassNotFoundException e) {log.error(e.getMessage(), e);}return null;}/*** 拼接需要在业务 SQL 中额外追加的数据权限 SQL* @param annotation* @return 数据权限 SQL*/private String getSqlSegment(MyDataScope annotation) {LoginUser loginUser = getLoginUser();String userType = loginUser.getSysUser().getUserType();Long userId = loginUser.getSysUser().getUserId();String sqlSegment = "";if (StrUtil.isBlank(userType)) {return sqlSegment;}if ("0".equals(userType)) {return sqlSegment;} else {sqlSegment = StrUtil.format(" {}.{} IN (SELECT project_id FROM sys_user where user_id = '{}') ",annotation.alias(), annotation.dataId(), userId);}return sqlSegment;}/*** 将数据权限 SQL 语句追加到数据权限 SQL 片段表达式里* @param where 待执行 SQL Where 条件表达式* @param sqlSegment 数据权限 SQL 片段* @return*/private Expression getExpression(Expression where, String sqlSegment) {try {Expression sqlSegmentExpression = CCJSqlParserUtil.parseCondExpression(sqlSegment);return (null != where) ? new AndExpression(where, sqlSegmentExpression) : sqlSegmentExpression;} catch (JSQLParserException e) {log.error(e.getMessage(), e);}return null;}
}
拦截器配置
配置Mybatis Plus拦截器,数据权限handler作为参数传给拦截器构造方法。
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new DataPermissionInterceptor(new MyDataScopeHandler()));return interceptor;}
}
使用
使用时,在mapper接口的方法上标注MyDataScope注解,给注解标上表别名和表字段。
public interface MyMapper {@MyDataScope(alias = "a", dataId = "id")List findList();
}
建议点赞+收藏+关注,方便以后复习查阅。