概念
数据权限是指对系统用户进行数据资源可见性的控制。实现不同角色登录系统所展示的操作数据范围不一样,达到角色与角色、用户与用户之间数据的隔离。例如:管理员可以看到所有的菜单,而普通用户只能看到部分菜单。在同个表格数据中,管理员可以看到所有用户的数据,而普通用户只能查询到自己的数据。
1.引入依赖
<dependency> <groupId>com.baomidougroupId> <artifactId>mybatis-plus-boot-starterartifactId> <version>版本自选version>dependency>
2.基本使用
(1).数据权限枚举
import lombok.AllArgsConstructor;import lombok.Getter;@AllArgsConstructor@Getterpublic enum DataScope { ALL(1, "所有权限"), DEPARTMENT(2, "本部门"), SELF(3, "仅本人") ; private final Integer code; private final String description; public static DataScope findDataScope(Integer code) { for (DataScope value : DataScope.values()) { if (value.code.equals(code)) { return value; } } return null; }}
(2).Mybatis-Plus配置类
import com.baomidou.mybatisplus.annotation.DbType;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;import com.baymax.interceptor.MybatisPlusPermissionInterceptor;import org.mybatis.spring.annotation.MapperScan;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2)); // 数据权限插件,MybatisPlusPermissionInterceptor需自己实现 interceptor.addInnerInterceptor(new MybatisPlusPermissionInterceptor()); return interceptor; }}
(3).Mybatis-Plus拦截器
注意:下面继承的JsqlParserSupport类,是Mybatis-Plus包里已经引入的,这是一个强大的SQL语句解析器,感兴趣的可以去GitHub上看看,地址:https://github.com/JSQLParser/JSqlParser
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;import net.sf.jsqlparser.expression.Expression;import net.sf.jsqlparser.statement.delete.Delete;import net.sf.jsqlparser.statement.select.PlainSelect;import net.sf.jsqlparser.statement.select.Select;import net.sf.jsqlparser.statement.select.SelectBody;import net.sf.jsqlparser.statement.select.SetOperationList;import net.sf.jsqlparser.statement.update.Update;import org.apache.ibatis.executor.Executor;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.session.ResultHandler;import org.apache.ibatis.session.RowBounds;import java.sql.Connection;import java.sql.SQLException;import java.util.List;public class MybatisPlusPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor { private final MybatisPlusPermissionHandler mybatisPlusPermissionHandler = new MybatisPlusPermissionHandler(); @Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { // 通过MP插件拿到即将执行的SQL PluginUtils.MPBoundSql mp = PluginUtils.mpBoundSql(boundSql); // parserSingle方法是JsqlParserSupport父类实现的方法,这里会根据执行的SQL是查询、新增、修改、删除来调用不同的方法,例如:如果是查询,就会调用当前类的processSelect方法 mp.sql(parserSingle(mp.sql(), ms.getId())); } @Override public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { InnerInterceptor.super.beforePrepare(sh, connection, transactionTimeout); } @Override protected void processDelete(Delete delete, int index, String sql, Object obj) { super.processDelete(delete, index, sql, obj); } @Override protected void processUpdate(Update update, int index, String sql, Object obj) { super.processUpdate(update, index, sql, obj); } @Override protected void processSelect(Select select, int index, String sql, Object obj) { SelectBody selectBody = select.getSelectBody(); try { // 单个sql if (selectBody instanceof PlainSelect) { this.setWhere((PlainSelect) selectBody, obj.toString()); } else if (selectBody instanceof SetOperationList) { // 多个sql,用;号隔开,一般不会用到。例如:select * from user;select * from role; SetOperationList setOperationList = (SetOperationList) selectBody; List<SelectBody> selects = setOperationList.getSelects(); selects.forEach(s -> this.setWhere((PlainSelect) s, obj.toString())); } } catch (Exception e) { e.printStackTrace(); } } protected void setWhere(PlainSelect plainSelect, String mapperId) { Expression sqlSegment = mybatisPlusPermissionHandler.getSqlSegment(plainSelect.getWhere(), mapperId); if (null != sqlSegment) { plainSelect.setWhere(sqlSegment); } }
(4).Mybatis-Plus处理器
这里就是处理数据权限的主要类,来动态拼接sql的条件,达到数据隔离的效果
import com.baymax.enums.DataScope;import com.baymax.utils.SecurityUserUtils;import net.sf.jsqlparser.expression.Expression;import net.sf.jsqlparser.expression.Parenthesis;import net.sf.jsqlparser.expression.operators.conditional.AndExpression;import net.sf.jsqlparser.parser.CCJSqlParserUtil;public class MybatisPlusPermissionHandler { public Expression getSqlSegment(Expression where, String mapperId) { //TODO 如果是管理员,拥有全部数据权限,不做任何条件语句的拼接,直接返回未处理的where if(管理员) { return where; } //TODO mapperId=类路径 + 方法。例如:com.xxx.UserMapper.selectList() // 结合自定义注解,通过反射的方式拿到方法上的注解。例如联查SQL,需要指定(别名.数据权限字段)拼接到where后面 // if("判断mapperId执行方法上是否有注解") { // "拿到value后,传入下面的buildDataFilter()方法里做条件拼接" // } // 构建查询条件 String sql = buildDataFilter(); if ("".equals(sql)) { return where; } try { Expression expression = CCJSqlParserUtil.parseExpression(sql); // 数据权限使用单独的括号 防止与其他条件冲突 Parenthesis parenthesis = new Parenthesis(expression); if (null != where) { return new AndExpression(where, parenthesis); } else { return parenthesis; } } catch (Exception e) { throw new RuntimeException("数据权限解析异常 => " + e.getMessage()); } } private String buildDataFilter() { StringBuilder stringBuilder = new StringBuilder(); DataScope dataScope = "拿当前用户的数据权限"; // 部门数据权限 if (DataScope.DEPARTMENT == dataScope) { // 例如:可以返回 "department_id IN (当前用户所属的部门id)" return ""; } else if (DataScope.SELF == dataScope) { // 仅本人数据权限,create_by字段是表中的字段,这里不一定是这个字段,根据自己需求在需要做数据权限的表加上自定义得字段 stringBuilder.append(" create_by").append("= '").append("拿当前用户的username").append("'"); return stringBuilder.toString(); } return ""; }
注意:这里省略了登录的流程,这个自行实现
3.测试
当前登录的用户为:zzh
(1).即将查询的表数据
(2).查询的结果
来源地址:https://blog.csdn.net/qq_44216791/article/details/128982068