Mybatis拦截器实现数据权限的示例代码

在我们日常开发过程中,通常会涉及到数据权限问题,下面以我们常见的一种场景举例:

一个公司有很多部门,每个人所处的部门和角色也不同,所以数据权限也可能不同,比如超级管理员可以查看某张

表的素有信息,部门领导可以查看该部门下的相关信息,部门普通人员只可以查看个人相关信息,而且由于角色的

不同,各个角色所能查看到的数据库字段也可能不相同,那么此处就涉及到了数据权限相关的问题。那么我们该如

何处理数据权限相关的问题呢?我们提供一种通过Mybatis拦截器实现的方式,下面我们来具体实现一下

pom.xml依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.13.RELEASE</version>
</parent>

<properties>
    <java.version>1.8</java.version>
    <mybatis-plus.version>3.2.0</mybatis-plus.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>${mybatis-plus.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

application.yml文件

server:
  port: 80

spring:
  application:
    name: data-scope
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    druid:
      # 验证连接是否有效。此参数必须设置为非空字符串,下面三项设置成true才能生效
      validation-query: SELECT 1
      # 连接是否被空闲连接回收器(如果有)进行检验. 如果检测失败, 则连接将被从池中去除
      test-while-idle: true
      # 是否在从池中取出连接前进行检验, 如果检验失败, 则从池中去除连接并尝试取出另一个
      test-on-borrow: true
      # 是否在归还到池中前进行检验
      test-on-return: false
      # 连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 30000

#mybatis配置
mybatis-plus:
  type-aliases-package: com.mk.entity
  mapper-locations: classpath:mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      field-strategy: not_empty
      logic-delete-value: 1
      logic-not-delete-value: 0
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: false
    call-setters-on-nulls: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

代码实现

DataScode.java

@Data
public class DataScope {

    // sql过滤条件
    String sqlCondition;

    // 需要过滤的结果字段
    String[] filterFields;

}

MybatisPlusConfig.java

@Configuration
public class MybatisPlusConfig {

    @Bean
    @ConditionalOnMissingBean
    public DataScopeInterceptor dataScopeInterceptor() {
        return new DataScopeInterceptor();
    }

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor page = new PaginationInterceptor();
        page.setDialectType(DbType.MYSQL.getDb());
        return page;
    }

    @Bean
    public ConfigurationCustomizer mybatisConfigurationCustomizer(){
        return new ConfigurationCustomizer() {
            @Override
            public void customize(MybatisConfiguration configuration) {
                configuration.setObjectWrapperFactory(new MybatisMapWrapperFactory());
            }
        };
    }
}

DataScopeInterceptor.java

