0


MyBatis Plus 拦截器实现数据权限控制

一、介绍

上篇文章介绍的MyBatis Plus 插件实际上就是用拦截器实现的,MyBatis Plus拦截器对MyBatis的拦截器进行了包装处理,操作起来更加方便

二、自定义拦截器

2.1、InnerInterceptor

MyBatis Plus提供的InnerInterceptor接口提供了如下方法,主要包括:在查询之前执行,在更新之前执行,在SQL准备之前执行
在这里插入图片描述

2.2、编写简易拦截器

packagecom.xx.config;importcom.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;importcom.baomidou.mybatisplus.core.toolkit.PluginUtils;importcom.baomidou.mybatisplus.extension.parser.JsqlParserSupport;importcom.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;importlombok.extern.slf4j.Slf4j;importnet.sf.jsqlparser.expression.StringValue;importnet.sf.jsqlparser.expression.operators.conditional.AndExpression;importnet.sf.jsqlparser.expression.operators.relational.EqualsTo;importnet.sf.jsqlparser.expression.operators.relational.ExpressionList;importnet.sf.jsqlparser.expression.operators.relational.ItemsList;importnet.sf.jsqlparser.schema.Column;importnet.sf.jsqlparser.statement.delete.Delete;importnet.sf.jsqlparser.statement.insert.Insert;importnet.sf.jsqlparser.statement.select.PlainSelect;importnet.sf.jsqlparser.statement.select.Select;importnet.sf.jsqlparser.statement.select.SelectBody;importnet.sf.jsqlparser.statement.update.Update;importorg.apache.ibatis.executor.statement.StatementHandler;importorg.apache.ibatis.mapping.MappedStatement;importorg.springframework.stereotype.Component;importjava.sql.Connection;/**
 * @author aqi
 * @date 2023/5/17 15:07
 */@Slf4j@ComponentpublicclassTestInterceptorextendsJsqlParserSupportimplementsInnerInterceptor{@OverridepublicvoidbeforePrepare(StatementHandler sh,Connection connection,Integer transactionTimeout){// 这里固定这么写就可以了PluginUtils.MPStatementHandler mpSh =PluginUtils.mpStatementHandler(sh);MappedStatement ms = mpSh.mappedStatement();if(InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())){return;}PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
        mpBs.sql(parserMulti(mpBs.sql(),null));}/**
     * 该方法由JsqlParserSupport提供,主要用于通过API的方式操作SQL
     * 思路:通过API构建出新的条件,并将新的条件和之前的条件拼接在一起
     */@OverrideprotectedvoidprocessSelect(Select select,int index,String sql,Object obj){// 解析SQLSelectBody selectBody = select.getSelectBody();PlainSelect plainSelect =(PlainSelect) selectBody;// 构建eq对象EqualsTo equalsTo =newEqualsTo(newColumn("name"),newStringValue("tom"));// 将原来的条件和新构建的条件合在一起AndExpression andExpression =newAndExpression(plainSelect.getWhere(), equalsTo);// 重新封装where条件
        plainSelect.setWhere(andExpression);}@OverrideprotectedvoidprocessInsert(Insert insert,int index,String sql,Object obj){
        insert.getColumns().add(newColumn("name"));((ExpressionList) insert.getItemsList()).getExpressions().add(newStringValue("tom"));}@OverrideprotectedvoidprocessUpdate(Update update,int index,String sql,Object obj){
        update.addUpdateSet(newColumn("name"),newStringValue("tom"));}@OverrideprotectedvoidprocessDelete(Delete delete,int index,String sql,Object obj){// 删除新增条件和查询一样,不做演示}}

2.3、将拦截器添加到MyBatis Plus拦截器中

packagecom.xx.config;importcom.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;/**
 * @author aqi
 * @date 2023/5/15 14:05
 */@ConfigurationpublicclassMybatisPlusConfig{@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){// 初始化Mybatis Plus拦截器MybatisPlusInterceptor interceptor =newMybatisPlusInterceptor();
        interceptor.addInnerInterceptor(newTestInterceptor());return interceptor;}}

2.4、编写测试用例

@Testvoidsave(){AirlinesInfo airlinesInfo =newAirlinesInfo();
        airlinesInfo.setInfo("remark");
        airlinesInfoService.save(airlinesInfo);}@Testvoidupdate(){AirlinesInfo airlinesInfo =newAirlinesInfo();
        airlinesInfo.setId(1L);
        airlinesInfo.setInfo("remark, remark");
        airlinesInfoService.updateById(airlinesInfo);}@Testvoidselect(){
        airlinesInfoService.list();}

2.5、执行结果

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

三、自定义拦截器实现数据权限控制

3.1、编写拦截器

packagecom.xx.config;importcom.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;importcom.baomidou.mybatisplus.core.toolkit.PluginUtils;importcom.baomidou.mybatisplus.extension.parser.JsqlParserSupport;importcom.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;importcom.xx.entity.Permission;importcom.xx.utils.ExpressionUtils;importcom.xx.utils.UserUtils;importlombok.extern.slf4j.Slf4j;importnet.sf.jsqlparser.expression.Expression;importnet.sf.jsqlparser.statement.select.PlainSelect;importnet.sf.jsqlparser.statement.select.Select;importnet.sf.jsqlparser.statement.select.SelectBody;importorg.apache.ibatis.executor.statement.StatementHandler;importorg.apache.ibatis.mapping.MappedStatement;importorg.apache.ibatis.mapping.SqlCommandType;importorg.springframework.stereotype.Component;importjava.sql.Connection;/**
 * @author xiaxing
 */@Slf4j@ComponentpublicclassDataScopeInterceptorextendsJsqlParserSupportimplementsInnerInterceptor{@OverridepublicvoidbeforePrepare(StatementHandler sh,Connection connection,Integer transactionTimeout){
        log.info("[DataScopeInterceptor]beforePrepare...");PluginUtils.MPStatementHandler mpSh =PluginUtils.mpStatementHandler(sh);MappedStatement ms = mpSh.mappedStatement();SqlCommandType sct = ms.getSqlCommandType();if(sct ==SqlCommandType.INSERT || sct ==SqlCommandType.SELECT){if(InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())){return;}PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
            mpBs.sql(parserMulti(mpBs.sql(),null));}}/**
     * 查询
     */@OverrideprotectedvoidprocessSelect(Select select,int index,String sql,Object obj){SelectBody selectBody = select.getSelectBody();PlainSelect plainSelect =(PlainSelect) selectBody;// 获取表名/别名(如果是关联查询是取第一个join左侧的表名/别名)String tableName =ExpressionUtils.getTableName(plainSelect);// 构建用户权限控制条件Expression userPermissionExpression =this.buildUserPermissionSql(tableName);if(null!= userPermissionExpression){// 将sql原本就有得where条件和新构建出来的条件拼接起来
            plainSelect.setWhere(ExpressionUtils.appendExpression(plainSelect.getWhere(), userPermissionExpression));}}/**
     * 构建用户权限控制条件
     * @param tableName 表名/别名(join查询左侧表名)
     */privateExpressionbuildUserPermissionSql(String tableName){// 获取当前用户信息(这里的数据都是模拟的,实际上可能得从缓存或者session中获取)Permission permission =UserUtils.getUserPermission();returnnull!= permission ?ExpressionUtils.buildInSql(tableName +"."+ permission.getField(), permission.getValue()):null;}}

3.2、编写构建SQL工具类

packagecom.xx.utils;importnet.sf.jsqlparser.expression.Alias;importnet.sf.jsqlparser.expression.Expression;importnet.sf.jsqlparser.expression.StringValue;importnet.sf.jsqlparser.expression.operators.conditional.AndExpression;importnet.sf.jsqlparser.expression.operators.relational.EqualsTo;importnet.sf.jsqlparser.expression.operators.relational.ExpressionList;importnet.sf.jsqlparser.expression.operators.relational.InExpression;importnet.sf.jsqlparser.expression.operators.relational.ItemsList;importnet.sf.jsqlparser.schema.Column;importnet.sf.jsqlparser.schema.Table;importnet.sf.jsqlparser.statement.select.PlainSelect;importjava.util.Set;importjava.util.stream.Collectors;/**
 * @author aqi
 * @date 2023/5/17 10:16
 * @describe JSqlParser工具类,用于通过API的方式操作SQL语句
 */publicclassExpressionUtils{/**
     * 构建in sql
     * @param columnName 字段名称
     * @param params 字段值
     * @return InExpression
     */publicstaticInExpressionbuildInSql(String columnName,Set<String> params){// 把集合转变为JSQLParser需要的元素列表ItemsList itemsList =newExpressionList(params.stream().map(StringValue::new).collect(Collectors.toList()));// 创建IN表达式对象,传入列名及IN范围列表returnnewInExpression(newColumn(columnName), itemsList);}/**
     * 构建eq sql
     * @param columnName 字段名称
     * @param value 字段值
     * @return EqualsTo
     */publicstaticEqualsTobuildEq(String columnName,String value){returnnewEqualsTo(newColumn(columnName),newStringValue(value));}/**
     * 获取表名/别名
     * @param plainSelect plainSelect
     * @return 表名/别名
     */publicstaticStringgetTableName(PlainSelect plainSelect){// 获取别名Table table=(Table) plainSelect.getFromItem();Alias alias = table.getAlias();returnnull== alias ? table.getName(): alias.getName();}/**
     * 将2个where条件拼接到一起
     * @param where 条件
     * @param appendExpression 待拼接条件
     * @return Expression
     */publicstaticExpressionappendExpression(Expression where,Expression appendExpression){returnnull== where ? appendExpression :newAndExpression(where, appendExpression);}}

3.3、模拟用户信息工具类

packagecom.xx.utils;importcom.xx.config.Globle;importcom.xx.entity.Permission;importcom.xx.entity.User;importlombok.extern.slf4j.Slf4j;importorg.springframework.web.context.request.RequestAttributes;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;importjavax.servlet.http.HttpServletRequest;importjava.util.*;/**
 * @author aqi
 * @date 2023/5/17 14:20
 */@Slf4jpublicclassUserUtils{publicstaticUser currentUser;static{// 构建测试数据List<Permission> permissionList =newArrayList<>();// demo/test接口权限Permission permission =newPermission();
        permission.setField("id");
        permission.setUri("/demo/test");Set<String> set =newHashSet<>();
        set.add("1");
        set.add("2");
        set.add("3");
        permission.setValue(set);
        permissionList.add(permission);// demo/test1接口权限Permission permission1 =newPermission();
        permission1.setField("id");
        permission1.setUri("/demo/test1");Set<String> set1 =newHashSet<>();
        set1.add("4");
        set1.add("5");
        set1.add("6");
        permission1.setValue(set1);
        permissionList.add(permission1);User user =newUser();
        user.setPermissionList(permissionList);
        user.setTenantId("1");
        currentUser = user;}publicstaticPermissiongetUserPermission(){User currentUser =Globle.currentUser;String uri =UserUtils.getUri();List<Permission> permissionList = currentUser.getPermissionList();return permissionList.stream().filter(e ->Objects.equals(e.getUri(), uri)).findFirst().orElse(null);}/**
     * 获取本次请求的uri
     * @return uri
     */privatestaticStringgetUri(){// 获取此次请求的uriString uri ="";RequestAttributes requestAttributes =RequestContextHolder.getRequestAttributes();if(null!= requestAttributes){HttpServletRequest request =((ServletRequestAttributes) requestAttributes).getRequest();
            uri = request.getRequestURI();}
        log.info("[DataScopeInterceptor]此次请求uri:{}", uri);return uri;}}

3.4、将拦截器添加到MyBatis Plus蓝机器中

packagecom.xx.config;importcom.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;/**
 * @author aqi
 * @date 2023/5/15 14:05
 */@ConfigurationpublicclassMybatisPlusConfig{@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){// 初始化Mybatis Plus拦截器MybatisPlusInterceptor interceptor =newMybatisPlusInterceptor();
        interceptor.addInnerInterceptor(newDataScopeInterceptor());return interceptor;}}

3.5、测试

packagecom.xx.controller;importcom.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;importcom.baomidou.mybatisplus.core.conditions.query.QueryWrapper;importcom.baomidou.mybatisplus.extension.plugins.pagination.Page;importcom.github.pagehelper.PageHelper;importcom.xx.entity.AirlinesInfo;importcom.xx.service.AirlinesInfoService;importlombok.extern.slf4j.Slf4j;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjavax.annotation.Resource;/**
 * @author aqi
 * @date 2023/5/18 11:01
 */@Slf4j@RestController@RequestMapping("/demo")publicclassDemoController{@ResourceprivateAirlinesInfoService airlinesInfoService;@GetMapping("/test")publicvoidtest(){
        log.info("进入test接口,测试权限控制在基础的sql语句是否能生效");
        airlinesInfoService.list();// 执行结果:(SELECT * FROM airlines_info WHERE state = 0 AND airlines_info.id IN ('1', '2', '3'))}@GetMapping("/test1")publicvoidtest1(){
        log.info("进入test1接口,测试权限控制在使用MyBatis Plus 的分页插件之后能否生效");Page<AirlinesInfo> page =newPage<>(1,5);
        airlinesInfoService.page(page,newQueryWrapper<AirlinesInfo>().eq("name","tom"));// 执行结果:(SELECT * FROM airlines_info WHERE state = 0 AND (name = ?) AND airlines_info.id IN ('4', '5', '6') LIMIT ?)}@GetMapping("/test2")publicvoidtest2(){
        log.info("进入test2接口,测试权限控制在使用PageHelper之后能否生效");PageHelper.startPage(1,5);
        airlinesInfoService.list(newLambdaQueryWrapper<AirlinesInfo>().eq(AirlinesInfo::getName,"tom"));// 执行结果:(SELECT * FROM airlines_info WHERE state = 0 AND (name = ?) AND airlines_info.id IN ('7', '8', '9') LIMIT ?)}@GetMapping("/test3")publicvoidtest3(){
        log.info("进入test3接口,测试权限控制在使用自定义复杂关联查询之后能否生效");
        airlinesInfoService.innerSql();// 原始SQL:(select * from airlines_info t1 INNER JOIN t_config on t1.id = t_config.id where t1.name = 'tom' and t_config.name = 'jack' limit 5)// 执行结果:(SELECT * FROM airlines_info t1 INNER JOIN t_config ON t1.id = t_config.id WHERE t1.name = 'tom' AND t_config.name = 'jack' AND t1.id IN ('11', '12', '10') LIMIT 5)}@GetMapping("/test4")publicvoidtest4(){
        log.info("进入test4接口,测试该接口没有设计权限限制是否可以不生效");
        airlinesInfoService.list();// 执行结果:(SELECT * FROM airlines_info WHERE state = 0)}}

四、结论

通过测试可以看出不论在什么情况下都可以正常的对权限进行控制

注意:上面部分代码使用的是MyBatis Plus 3.5.3版本,并且使用的JSqlParser部分API已经不推荐使用,但是我没有找到最新的API应该怎么写

标签: mybatis java mysql

本文转载自: https://blog.csdn.net/progammer10086/article/details/130701584
版权归原作者 有人看我吗 所有, 如有侵权,请联系我们删除。

“MyBatis Plus 拦截器实现数据权限控制”的评论:

还没有评论