Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限詳解
前言
在我們?nèi)粘i_發(fā)過程中,通常會涉及到數(shù)據(jù)權(quán)限問題,下面以我們常見的一種場景舉例:
一個公司有很多部門,每個人所處的部門和角色也不同,所以數(shù)據(jù)權(quán)限也可能不同,比如超級管理員可以查看某張表的素有信息,部門領(lǐng)導(dǎo)可以查看該部門下的相關(guān)信息,部門普通人員只可以查看個人相關(guān)信息,而且由于角色的不同,各個角色所能查看到的數(shù)據(jù)庫字段也可能不相同,那么此處就涉及到了數(shù)據(jù)權(quán)限相關(guān)的問題。
那么我們該如何處理數(shù)據(jù)權(quán)限相關(guān)的問題呢?
我們提供一種通過Mybatis攔截器實(shí)現(xiàn)的方式,下面我們來具體實(shí)現(xiàn)一下
具體實(shí)現(xiàn)
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: # 驗(yàn)證連接是否有效。此參數(shù)必須設(shè)置為非空字符串,下面三項(xiàng)設(shè)置成true才能生效 validation-query: SELECT 1 # 連接是否被空閑連接回收器(如果有)進(jìn)行檢驗(yàn). 如果檢測失敗, 則連接將被從池中去除 test-while-idle: true # 是否在從池中取出連接前進(jìn)行檢驗(yàn), 如果檢驗(yàn)失敗, 則從池中去除連接并嘗試取出另一個 test-on-borrow: true # 是否在歸還到池中前進(jìn)行檢驗(yàn) 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
代碼實(shí)現(xiàn)
DataScode.java
@Data public class DataScope { // sql過濾條件 String sqlCondition; // 需要過濾的結(jié)果字段 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("執(zhí)行intercept方法:{}", invocation.toString()); Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object parameterObject = args[1]; // 查找參數(shù)中包含DataScope類型的參數(shù) DataScope dataScope = findDataScopeObject(parameterObject); if (dataScope == null) { return invocation.proceed(); } if (!ObjectUtils.isEmpty(dataScope.getSqlCondition())) { // id為執(zhí)行的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())); } /** * 定義一個內(nèi)部輔助類,作用是包裝 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()); } /** * 查找參數(shù)是否包括DataScope對象 * * @param parameterObj 參數(shù)列表 * @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(); // 設(shè)置查詢條件 dataScope.setSqlCondition("s where 1=1 and s.emp_no = '10001'"); // 將結(jié)果集過濾掉salary和toDate字段 dataScope.setFilterFields(new String[]{"salary", "toDate"}); return salariesMapper.pageList(dataScope, 60000, 70000, page); } public Map<String, Object> getByEmpNo() { DataScope dataScope = new DataScope(); // 將結(jié)果集過濾掉salary和toDate字段 dataScope.setFilterFields(new String[]{"salary", "toDate"}); return salariesMapper.getByEmpNo(dataScope, 10001); } }
啟動服務(wù),執(zhí)行相關(guān)操作,sql在執(zhí)行之前會執(zhí)行DataScopeInterceptor攔截器中的邏輯,從而改變sql,具體的相關(guān)操作就是將原來的sql語句origSql在外層包裝一層過濾條件,如:select * from (origSql) 過濾條件,此處的過濾條件要封裝到DataScope對象中
例如:
dataScope.setSqlCondition("s where 1=1 and s.emp_no = '10001'")
那么在經(jīng)過攔截器處理以后要執(zhí)行的sql語句為
select * from (origSql) s where 1=1 and s.emp_no = '10001'
從而實(shí)現(xiàn)數(shù)據(jù)權(quán)限相操作,當(dāng)然此處的過濾條件只是為了演示效果舉的一個例子
而已,在實(shí)際開發(fā)過程中要根據(jù)用戶角色等等設(shè)置具體的過濾條件。
到此這篇關(guān)于Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限詳解的文章就介紹到這了,更多相關(guān)Mybatis攔截器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)庫數(shù)據(jù)權(quán)限隔離方式
- MyBatis-Plus數(shù)據(jù)權(quán)限插件的簡單使用
- MybatisPlus實(shí)現(xiàn)數(shù)據(jù)權(quán)限隔離的示例詳解
- MyBatis-Plus攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制的方法
- Mybatis-plus通過添加攔截器實(shí)現(xiàn)簡單數(shù)據(jù)權(quán)限
- mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限項(xiàng)目實(shí)踐
- mybatis-plus數(shù)據(jù)權(quán)限實(shí)現(xiàn)代碼
- MyBatis-Plus攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制的示例
- Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限的示例代碼
- Mybatis自定義攔截器實(shí)現(xiàn)權(quán)限功能
相關(guān)文章
java使用XSSFWorkbook實(shí)現(xiàn)讀寫Excel
這篇文章主要為大家詳細(xì)介紹了java如何通過使用XSSFWorkbook實(shí)現(xiàn)讀寫Excel功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-04-04springboot?maven?打包插件介紹及注意事項(xiàng)說明
這篇文章主要介紹了springboot?maven?打包插件介紹及注意事項(xiàng)說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12Java線程之守護(hù)線程(Daemon)用法實(shí)例
這篇文章主要介紹了Java線程之守護(hù)線程(Daemon)用法,較為詳細(xì)的分析了守護(hù)線程的功能與實(shí)現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-07-07Java中EnumMap和EnumSet枚舉操作類的簡單使用詳解
這篇文章主要介紹了Java中EnumMap和EnumSet枚舉操作類的簡單使用詳解,EnumMap是Map接口的一種實(shí)現(xiàn),專門用于枚舉類型的鍵,所有枚舉的鍵必須來自同一個枚舉?EnumMap不允許鍵為空,允許值為空,需要的朋友可以參考下2023-11-11Springboot+Shiro+Mybatis+mysql實(shí)現(xiàn)權(quán)限安全認(rèn)證的示例代碼
Shiro是Apache?的一個強(qiáng)大且易用的Java安全框架,執(zhí)行身份驗(yàn)證、授權(quán)、密碼學(xué)和會話管理,Shiro?主要分為兩個部分就是認(rèn)證和授權(quán)兩部分,這篇文章主要介紹了Springboot+Shiro+Mybatis+mysql實(shí)現(xiàn)權(quán)限安全認(rèn)證的示例代碼,需要的朋友可以參考下2024-07-07SpringBoot整合jasypt實(shí)現(xiàn)數(shù)據(jù)加密的步驟
聽說過jasypt嗎?它可是一個超級流行的Java庫哦,提供了簡單又高效的加密和解密接口,整合jasypt后,我們的SpringBoot應(yīng)用就能輕松處理敏感數(shù)據(jù)的加密和解密,而不必為復(fù)雜的加密算法頭疼啦,下面給大家介紹SpringBoot整合jasypt實(shí)現(xiàn)數(shù)據(jù)加密的步驟,感興趣的朋友一起看看吧2025-04-04