MyBatis-Plus攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制的方法
一、介紹
上篇文章介紹的MyBatis Plus 插件實(shí)際上就是用攔截器實(shí)現(xiàn)的,MyBatis Plus攔截器對MyBatis的攔截器進(jìn)行了包裝處理,操作起來更加方便
二、自定義攔截器
2.1、InnerInterceptor
MyBatis Plus提供的InnerInterceptor接口提供了如下方法,主要包括:在查詢之前執(zhí)行,在更新之前執(zhí)行,在SQL準(zhǔn)備之前執(zhí)行
2.2、編寫簡易攔截器
package com.xx.config; import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; import com.baomidou.mybatisplus.core.toolkit.PluginUtils; import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport; import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; import lombok.extern.slf4j.Slf4j; import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.ItemsList; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.delete.Delete; import net.sf.jsqlparser.statement.insert.Insert; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.select.SelectBody; import net.sf.jsqlparser.statement.update.Update; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.MappedStatement; import org.springframework.stereotype.Component; import java.sql.Connection; /** * @author aqi * @date 2023/5/17 15:07 */ @Slf4j @Component public class TestInterceptor extends JsqlParserSupport implements InnerInterceptor { @Override public void beforePrepare(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構(gòu)建出新的條件,并將新的條件和之前的條件拼接在一起 */ @Override protected void processSelect(Select select, int index, String sql, Object obj) { // 解析SQL SelectBody selectBody = select.getSelectBody(); PlainSelect plainSelect = (PlainSelect) selectBody; // 構(gòu)建eq對象 EqualsTo equalsTo = new EqualsTo(new Column("name"), new StringValue("tom")); // 將原來的條件和新構(gòu)建的條件合在一起 AndExpression andExpression = new AndExpression(plainSelect.getWhere(), equalsTo); // 重新封裝where條件 plainSelect.setWhere(andExpression); } @Override protected void processInsert(Insert insert, int index, String sql, Object obj) { insert.getColumns().add(new Column("name")); ((ExpressionList) insert.getItemsList()).getExpressions().add(new StringValue("tom")); } @Override protected void processUpdate(Update update, int index, String sql, Object obj) { update.addUpdateSet(new Column("name"), new StringValue("tom")); } @Override protected void processDelete(Delete delete, int index, String sql, Object obj) { // 刪除新增條件和查詢一樣,不做演示 } }
2.3、將攔截器添加到MyBatis Plus攔截器中
package com.xx.config; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author aqi * @date 2023/5/15 14:05 */ @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { // 初始化Mybatis Plus攔截器 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new TestInterceptor()); return interceptor; } }
2.4、編寫測試用例
@Test void save() { AirlinesInfo airlinesInfo = new AirlinesInfo(); airlinesInfo.setInfo("remark"); airlinesInfoService.save(airlinesInfo); } @Test void update() { AirlinesInfo airlinesInfo = new AirlinesInfo(); airlinesInfo.setId(1L); airlinesInfo.setInfo("remark, remark"); airlinesInfoService.updateById(airlinesInfo); } @Test void select() { airlinesInfoService.list(); }
2.5、執(zhí)行結(jié)果
三、自定義攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制
3.1、編寫攔截器
package com.xx.config; import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; import com.baomidou.mybatisplus.core.toolkit.PluginUtils; import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport; import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; import com.xx.entity.Permission; import com.xx.utils.ExpressionUtils; import com.xx.utils.UserUtils; import lombok.extern.slf4j.Slf4j; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.select.SelectBody; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.springframework.stereotype.Component; import java.sql.Connection; /** * @author xiaxing */ @Slf4j @Component public class DataScopeInterceptor extends JsqlParserSupport implements InnerInterceptor { @Override public void beforePrepare(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)); } } /** * 查詢 */ @Override protected void processSelect(Select select, int index, String sql, Object obj) { SelectBody selectBody = select.getSelectBody(); PlainSelect plainSelect = (PlainSelect) selectBody; // 獲取表名/別名(如果是關(guān)聯(lián)查詢是取第一個(gè)join左側(cè)的表名/別名) String tableName = ExpressionUtils.getTableName(plainSelect); // 構(gòu)建用戶權(quán)限控制條件 Expression userPermissionExpression = this.buildUserPermissionSql(tableName); if (null != userPermissionExpression) { // 將sql原本就有得where條件和新構(gòu)建出來的條件拼接起來 plainSelect.setWhere(ExpressionUtils.appendExpression(plainSelect.getWhere(), userPermissionExpression)); } } /** * 構(gòu)建用戶權(quán)限控制條件 * @param tableName 表名/別名(join查詢左側(cè)表名) */ private Expression buildUserPermissionSql(String tableName) { // 獲取當(dāng)前用戶信息(這里的數(shù)據(jù)都是模擬的,實(shí)際上可能得從緩存或者session中獲取) Permission permission = UserUtils.getUserPermission(); return null != permission ? ExpressionUtils.buildInSql(tableName + "." + permission.getField(), permission.getValue()) : null; } }
3.2、編寫構(gòu)建SQL工具類
package com.xx.utils; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.ItemsList; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.select.PlainSelect; import java.util.Set; import java.util.stream.Collectors; /** * @author aqi * @date 2023/5/17 10:16 * @describe JSqlParser工具類,用于通過API的方式操作SQL語句 */ public class ExpressionUtils { /** * 構(gòu)建in sql * @param columnName 字段名稱 * @param params 字段值 * @return InExpression */ public static InExpression buildInSql(String columnName, Set<String> params) { // 把集合轉(zhuǎn)變?yōu)镴SQLParser需要的元素列表 ItemsList itemsList = new ExpressionList(params.stream().map(StringValue::new).collect(Collectors.toList())); // 創(chuàng)建IN表達(dá)式對象,傳入列名及IN范圍列表 return new InExpression(new Column(columnName), itemsList); } /** * 構(gòu)建eq sql * @param columnName 字段名稱 * @param value 字段值 * @return EqualsTo */ public static EqualsTo buildEq(String columnName, String value) { return new EqualsTo(new Column(columnName), new StringValue(value)); } /** * 獲取表名/別名 * @param plainSelect plainSelect * @return 表名/別名 */ public static String getTableName(PlainSelect plainSelect) { // 獲取別名 Table table= (Table) plainSelect.getFromItem(); Alias alias = table.getAlias(); return null == alias ? table.getName() : alias.getName(); } /** * 將2個(gè)where條件拼接到一起 * @param where 條件 * @param appendExpression 待拼接條件 * @return Expression */ public static Expression appendExpression(Expression where, Expression appendExpression) { return null == where ? appendExpression : new AndExpression(where, appendExpression); } }
3.3、模擬用戶信息工具類
package com.xx.utils; import com.xx.config.Globle; import com.xx.entity.Permission; import com.xx.entity.User; import lombok.extern.slf4j.Slf4j; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.*; /** * @author aqi * @date 2023/5/17 14:20 */ @Slf4j public class UserUtils { public static User currentUser; static { // 構(gòu)建測試數(shù)據(jù) List<Permission> permissionList = new ArrayList<>(); // demo/test接口權(quán)限 Permission permission = new Permission(); permission.setField("id"); permission.setUri("/demo/test"); Set<String> set = new HashSet<>(); set.add("1"); set.add("2"); set.add("3"); permission.setValue(set); permissionList.add(permission); // demo/test1接口權(quán)限 Permission permission1 = new Permission(); permission1.setField("id"); permission1.setUri("/demo/test1"); Set<String> set1 = new HashSet<>(); set1.add("4"); set1.add("5"); set1.add("6"); permission1.setValue(set1); permissionList.add(permission1); User user = new User(); user.setPermissionList(permissionList); user.setTenantId("1"); currentUser = user; } public static Permission getUserPermission() { 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 */ private static String getUri() { // 獲取此次請求的uri String 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藍(lán)機(jī)器中
package com.xx.config; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author aqi * @date 2023/5/15 14:05 */ @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { // 初始化Mybatis Plus攔截器 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new DataScopeInterceptor()); return interceptor; } }
3.5、測試
package com.xx.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.github.pagehelper.PageHelper; import com.xx.entity.AirlinesInfo; import com.xx.service.AirlinesInfoService; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * @author aqi * @date 2023/5/18 11:01 */ @Slf4j @RestController @RequestMapping("/demo") public class DemoController { @Resource private AirlinesInfoService airlinesInfoService; @GetMapping("/test") public void test() { log.info("進(jìn)入test接口,測試權(quán)限控制在基礎(chǔ)的sql語句是否能生效"); airlinesInfoService.list(); // 執(zhí)行結(jié)果:(SELECT * FROM airlines_info WHERE state = 0 AND airlines_info.id IN ('1', '2', '3')) } @GetMapping("/test1") public void test1() { log.info("進(jìn)入test1接口,測試權(quán)限控制在使用MyBatis Plus 的分頁插件之后能否生效"); Page<AirlinesInfo> page = new Page<>(1, 5); airlinesInfoService.page(page, new QueryWrapper<AirlinesInfo>().eq("name", "tom")); // 執(zhí)行結(jié)果:(SELECT * FROM airlines_info WHERE state = 0 AND (name = ?) AND airlines_info.id IN ('4', '5', '6') LIMIT ?) } @GetMapping("/test2") public void test2() { log.info("進(jìn)入test2接口,測試權(quán)限控制在使用PageHelper之后能否生效"); PageHelper.startPage(1, 5); airlinesInfoService.list(new LambdaQueryWrapper<AirlinesInfo>().eq(AirlinesInfo::getName, "tom")); // 執(zhí)行結(jié)果:(SELECT * FROM airlines_info WHERE state = 0 AND (name = ?) AND airlines_info.id IN ('7', '8', '9') LIMIT ?) } @GetMapping("/test3") public void test3() { log.info("進(jìn)入test3接口,測試權(quán)限控制在使用自定義復(fù)雜關(guān)聯(lián)查詢之后能否生效"); 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) // 執(zhí)行結(jié)果:(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") public void test4() { log.info("進(jìn)入test4接口,測試該接口沒有設(shè)計(jì)權(quán)限限制是否可以不生效"); airlinesInfoService.list(); // 執(zhí)行結(jié)果:(SELECT * FROM airlines_info WHERE state = 0) } }
四、結(jié)論
通過測試可以看出不論在什么情況下都可以正常的對權(quán)限進(jìn)行控制
注意:上面部分代碼使用的是MyBatis Plus 3.5.3版本,并且使用的JSqlParser部分API已經(jīng)不推薦使用,但是我沒有找到最新的API應(yīng)該怎么寫
到此這篇關(guān)于MyBatis-Plus攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制的文章就介紹到這了,更多相關(guān)MyBatis Plus攔截器數(shù)據(jù)權(quán)限控制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- MybatisPlus攔截器如何實(shí)現(xiàn)數(shù)據(jù)表分表
- mybatis-plus配置攔截器實(shí)現(xiàn)sql完整打印的代碼設(shè)計(jì)
- Mybatis-plus通過添加攔截器實(shí)現(xiàn)簡單數(shù)據(jù)權(quán)限
- MybatisPlusInterceptor實(shí)現(xiàn)sql攔截器超詳細(xì)教程
- MyBatis-Plus攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制的示例
- mybatis-plus 攔截器敏感字段加解密的實(shí)現(xiàn)
- MyBatis-Plus攔截器對敏感數(shù)據(jù)實(shí)現(xiàn)加密
- mybatis-plus攔截器、字段填充器、類型處理器、表名替換、SqlInjector(聯(lián)合主鍵處理)
- mybatisplus 的SQL攔截器實(shí)現(xiàn)關(guān)聯(lián)查詢功能
- Mybatis Plus 3.4.0分頁攔截器的用法小結(jié)
相關(guān)文章
idea啟動(dòng)多個(gè)服務(wù)不顯示Services或者RunDashboard窗口的處理方法
這篇文章主要介紹了idea啟動(dòng)多個(gè)服務(wù)不顯示Services或者RunDashboard窗口,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03淺談Java代碼的 微信長鏈轉(zhuǎn)短鏈接口使用 post 請求封裝Json(實(shí)例)
下面小編就為大家?guī)硪黄獪\談Java代碼的 微信長鏈轉(zhuǎn)短鏈接口使用 post 請求封裝Json(實(shí)例)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07JAVA-4NIO之Channel之間的數(shù)據(jù)傳輸方法
下面小編就為大家?guī)硪黄狫AVA-4NIO之Channel之間的數(shù)據(jù)傳輸方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06SpringBoot前后端交互、全局異常處理之后端異常信息拋到前端顯示彈窗
Spring Boot是一個(gè)用于構(gòu)建獨(dú)立的、基于生產(chǎn)級別的Spring應(yīng)用程序的框架,下面這篇文章主要給大家介紹了關(guān)于SpringBoot前后端交互、全局異常處理之后端異常信息拋到前端顯示彈窗的相關(guān)資料,需要的朋友可以參考下2024-08-08Java源碼解析阻塞隊(duì)列ArrayBlockingQueue功能簡介
今天小編就為大家分享一篇關(guān)于Java源碼解析阻塞隊(duì)列ArrayBlockingQueue功能簡介,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-01-01SpringMVC中使用@PathVariable綁定路由中的數(shù)組的方法
這篇文章主要介紹了SpringMVC中使用@PathVariable綁定路由中的數(shù)組的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07使用自定義注解和@Aspect實(shí)現(xiàn)責(zé)任鏈模式的組件增強(qiáng)的詳細(xì)代碼
責(zé)任鏈模式是一種行為設(shè)計(jì)模式,其作用是將請求的發(fā)送者和接收者解耦,從而可以靈活地組織和處理請求,本文講給大家介紹如何使用自定義注解和@Aspect實(shí)現(xiàn)責(zé)任鏈模式的組件增強(qiáng),文中有詳細(xì)的代碼示例供大家參考,感興趣的同學(xué)可以借鑒一下2023-05-05