mybatis拦截器实现权限管理

article/2025/10/1 7:55:19

框架设计

基于Spring Security+JWT技术实现登录认证和访问授权,基于Mybatis 拦截器实现数据权限的控制。由于已有前文介绍Spring Security如有兴趣可以查看,在此将重点于数据权限的实现及菜单等表结构的设计。

Mybatis 拦截器

介绍Mybatis拦截器之前先来了解一下Mybatis工作流程。

工作流程
  • Configuration装载配置文件Mapper.xml。

  • 通过装载得配置文件Configuration构建SqlSessionFactory。

  • 通过SqlSessionFactory工厂创建SqlSession会话。

  • SqlSession持有Configuration、Executor对象执行被映射得Sql语句。

  • Executor真正执行Sql的核心接口。

  • StatementHandler封装了JDBC Statement类的相关操作,真正与数据打有交道的接口。

  • ResultSetHandler封装查询结果集,将StatementHandler执行结果输出到pojo。

拦截器

MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢?

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

  1. ParameterHandler (getParameterObject, setParameters)

  1. ResultSetHandler (handleResultSets, handleOutputParameters)

  1. StatementHandler (prepare, parameterize, batch, update, query)

我们看到了可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法等。

总体概括为:

  1. 拦截执行器的方法

  1. 拦截参数的处理

  1. 拦截结果集的处理

  1. 拦截Sql语法构建的处理

分页插件PageHelper其实就是利用Mybatis拦截器实现得,我们这也是类似,通过Mybatis拦截器拦截执行器得方法对将要执行的sql语句进行拦截解析成Sql表达式,在根据认证用户所拥有的数据权限进行拼接过滤条件达到控制的效果。整体思路并不复杂,难点在于如何将用户所拥有的资源传递到拦截器中,拦截器如何解析Sql,以及Sql的重新组装。下面将贴出实现的核心代码。

