软件编程
位置:首页>> 软件编程>> java编程>> Mybatis拦 截 器实现数据权限的示例代码

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

作者:chaojunma  发布时间:2023-11-19 20:15:04 

标签: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', 从而实现数据权限相操作,当然此处的过滤条件只是为了演示效果举的一个例子

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

来源:https://blog.csdn.net/chaojunma/article/details/123400521

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com