@Slf4j
@Intercepts({@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class})})
public class DataScopeInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        log.info("执行intercept方法:{}", invocation.toString());

        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameterObject = args[1];

        // 查找参数中包含DataScope类型的参数
        DataScope dataScope = findDataScopeObject(parameterObject);
        if (dataScope == null) {
            return invocation.proceed();
        }

        if (!ObjectUtils.isEmpty(dataScope.getSqlCondition())) {
            // id为执行的mapper方法的全路径名,如com.mapper.UserMapper
            String id = ms.getId();

            // sql语句类型 select、delete、insert、update
            String sqlCommandType = ms.getSqlCommandType().toString();

            // 仅拦截 select 查询
            if (!sqlCommandType.equals(SqlCommandType.SELECT.toString())) {
                return invocation.proceed();
            }

            BoundSql boundSql = ms.getBoundSql(parameterObject);
            String origSql = boundSql.getSql();
            log.info("原始SQL: {}", origSql);

            // 组装新的 sql
            String newSql = String.format("%s%s%s%s", "select * from (", origSql, ") ", dataScope.getSqlCondition());

            // 重新new一个查询语句对象
            BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());

            // 把新的查询放到statement里
            MappedStatement newMs = newMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
            for (ParameterMapping mapping : boundSql.getParameterMappings()) {
                String prop = mapping.getProperty();
                if (boundSql.hasAdditionalParameter(prop)) {
                    newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
                }
            }

            Object[] queryArgs = invocation.getArgs();
            queryArgs[0] = newMs;

            log.info("改写的SQL: {}", newSql);
        }

        Object result = invocation.proceed();

        return handleReslut(result, Arrays.asList(dataScope.getFilterFields()));
    }

    /**
     * 定义一个内部辅助类,作用是包装 SQL
     */
    class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;
        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }

    }

    private MappedStatement newMappedStatement (MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new
                MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    @Override
    public Object plugin(Object target) {
        log.info("plugin方法:{}", target);

        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        }
        return target;

    }

    @Override
    public void setProperties(Properties properties) {
        // 获取属性
        // String value1 = properties.getProperty("prop1");
        log.info("properties方法:{}", properties.toString());
    }

    /**
     * 查找参数是否包括DataScope对象
     *
     * @param parameterObj 参数列表
     * @return DataScope
     */
    private DataScope findDataScopeObject(Object parameterObj) {
        if (parameterObj instanceof DataScope) {
            return (DataScope) parameterObj;
        } else if (parameterObj instanceof Map) {
            for (Object val : ((Map<?, ?>) parameterObj).values()) {
                if (val instanceof DataScope) {
                    return (DataScope) val;
                }
            }
        }
        return null;
    }

    public Object handleReslut(Object returnValue, List<String> filterFields){
        if(returnValue != null && !ObjectUtils.isEmpty(filterFields)){
            if (returnValue instanceof ArrayList<?>){
                List<?> list = (ArrayList<?>) returnValue;
                List<Object> newList  = new ArrayList<Object>();
                if (1 <= list.size()) {
                    for(Object object:list){
                        if (object instanceof Map) {
                            Map map = (Map) object;
                            for (String key : filterFields) {
                                map.remove(key);
                            }
                            newList.add(map);
                        } else {
                            newList.add(decrypt(filterFields, object));
                        }
                    }
                    returnValue = newList;
                }
            } else {
                if (returnValue instanceof Map) {
                    Map map = (Map) returnValue;
                    for (String key : filterFields) {
                        map.remove(key);
                    }
                } else {
                    returnValue = decrypt(filterFields, returnValue);
                }
            }
        }
        return returnValue;
    }

    public static <T> T decrypt(List<String> filterFields, T t) {
        Field[] declaredFields = t.getClass().getDeclaredFields();
        try {
            if (declaredFields != null && declaredFields.length > 0) {
                for (Field field : declaredFields) {
                    if (filterFields.contains(field.getName())) {
                        field.setAccessible(true);
                        field.set(t, null);
                        field.setAccessible(false);
                    }
                }
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

        return t;
    }
}

SalariesMapper.xml

<mapper namespace="com.mk.mapper.SalariesMapper">
    <select id="pageList" resultType="com.mk.entity.Salaries">
        SELECT * from salaries where salary between #{start} and #{end}
    </select>

    <select id="getByEmpNo" resultType="java.util.Map">
        select * from salaries where emp_no = #{empNo} limit 0,1
    </select>
</mapper>

SalariesMapper.java

@Mapper
public interface SalariesMapper extends BaseMapper<Salaries> {

    List<Salaries> pageList(DataScope dataScope, @Param("start") int start,  @Param("end") int end, Page<Salaries> page);

    Map<String, Object> getByEmpNo(DataScope dataScope, @Param("empNo") int empNo);
}

SalariesService.java

@Service
public class SalariesService extends ServiceImpl<SalariesMapper, Salaries> {

    @Autowired
    private SalariesMapper salariesMapper;

    public List<Salaries> getList(){
        Page<Salaries> page = new Page<>(1, 10);
        DataScope dataScope = new DataScope();
        // 设置查询条件
        dataScope.setSqlCondition("s where 1=1 and s.emp_no = '10001'");
        // 将结果集过滤掉salary和toDate字段
        dataScope.setFilterFields(new String[]{"salary", "toDate"});
        return salariesMapper.pageList(dataScope, 60000, 70000, page);

    }

    public Map<String, Object> getByEmpNo() {
        DataScope dataScope = new DataScope();
        // 将结果集过滤掉salary和toDate字段
        dataScope.setFilterFields(new String[]{"salary", "toDate"});
        return salariesMapper.getByEmpNo(dataScope, 10001);
    }
}

启动服务,执行相关操作,sql在执行之前会执行DataScopeInterceptor拦截器中的逻辑,从而改变sql,具体的

相关操作就是将原来的sql语句origSql在外层包装一层过滤条件,如:select * from (origSql) 过滤条件,

此处的过滤条件要封装到DataScope对象中,例如:dataScope.setSqlCondition("s where 1=1 and

s.emp_no = '10001'") , 那么在经过拦截器处理以后要执行的sql语句为

select * from (origSql) s where 1=1 and s.emp_no = '10001', 从而实现数据权限相操作,当然此处的过滤条件只是为了演示效果举的一个例子

而已,在实际开发过程中要根据用户角色等等设置具体的过滤条件。

到此这篇关于Mybatis拦截器实现数据权限的示例代码的文章就介绍到这了,更多相关Mybatis拦截器实现数据权限内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • mybatis拦截器实现通用权限字段添加的方法

    实现效果 日常sql中直接使用权限字段实现权限内数据筛选,无需入参,直接使用,使用形式为: select * from crh_snp.channelinfo where short_code in (${commonEnBranchNo}) 注意事项说明 1.添加插件若使用xml形式mybatis可在配置文件中plugins标签中添加,本项目实际使用的为注解形式mybatis,需要通过SqlSessionFactoryBean代码方式添加或者SqlSessionFactoryBean的xml配

  • Mybatis拦截器实现数据权限的示例代码

    在我们日常开发过程中,通常会涉及到数据权限问题,下面以我们常见的一种场景举例: 一个公司有很多部门,每个人所处的部门和角色也不同,所以数据权限也可能不同,比如超级管理员可以查看某张 表的素有信息,部门领导可以查看该部门下的相关信息,部门普通人员只可以查看个人相关信息,而且由于角色的 不同,各个角色所能查看到的数据库字段也可能不相同,那么此处就涉及到了数据权限相关的问题.那么我们该如 何处理数据权限相关的问题呢?我们提供一种通过Mybatis拦截器实现的方式,下面我们来具体实现一下 pom.xml

  • MyBatis-Plus拦截器实现数据权限控制的示例

    目录 前言背景 上代码(基础版) 进阶版 前言背景 平时开发中遇到根据当前用户的角色,只能查看数据权限范围的数据需求.列表实现方案有两种,一是在开发初期就做好判断赛选,但如果这个需求是中途加的,或不希望每个接口都加一遍,就可以方案二加拦截器的方式.在mybatis执行sql前修改语句,限定where范围. 当然拦截器生效后是全局性的,如何保证只对需要的接口进行拦截和转化,就可以应用注解进行识别 因此具体需要哪些步骤就明确了 创建注解类 创建拦截器实现InnerInterceptor接口,重写查询

  • ASP.NET 通过拦截器记录错误日志的示例代码

    目录 前言 拦截器 代码实战 前言 主要是记录一下实现的错误日志拦截,可以在拦截器里面控制返回的信息,把错误信息处理后返回给请求端. 拦截器 拦截器又称过滤器. asp.net mvc本身是自带3种拦截器:Action拦截器.Result拦截器.Exception拦截器. 应用中常见的拦截器有日志拦截器(Action拦截器)和异常处理拦截器(Exception拦截器). java里spring mvc也常用拦截器来做些非干预业务逻辑的事,诸如实现HandlerInterceptor接口. 拦截器

  • springboot+mybatis-plus基于拦截器实现分表的示例代码

    目录 前言 一.设计思路 二.实现思路 三.代码实现 接口描述 核心组成部分 1.本地线程工具类 2.注解部分 3.拦截器实现 四.测试 后记 前言 最近在工作遇到数据量比较多的情况,单表压力比较大,crud的操作都受到影响,因为某些原因,项目上没有引入sharding-jdbc这款优秀的分表分库组件,所以打算简单写一个基于mybatis拦截器的分表实现 一.设计思路 在现有的业务场景下,主要实现的目标就是表名的替换,需要解决的问题有 如何从执行的方法中,获取对应的sql并解析获取当前执行的表名

  • 详解Mybatis拦截器安全加解密MySQL数据实战

    需求背景 公司为了通过一些金融安全指标(政策问题)和防止数据泄漏,需要对用户敏感数据进行加密,所以在公司项目中所有存储了用户信息的数据库都需要进行数据加密改造.包括Mysql.redis.mongodb.es.HBase等. 因为在项目中是使用springboot+mybatis方式连接数据库进行增删改查,并且项目是中途改造数据.所以为了不影响正常业务,打算这次改动尽量不侵入到业务代码,加上mybatis开放的各种拦截器接口,所以就以此进行改造数据. 本篇文章讲述如何在现有项目中尽量不侵入业务方

  • MyBatis拦截器的原理与使用

    目录 二.拦截器注册的三种方式 1.XML注册 2.配置类注册 3.注解方式 三.ParameterHandler参数改写-修改时间和修改人统一插入 四.通过StatementHandler改写SQL 一.拦截对象和接口实现示例 MyBatis拦截器的作用是在于Dao到DB中间进行额外的处理.大部分情况下通过mybatis的xml配置sql都可以达到想要的DB操作效果,然而存在一些类似或者相同的查询条件或者查询要求,这些可以通过拦截器的实现可以提升开发效率,比如:分页.插入和更新时间/人.数据权

  • MyBatis拦截器的实现原理

    目录 前言 1.使用方法 2.MyBatis对象的创建 3.代理对象的创建 3.1 拦截器的获取 3.2 代理对象的创建 4. 拦截器的执行过程 5. 拦截器的执行顺序 前言 Mybatis拦截器并不是每个对象里面的方法都可以被拦截的.Mybatis拦截器只能拦截Executor.StatementHandler.ParameterHandler.ResultSetHandler四个类里面的方法,这四个对象在创建的时候才会创建代理. 用途:实际工作中,可以使用Mybatis拦截器来做一些SQL权

  • MyBatis拦截器实现分页功能的实现方法

    MyBatis拦截器实现分页功能的实现方法 前言: 首先说下实现原理.使用拦截器拦截原始的sql,然后加上分页查询的关键字和属性,拼装成新的sql语句再交给mybatis去执行. 除了业务代码之外,需要写的东西不多,提几个关键的: 1.分页对象Page类.给该对象设置一个当前页数(前端给).总记录数(拦截器内赋值)2个参数,他就能帮你计算出分页sql语句用的2个参数. /** * 分页对应的实体类 */ public class Page { /** * 总条数 */ private int t

  • Mybatis拦截器实现分页

    最终dao层结果: public interface ModelMapper { Page<Model> pageByConditions(RowBounds rowBounds, Model record); } 接下来一步一步来实现分页. 一.创建Page对象: public class Page<T> extends PageList<T> { private int pageNo = 1;// 页码,默认是第一页 private int pageSize = 1

随机推荐