Springboot+mybatis-plus+注解實(shí)現(xiàn)數(shù)據(jù)權(quán)限隔離
1.創(chuàng)建注解
當(dāng)此注解打在類上,不需要傳參,該類下所有查詢接口開啟數(shù)據(jù)隔離;打在方法上默認(rèn)開啟數(shù)據(jù)隔離,傳參為false則該方法關(guān)閉驗(yàn)證
/** * 數(shù)據(jù)權(quán)限驗(yàn)證注解 * @author xiaohua * @date 2021/6/23 */ @Documented @Target({METHOD, ANNOTATION_TYPE, TYPE}) @Retention(RUNTIME) public @interface DataPermission { /** * 是否要進(jìn)行數(shù)據(jù)權(quán)限隔離 */ boolean isPermi() default true; }
2. 具體實(shí)現(xiàn)
@Component @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) public class DataPermissionInterceptor implements Interceptor { private static final Logger logger = LoggerFactory.getLogger(DataPermissionInterceptor.class); @Autowired private TokenService tokenService; //掃描的包路徑(根據(jù)自己的項(xiàng)目路徑來),這里是取的配置里的包路徑 @Value("${permission.package-path}") private String packagePath; private final static String DEPT_ID = "dept_id"; private final static String USER_ID = "create_user"; private static List<String> classNames; @Override public Object intercept(Invocation invocation) throws Throwable { try { LoginInfo user = tokenService.getLoginInfo(); if (user == null){ return invocation.proceed(); } List<Long> deptIds = (List<Long>) Convert.toList(user.getDataScope()); if (deptIds == null){ deptIds = new ArrayList<>(); } //反射掃包會(huì)比較慢,這里做了個(gè)懶加載 if (classNames == null) { synchronized (LazyInit.class){ if (classNames == null){ //掃描指定包路徑下所有包含指定注解的類 Set<Class<?>> classSet = ClassUtil.scanPackageByAnnotation(packagePath, DataPermission.class); if (classSet == null && classSet.size() == 0){ classNames = new ArrayList<>(); } else { //取得類全名 classNames = classSet.stream().map(Class::getName).collect(Collectors.toList()); } } } } // 拿到mybatis的一些對(duì)象 StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget()); MetaObject metaObject = SystemMetaObject.forObject(statementHandler); MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); // mappedStatement.getId()為執(zhí)行的mapper方法的全路徑名,newId為執(zhí)行的mapper方法的類全名 String newId = mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf(".")); // 如果不是指定的方法,直接結(jié)束攔截 if (!classNames.contains(newId)) { return invocation.proceed(); } String newName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") + 1, mappedStatement.getId().length()); //是否開啟數(shù)據(jù)權(quán)限 boolean isPermi = true; Class<?> clazz = Class.forName(newId); //遍歷方法 for (Method method : clazz.getDeclaredMethods()) { //方法是否含有DataPermission注解,如果含有注解則將數(shù)據(jù)結(jié)果過濾 if (method.isAnnotationPresent(DataPermission.class) && newName.equals(method.getName())) { DataPermission dataPermission = method.getAnnotation(DataPermission.class); if (dataPermission != null) { //不驗(yàn)證 if (!dataPermission.isPermi()) { isPermi = false; } else { //開啟驗(yàn)證 isPermi = true; } } } } if (isPermi){ // 獲取到原始sql語句 String sql = statementHandler.getBoundSql().getSql(); // 解析并返回新的SQL語句,只處理查詢sql if (mappedStatement.getSqlCommandType().toString().equals("SELECT")) { // String newSql = getNewSql(sql,deptIds,user.getUserId()); sql = getSql(sql,deptIds,user.getUserId()); } // 修改sql metaObject.setValue("delegate.boundSql.sql", sql); } return invocation.proceed(); } catch (Exception e){ logger.error("數(shù)據(jù)權(quán)限隔離異常:", e); return invocation.proceed(); } } /** * 解析SQL語句,并返回新的SQL語句 * 注意,該方法使用了JSqlParser來操作SQL,該依賴包Mybatis-plus已經(jīng)集成了。如果要單獨(dú)使用,請(qǐng)先自行導(dǎo)入依賴 * * @param sql 原SQL * @return 新SQL */ private String getSql(String sql,List<Long> deptIds,Long userId) { try { String condition = ""; String permissionSql = "("; if (deptIds.size() > 0){ for (Long deptId : deptIds) { if ("(".equals(permissionSql)){ permissionSql = permissionSql + deptId; } else { permissionSql = permissionSql + "," + deptId; } } permissionSql = permissionSql + ")"; // 修改原語句 condition = DEPT_ID +" in " + permissionSql; } else { condition = USER_ID +" = " + userId; } if (StringUtils.isBlank(condition)){ return sql; } Select select = (Select)CCJSqlParserUtil.parse(sql); PlainSelect plainSelect = (PlainSelect)select.getSelectBody(); //取得原SQL的where條件 final Expression expression = plainSelect.getWhere(); //增加新的where條件 final Expression envCondition = CCJSqlParserUtil.parseCondExpression(condition); if (expression == null) { plainSelect.setWhere(envCondition); } else { AndExpression andExpression = new AndExpression(expression, envCondition); plainSelect.setWhere(andExpression); } return plainSelect.toString(); } catch (JSQLParserException e) { logger.error("解析原SQL并構(gòu)建新SQL錯(cuò)誤:" + e); return sql; } }
到此這篇關(guān)于Springboot+mybatis-plus+注解實(shí)現(xiàn)數(shù)據(jù)權(quán)限隔離的文章就介紹到這了,更多相關(guān)Springboot+mybatis-plus+注解實(shí)現(xiàn)數(shù)據(jù)權(quán)限隔離內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于web項(xiàng)目讀取classpath下面文件的心得分享
這篇文章主要介紹了關(guān)于web項(xiàng)目讀取classpath下面文件的心得,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07Java實(shí)現(xiàn)視頻初步壓縮和解壓的代碼示例
從攝像頭讀取每一幀的圖片,用一些簡(jiǎn)單的方法將多張圖片信息壓縮到一份文件中(自定義的視頻文件),自定義解碼器讀取視頻文件,并將每幀圖片展示成視頻,本文主要介紹了Java實(shí)現(xiàn)視頻初步壓縮和解壓,需要的朋友可以參考下2023-10-10Java利用Sping框架編寫RPC遠(yuǎn)程過程調(diào)用服務(wù)的教程
這篇文章主要介紹了Java利用Sping框架編寫RPC遠(yuǎn)程過程調(diào)用服務(wù)的教程,包括項(xiàng)目管理工具M(jìn)aven的搭配使用方法,需要的朋友可以參考下2016-06-06