内容简介:前几篇文章分享了下 MyBatis 拦截器的相关知识,这里再分享下自己项目中遇到的一个问题,然后通过自定义的拦截器快速的解决了问题。SpringBoot,MyBatis.....最近项目中需要增加「数据权限」功能。所谓的「数据权限」是指不同用户在查询某张表的数据时看到的数据范围是不一样的,有的用户可以看到全部门店的数据,有的用户只能看到部分门店的数据,这就需要给不同角色用户配置不同的数据范围查看权限。比如 “商品信息表” 中有所属门店的 “门店id” 字段,用户能看到哪些门店的商品信息数据是根据配置的数据权
1. 前言:
前几篇文章分享了下 MyBatis 拦截器的相关知识,这里再分享下自己项目中遇到的一个问题,然后通过自定义的拦截器快速的解决了问题。
2. 项目用到的技术:
SpringBoot,MyBatis.....
3. 业务需求:
最近项目中需要增加「数据权限」功能。所谓的「数据权限」是指不同用户在查询某张表的数据时看到的数据范围是不一样的,有的用户可以看到全部门店的数据,有的用户只能看到部分门店的数据,这就需要给不同角色用户配置不同的数据范围查看权限。比如 “商品信息表” 中有所属门店的 “门店id” 字段,用户能看到哪些门店的商品信息数据是根据配置的数据权限来判断的, 例如A 用户能看到 3 家门店的商品信息数据,B 用户能看到 4 家门店的商品信息数据。具体的实现就是在对应的 SQL 查询语句的 WHERE 条件中加上 tableName.shopId IN (shopId1, shopId2,.....) 这个条件即可。(PS:系统中的十几张表都有 “门店id” 字段,所以对应的就有十几个功能需要加上数据权限)。
4. 实现方案:
具体的实现方式有以下两种:
(1)在每个需要加数据权限的地方加上对应的代码
如果我们的系统刚开始开发,对应的功能需要加数据权限,比如商品信息数据获取我们完全可以在业务代码中这样写:
public List<GoodsInformation> searchGoodsInformation(Integer userId) { List<Integer> shopIds = authService.getIds(userId); // 调用权限服务获取用户所能看到的门店 // 1. 如果 shopIds 为空,就是可以查询所有门店数据 ...... // 2. 如果 shopIds 不为空,就在查询语句中加上 tableName.shopId IN (shopId1, shopId2,.....) ...... }复制代码
上面的 1 和 2 两部操作,可以放在 MyBatis 中用 if 判断下即可,这样可以复用一条查询语句。
(2)利用 MyBatis 的拦截器加 AOP 的方式实现
如果我们的项目开发到后期了,这个时候产品说要给系统中十几个查询页面加上数据权限功能,听到这句话我们是有点蒙蔽的。如果按照方法 1,我们需要修改业务代码和 MyBatis 中的查询语句,还是比较耗费时间的。
其实我们分析下方法 1 中的代码可以发现: 根据 userId 获取门店集合是通用的代码,因为所有的数据权限功能都是根据 shopId 来判断的,所以这种通用的代码可以用 AOP 来实现,这样就不用修改散落在不同包中的业务代码了。还有给不同表加 tableName.shopId IN (shopId1, shopId2,.....) 这个条件,除了表名不同和字段名可能不同(有的叫shopId,有的叫shop等) 之外, IN (shopId1, shopId2,.....) 这个完全是相同的,所以我们可以自定义 MyBatis 拦截器 通过修改查询语句,添加我们需要的条件即可实现。其中对不同的表名和字段名,我们可以用自定义的注解来配置。
具体实现:
将 userId 放入自定义的 threadlocal 中
@ControllerAdvice public class ApplicationControllerAdvice { @ModelAttribute public void addAttributes(@RequestParam(required = false) String userId) { if(userId != null) { UserContext userContext = new UserContext(); userContext.setUserId(userId); // 将 userId 放入 threadlocal 中 userContextHolder.userContextThreadLocal.set(userContext); } } }复制代码
然后定义 AOP 切面,根据 userId 获取 shopIds 集合以及方法上自定义注解中的 tableName 和 field 信息,并存入 threadlocal 中:
@Before(value = "execution(* my.study.dataauthplugin.demo.*(..)) && @annotation(dataAuthentication)") public void getDataAuth(DataAuthentication dataAuthentication) throws Throwable { UserContext uc = UserContextHolder.userContextThreadLocal.get(); if (uc != null && !"".equals(uc.getUserId()) && dataAuthentication != null) { // 获取 shopId 集合 List<Integer> ids = authService.getIds(uc.getUserId()); if (ids != null && !ids.isEmpty()) { List<String> fields = new ArrayList<>(); List<String> tableNames = new ArrayList<>(); // 获取自定义注解中的 tableNames 和 fields SqlSignature[] signatures = dataAuthentication.value(); if(signatures.length > 0) { for (int i = 0; i < signatures.length; i++) { fields.add(signatures[i].field()); tableNames.add(signatures[i].tableName()); } } // 将 shopIds ,fields,tableNames 存入 threadlocal 中 uc.init(ids, fields, tableNames); } } } }复制代码
然后定义 MyBatis 插件,根据 threadlocal 中的值,修改对应的查询语句。
具体实现见: github.com/qianhongxin…
5. 感悟:
对自己项目中用到的相关技术,我们需要深入去学习。比如 MyBatis 提供了插件这种扩展机制,那我们就要充分去用好它,提高开发效率,为业务赋能。
人说脱离业务谈技术是耍牛氓!话说回来,想要更好的支持业务,不深入的学习好业务用到的技术,拿什么来支持业务呢?个人觉得深入学习技术源码是很有必要的,虽说暂时用不上,但是通过持续的阅读技术源码,也能给我们开发业务代码提供更好的实现方案。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- react离开页面,自定义弹框拦截,路由拦截
- Mybatis 自定义拦截器与插件开发
- 前端架构之vue+axios 前端实现登录拦截(路由拦截、http拦截)
- Springboot整合Hibernate拦截器时无法向拦截器注入Bean
- 基于原生fetch封装一个带有拦截器功能的fetch,类似axios的拦截器
- SpringMVC拦截器
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Designing Data-Intensive Applications
Martin Kleppmann / O'Reilly Media / 2017-4-2 / USD 44.99
Data is at the center of many challenges in system design today. Difficult issues need to be figured out, such as scalability, consistency, reliability, efficiency, and maintainability. In addition, w......一起来看看 《Designing Data-Intensive Applications》 这本书的介绍吧!