核心代码实现
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
@Component
public class DataCountInterceptor implements Interceptor {private static final ReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory();private static EqualsTo NO_Match = new EqualsTo();private static EqualsTo Match = new EqualsTo();static {NO_Match.setLeftExpression(new Column("1"));NO_Match.setRightExpression(new LongValue(0));Match.setLeftExpression(new Column("1"));Match.setRightExpression(new LongValue(1));}@Overridepublic Object intercept(Invocation invocation) throws Throwable {try {Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement) args[0];Object parameter = args[1];RowBounds rowBounds = (RowBounds) args[2];ResultHandler resultHandler = (ResultHandler) args[3];Executor executor = (Executor) invocation.getTarget();CacheKey cacheKey;BoundSql boundSql;//由于逻辑关系,只会进入一次if (args.length == 4) {//4 个参数时boundSql = ms.getBoundSql(parameter);cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);} else {//6 个参数时cacheKey = (CacheKey) args[4];boundSql = (BoundSql) args[5];}//TODO 自己要进行的各种处理String id = ms.getId();id = id.substring(0, id.lastIndexOf('.'));MybatisDataAuthority localDataAuthority = DataPermissionHelper.getLocalDataAuthority();// 超级管理员放行if (localDataAuthority != null && localDataAuthority.getSuperAdmin() != 1) {String operatorAlias = Strings.isNotBlank(localDataAuthority.getOperatorAlias()) ? localDataAuthority.getOperatorAlias() : "operator";String gameAlias = Strings.isNotBlank(localDataAuthority.getGameAlias()) ? localDataAuthority.getGameAlias() : "game_code";String mediaAlias = Strings.isNotBlank(localDataAuthority.getMediaAlias()) ? localDataAuthority.getMediaAlias() : "media_code";String sql = boundSql.getSql();Statement sm = CCJSqlParserUtil.parse(sql);Select select = (Select) sm;assert select != null;SelectBody selectBody = select.getSelectBody();PlainSelect plainSelect = (PlainSelect) selectBody;// 设置whereExpression expression = Match;if (localDataAuthority.isHasOperator() && !localDataAuthority.isAllOperator()) {if (CollectionUtils.isEmpty(localDataAuthority.getOperators())) {expression = NO_Match;} else {List<Expression> collect = new ArrayList<>(16);for (Integer userId : localDataAuthority.getOperators()) {collect.add(new StringValue("" + userId + ""));}ExpressionList operatorList = new ExpressionList(collect);expression = new InExpression(new Column(operatorAlias), operatorList);}}if (localDataAuthority.isHasGame() && localDataAuthority.getAllGame() != 1) {Expression gameCodeExpression;if (CollectionUtils.isEmpty(localDataAuthority.getGames())) {gameCodeExpression = NO_Match;} else {List<Expression> collect = new ArrayList<>(16);for (String gameCode : localDataAuthority.getGames()) {collect.add(new StringValue("" + gameCode + ""));}ExpressionList gameCodeList = new ExpressionList(collect);gameCodeExpression = new InExpression(new Column(gameAlias), gameCodeList);}expression = new AndExpression(expression, gameCodeExpression);}if (localDataAuthority.isHasMedia() && localDataAuthority.getAllMedia() != 1) {Expression mediaCodeExpression;if (CollectionUtils.isEmpty(localDataAuthority.getMediaCodes())) {mediaCodeExpression = NO_Match;} else {List<Expression> collect = new ArrayList<>(16);for (String mediaCode : localDataAuthority.getMediaCodes()) {collect.add(new StringValue("" + mediaCode + ""));}ExpressionList mediaCodeList = new ExpressionList(collect);mediaCodeExpression = new InExpression(new Column(mediaAlias), mediaCodeList);}expression = new AndExpression(expression, mediaCodeExpression);}//代理人处理if (!CollectionUtils.isEmpty(localDataAuthority.getAgentIds())) {List<Expression> collect = new ArrayList<>(16);for (Integer agentId : localDataAuthority.getAgentIds()) {collect.add(new StringValue("" + agentId + ""));}ExpressionList agentIds = new ExpressionList(collect);InExpression inExpression = new InExpression(new Column("id"), agentIds);expression = new OrExpression(expression, inExpression);}CustomMultiExpression customMultiExpression = new CustomMultiExpression(Arrays.asList(expression));if (plainSelect.getWhere() != null) {plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), customMultiExpression));} else {plainSelect.setWhere(customMultiExpression);}Field additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");additionalParametersField.setAccessible(true);Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), plainSelect.toString(), boundSql.getParameterMappings(), parameter);//设置动态参数for (String key : additionalParameters.keySet()) {pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));}return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, pageBoundSql);} else {return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);}} finally {DataPermissionHelper.clearDataPermission();}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}}

由上代码可以看到,我们定义了一个拦截器,把将要执行Executor的sql语句进行拦截,通过线程上下文拿到此前封装好的数据形式MybatisDataAuthority,在由Jsqlparser解析器将一段完整的Sql解析成方便拼装的Sql表达式,最后将此sql输出由执行器执行,达到控制的效果。

public class DataPermissionHelper {private static ThreadLocal<MybatisDataAuthority> localDataPermission = new ThreadLocal<>();/*** 获取 Page 参数** @return*/public static MybatisDataAuthority getLocalDataAuthority() {return localDataPermission.get();}public static void clearDataPermission() {localDataPermission.remove();}public static void startDataPermissionHasAgent(Integer permissionId, Boolean hasGame, Boolean hasMedia, String operatorAlias, String gameAlias, String mediaAlias, String userDataTable) {// 获取当前的用户LoginUser loginUser = SecurityUtils.getLoginUser();List<Integer> userIds = new ArrayList<>(16);boolean allOperator = false;if (loginUser != null) {// 查询本人的当前页面的操作权限for (PermissionV2Vo permissionV2Vo : loginUser.getPermissionV2VoList()) {if (permissionV2Vo.getId().equals(permissionId)) {switch (permissionV2Vo.getDataAuthority()) {// 本人case 1:userIds.add(loginUser.getUserId());break;// 本部门case 2:// 获取部门的人员Example example = new Example(UserV2.class);example.createCriteria().andEqualTo("departmentId", loginUser.getUser().getDepartmentId());userIds = SpringUtil.getBeanByType(UserV2Mapper.class).selectByExample(example).stream().map(BaseEntity::getId).collect(Collectors.toList());break;// 公司case 3:allOperator = true;default:break;}}}}// 获取代理人UserDataPermissionMapper userDataPermissionMapper = SpringUtil.getBeanByType(UserDataPermissionMapper.class);Example example = new Example(UserDataPermission.class);example.createCriteria().andEqualTo("status", 1).andEqualTo("tableName", userDataTable).andEqualTo("userId", loginUser.getUserId());List<UserDataPermission> userDataPermissions = Optional.ofNullable(userDataPermissionMapper.selectByExample(example)).orElse(new ArrayList<>(16));List<Integer> agentIds = userDataPermissions.stream().map(UserDataPermission::getRelatedId).collect(Collectors.toList());MybatisDataAuthority mybatisDataAuthority = MybatisDataAuthority.builder().superAdmin(loginUser.getUser().getSuperAdmin()).hasOperator(true).hasMedia(hasMedia).hasGame(hasGame).operatorAlias(operatorAlias).gameAlias(gameAlias).allGame(CollectionUtils.isEmpty(loginUser.getGameCodes()) ? 1 : 0).allMedia(CollectionUtils.isEmpty(loginUser.getMediaCodes()) ? 1 : 0).allOperator(allOperator).mediaAlias(mediaAlias).operators(userIds).games(loginUser.getGameCodes()).games(loginUser.getGameCodes()).mediaCodes(loginUser.getMediaCodes()).agentIds(agentIds).build();localDataPermission.set(mybatisDataAuthority);}public static void startDataPermission(Integer permissionId, Boolean hasGame, Boolean hasMedia, String operatorAlias, String gameAlias, String mediaAlias) {// 获取当前的用户boolean allOperator = false;LoginUser loginUser = SecurityUtils.getLoginUser();List<Integer> userIds = new ArrayList<>(16);if (loginUser != null) {// 查询本人的当前页面的操作权限for (PermissionV2Vo permissionV2Vo : loginUser.getPermissionV2VoList()) {if (permissionV2Vo.getId().equals(permissionId)) {switch (permissionV2Vo.getDataAuthority()) {// 本人case 1:userIds.add(loginUser.getUserId());break;// 本部门case 2:// 获取部门的人员Example example = new Example(UserV2.class);example.createCriteria().andEqualTo("departmentId", loginUser.getUser().getDepartmentId());userIds = SpringUtil.getBeanByType(UserV2Mapper.class).selectByExample(example).stream().map(BaseEntity::getId).collect(Collectors.toList());break;// 公司case 3:allOperator = true;default:break;}}}}MybatisDataAuthority mybatisDataAuthority = MybatisDataAuthority.builder().superAdmin(loginUser.getUser().getSuperAdmin()).hasOperator(true).hasMedia(hasMedia).hasGame(hasGame).operatorAlias(operatorAlias).gameAlias(gameAlias).allOperator(allOperator).allGame(CollectionUtils.isEmpty(loginUser.getGameCodes()) ? 1 : 0).allMedia(CollectionUtils.isEmpty(loginUser.getMediaCodes()) ? 1 : 0).mediaAlias(mediaAlias).operators(userIds).games(loginUser.getGameCodes()).games(loginUser.getGameCodes()).mediaCodes(loginUser.getMediaCodes()).build();localDataPermission.set(mybatisDataAuthority);}public static void startOnlyGamePermission(String gameAlias) {// 获取当前的用户LoginUser loginUser = SecurityUtils.getLoginUser();MybatisDataAuthority mybatisDataAuthority = MybatisDataAuthority.builder().superAdmin(loginUser.getUser().getSuperAdmin()).hasOperator(false).hasMedia(false).gameAlias(gameAlias).hasGame(true).gameAlias(gameAlias).allGame(CollectionUtils.isEmpty(loginUser.getGameCodes()) ? 1 : 0).allMedia(CollectionUtils.isEmpty(loginUser.getMediaCodes()) ? 1 : 0).games(loginUser.getGameCodes()).build();localDataPermission.set(mybatisDataAuthority);}public static void startOnlyMediaPermission(String mediaAlias) {// 获取当前的用户LoginUser loginUser = SecurityUtils.getLoginUser();MybatisDataAuthority mybatisDataAuthority = MybatisDataAuthority.builder().superAdmin(loginUser.getUser().getSuperAdmin()).hasOperator(false).hasMedia(true).mediaAlias(mediaAlias).hasGame(false).allGame(CollectionUtils.isEmpty(loginUser.getGameCodes()) ? 1 : 0).allMedia(CollectionUtils.isEmpty(loginUser.getMediaCodes()) ? 1 : 0).mediaCodes(loginUser.getMediaCodes()).build();localDataPermission.set(mybatisDataAuthority);}public static void startOnlyMediaAndGamePermission(String mediaAlias, String gameAlias) {// 获取当前的用户LoginUser loginUser = SecurityUtils.getLoginUser();MybatisDataAuthority mybatisDataAuthority = MybatisDataAuthority.builder().superAdmin(loginUser.getUser().getSuperAdmin()).hasOperator(false).hasMedia(true).mediaAlias(mediaAlias).hasGame(true).gameAlias(gameAlias).allGame(CollectionUtils.isEmpty(loginUser.getGameCodes()) ? 1 : 0).allMedia(CollectionUtils.isEmpty(loginUser.getMediaCodes()) ? 1 : 0).mediaCodes(loginUser.getMediaCodes()).games(loginUser.getGameCodes()).build();localDataPermission.set(mybatisDataAuthority);}public static List<Integer> getOperatorList(Integer permissionId) {LoginUser loginUser = SecurityUtils.getLoginUser();List<Integer> userIds = new ArrayList<>(16);if (loginUser != null) {// 查询本人的当前页面的操作权限for (PermissionV2Vo permissionV2Vo : loginUser.getPermissionV2VoList()) {if (permissionV2Vo.getId().equals(permissionId)) {switch (permissionV2Vo.getDataAuthority()) {// 本人case 1:userIds.add(loginUser.getUserId());break;// 本部门case 2:// 获取部门的人员Example example = new Example(UserV2.class);example.createCriteria().andEqualTo("departmentId", loginUser.getUser().getDepartmentId());userIds = SpringUtil.getBeanByType(UserV2Mapper.class).selectByExample(example).stream().map(BaseEntity::getId).collect(Collectors.toList());break;// 公司case 3:default:break;}}}}return userIds;}
}

上面代码为根据认证用户所拥有的资源进行数据的封装传递给拦截器的过程。主要思路为根据用户所属的权限组获取到用户当前页面拥有的数据权限:公司、部门、个人,若所属权限为公司即可查查看所有资源,无需处理。若为部门则将部门下的所有用户传递给拦截器过滤,若为个人同理。

/**    * 获取应用名称列表    * @return*/
public List<AppNameVo> getAllAppNameList(){DataPermissionHelper.startOnlyGamePermission("game_code");return appConfigMapper.getAllAppNameList();
}

此为示例代码,可以看到在appConfigMapper.getAllAppNameList();上增加了一行开启游戏权限过滤的代码,通过此代码将原本查所有的游戏变成了只查当前用户拥有的游戏。


http://chatgpt.dhexx.cn/article/XcKbCPao.shtml

相关文章

Mybatis拦截器失效

现象&#xff1a; 自定义插件注册成功&#xff0c;但是始终不进入拦截器方法&#xff1b; 我的拦截器&#xff1a; 排查 Mybatis拦截器是采用的责任链模式&#xff0c;一般拦截器中intercept方法中最后执行 invocation.proceed() 方法&#xff0c;将拦截器责任链向后传递&…

MyBatis拦截器实现原理

Mybatis拦截器并不是每个对象里面的方法都可以被拦截的。Mybatis拦截器只能拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler四个类里面的方法&#xff0c;这四个对象在创建的时候才会创建代理。 用途&#xff1a;实际工作中&#xff0c;可以使用Mybatis拦…

自定义MyBatis拦截器

文章目录 自定义MyBatis拦截器作用MyBatis中的四大核心对象在mybatis中可被拦截的类型有四种(按照拦截顺序)拦截器需要实现Mybatis提供的Interceptor接口利用反射获取运行中的实体字段的名字利用反射动态的为sql语句传递新参数使用mybatis自定义的拦截器为插入&#xff0c;更新…

Mybatis拦截器的使用及其源码详解

Mybatis拦截器的使用及其源码详解 Mybatis相关全览一、简介执行与添加顺序拦截器生效入口 二、使用例子 三、原理加载入口生成代理遍历拦截器匹配&生成代理 四、实践例子 本文用的是3.5.10版本 源码地址&#xff1a;https://github.com/mybatis/mybatis-3/releases 文档地址…

Mybatis拦截器Interceptor

前言 最近项目使用Mybatis拦截器对数据进行加解密&#xff0c;以下记录如何将拦截器集成到项目中以及在使用过程中踩过的一些小坑&#xff0c;与君共勉 1.Myabtis拦截器是什么&#xff1f; MyBatis允许使用者在映射语句执行过程中的某一些指定的节点进行拦截调用&#xff0c…

Springboot 自定义mybatis 拦截器,实现我们要的扩展

前言 相信大家对拦截器并不陌生&#xff0c;对mybatis也不陌生。 有用过pagehelper的&#xff0c;那么对mybatis拦截器也不陌生了&#xff0c;按照使用的规则触发sql拦截&#xff0c;帮我们自动添加分页参数 。 那么今天&#xff0c;我们的实践 自定义mybatis拦截器也是如此&a…

mybatis 拦截器

&#x1f680; 优质资源分享 &#x1f680; 学习路线指引&#xff08;点击解锁&#xff09;知识定位人群定位&#x1f9e1; Python实战微信订餐小程序 &#x1f9e1;进阶级本课程是python flask微信小程序的完美结合&#xff0c;从项目搭建到腾讯云部署上线&#xff0c;打造一…

Mybatis——拦截器Interceptor

本篇介绍Mybatis拦截器/插件相关知识&#xff0c;包括相关的类及作用、拦截器有哪几种、拦截器可以在Mybatis的执行过程中的哪些节点起作用、拦截器如何使用、相关的API&#xff0c;最后附上代码示例。 1. Mybatis拦截器是什么&#xff1f;一般用途&#xff1f; MyBatis允许使…

vue后台管理框架(iview + vue)

iview官网&#xff1a;https://www.iviewui.com/docs/introduce vueiview后台管理文档&#xff1a;https://lison16.github.io/iview-admin-doc/#/ vueiview后台管理线上示例&#xff1a;https://admin.iviewui.com/login vueiview后台管理github&#xff1a;https://github.co…

基于ThinkPHP6+Layui的后台管理框架

项目介绍 一款 PHP 语言基于 ThinkPhp6.x、Layui、MySQL等框架精心打造的一款模块化、插件化、高性能的前后端分离架构敏捷开发框架&#xff0c;可用于快速搭建前后端分离后台管理系统&#xff0c;本着简化开发、提升开发效率的初衷&#xff0c;框架自研了一套个性化的组件&am…

Quickadmin:基于ThinkPhp6+Vue+ElementUI后台管理框架

Quickadmin&#xff1a;基于ThinkPhp6VueElementUI后台管理框架 简介 QuickAdmin 是一款基于ThinkPHP 6.x&#xff0c;ElementUI&&Vue 2.x前后端分离后台管理框架&#xff0c;通过框架自带的在线代码生成器&#xff0c;可以轻松的实现后台管理的curd&#xff08;增删…

后台管理怎样用html实现,后台管理实现

京淘后台管理实现 1.1 商品列表展现 1.1.1 商品POJO对象 1.1.2 表格数据页面结构 1.1.3 请求URL地址 说明&#xff1a;如果采用UI框架并且添加了分页插件&#xff0c;则会自动的形成如下的URL请求地址 1.1.4 编辑ItemController 1.1.5 编辑ItemService 1.1.6 编辑ItemMapper 手…

【React项目架构 】+后台管理系统cms实操

React项目架构 一、 react脚手架 (一) 、yarn 安装 (二) 、react安装 npx create-react-app [-g] npm i react17.0.0 react-dom17.0.0 babel-standalone6.26.0 npm i react-router-dom npm i redux4.1.1 --save npm i redux-thunk2.4.1 npm i redux-persist npm install an…

Python实现的一个简洁轻快的后台管理框架.支持拥有多用户组的RBAC管理后台,不用配置各种运行环境

Mini Admin 完整代码下载地址&#xff1a;Python实现的一个简洁轻快的后台管理框架.支持拥有多用户组的RBAC管理后台 Mini Admin,一个简洁轻快的后台管理框架.支持拥有多用户组的RBAC管理后台 &#x1f680; 应用场景&#xff1a;2-5人的管理团队&#xff0c;需要管理的资源数…

vue——后台管理系统框架

效果图 代码: <template><el-container class="box-el-container"><el-aside class="box-el-aside" :style="{width:isCollapse?60px:200px}"><el-col :span="12" class="el-menu-aside"><!…

基于ThinkPHP6+Layui通用后台管理框架

项目介绍 一款 PHP 语言基于 ThinkPhp6.x、Layui、MySQL等框架精心打造的一款模块化、插件化、高性能的前后端分离架构敏捷开发框架&#xff0c;可用于快速搭建前后端分离后台管理系统&#xff0c;本着简化开发、提升开发效率的初衷&#xff0c;框架自研了一套个性化的组件&am…

Rust学习指南(一)安装RUST后台管理框架

Windows安装 Rust安装非常简单&#xff0c;只要将Visual Studio的 Visual Studio的构建工具或者Visual Stuido 2022的构件工具安装即可。当被问及要安装哪些内容时&#xff0c;请确保已选择 “C build tools”&#xff0c;并包括 Windows 10 SDK 和英文语言包。具体可以参考这个…

php是一种通用开源,caozha-admin(PHP网站后台管理框架)

caozha-admin 后台管理框架 1.7.2 caozha-admin是一个通用的PHP网站后台管理框架&#xff0c;基于开源的ThinkPHP开发&#xff0c;特点&#xff1a;易上手&#xff0c;零门槛&#xff0c;界面清爽极简&#xff0c;极便于二次开发。 基础功能 1、系统设置 2、管理员管理 3、权限…

移动端后台管理系统框架

创建此项目的初衷 目前移动端越来越重要&#xff0c;好多项目都从PC端转移到了移动端。 前一段给客户做了一个PC和M自适应的项目&#xff0c;用vue-element-admin框架&#xff0c;手机端也能用&#xff0c;但体验有点差&#xff0c;客户改了好多。本来是好意&#xff0c;客户只…

(若依)RuoYi后台管理框架前端

若依后台管理 官方网址 后台管理二开推荐 官网的源码地址可供下载前后端代码 最近进到公司后跟着团队接了两家公司的App一套的开发&#xff0c;到公司的第一天就是先配置开发环境 安装软件之类的 第二天 带着熟悉RuoYi框架 很巧带我的大哥和我是一个学校毕业的 是大师兄 若依…