概念
数据权限是指对系统用户进行数据资源可见性的控制。实现不同角色登录系统所展示的操作数据范围不一样,达到角色与角色、用户与用户之间数据的隔离。例如:管理员可以看到所有的菜单,而普通用户只能看到部分菜单。在同个表格数据中,管理员可以看到所有用户的数据,而普通用户只能查询到自己的数据。
1.引入依赖
<!-- SpringBoot工程 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>版本自选</version>
</dependency>
2.基本使用
(1).数据权限枚举
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 数据权限枚举
*
* @author baymax
* @date 2023-02-07 10:41
*/
@AllArgsConstructor
@Getter
public 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;
/**
* mp配置类
*
* @author baymax
* @date 2023-02-07 10:11
*/
@Configuration
public class MybatisPlusConfig {
/**
* 添加MP插件到Spring容器中
*/
@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;
/**
* Mybatis-Plus拦截器
*
* @author baymax
* @date 2023-02-08 16:58
*/
public class MybatisPlusPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
/**
* 数据权限解析器,拼接条件sql,需自己创建的类
*/
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()));
}
/**
* 操作前置处理,可以在这里改改sql啥的
*/
@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;
/**
* 数据权限处理器
*
* @author baymax
* @date 2023-02-09 15:50
*/
public class MybatisPlusPermissionHandler {
/**
* 拼接数据权限sql的条件
*
* @param where sql语句的where条件
* @param mapperId mapper执行的方法
*/
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());
}
}
/**
* 非常重要!!!
* 拼接数据权限sql的条件,比如在我们登录成功时,会把用户、角色、数据权限信息缓存到容器上下文或者redis中,当非管理员用户访问某个接口时有查询一些数据,最后会到这个来,这时候我们可以从容器上下文或redis中拿到当前用户的信息,判断用户的数据权限,不同数据权限走不同的拼接SQL
*/
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).即将查询的表数据
文章来源:https://www.toymoban.com/news/detail-688145.html
(2).查询的结果
文章来源地址https://www.toymoban.com/news/detail-688145.html
到了这里,关于基于Mybatis-Plus实现数据权限的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!