在我们日常开发过程中,通常会涉及到数据权限问题,下面以我们常见的一种场景举例:
一个公司有很多部门,每个人所处的部门和角色也不同,所以数据权限也可能不同,比如超级管理员可以查看某张
表的素有信息,部门领导可以查看该部门下的相关信息,部门普通人员只可以查看个人相关信息,而且由于角色的
不同,各个角色所能查看到的数据库字段也可能不相同,那么此处就涉及到了数据权限相关的问题。那么我们该如
何处理数据权限相关的问题呢?我们提供一种通过Mybatis拦截器实现的方式,下面我们来具体实现一下
pom.xml依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
< 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文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
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
1 2 3 4 5 6 7 8 9 10 |
@Data public class DataScope {
// sql过滤条件 String sqlCondition;
// 需要过滤的结果字段 String[] filterFields;
} |
MybatisPlusConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
@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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
@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
1 2 3 4 5 6 7 8 9 |
< 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
1 2 3 4 5 6 7 |
@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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@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拦截器实现数据权限内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
原文链接:https://blog.csdn.net/chaojunma/article/details/123400521
查看更多关于Mybatis拦截器实现数据权限的示例代码的详细内容...