当前位置: 首页 > news >正文

使用MyBatis-Plus实现数据权限功能

什么是数据权限

数据权限是指系统根据用户的角色、职位或其他属性,控制用户能够访问的数据范围。与传统的功能权限(菜单、按钮权限)不同,数据权限关注的是数据行级别的访问控制。

常见的数据权限控制方式包括:

  • 部门数据权限:只能访问本部门数据

  • 个人数据权限:只能访问自己的数据

  • 自定义数据范围:通过特定规则限制数据访问

MyBatis-Plus实现数据权限的方案

实现完整例子

下面是一个完整的基于MyBatis-Plus和Spring Security的数据权限实现示例:

 1. 数据权限配置类

@Configuration
public class MybatisConfig {@Beanpublic ConfigurationCustomizer mybatisConfigurationCustomizer(){return configuration -> configuration.setObjectWrapperFactory(new MapWrapperFactory());}@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 1.添加数据权限插件interceptor.addInnerInterceptor(new DataPermissionInterceptor(new DataPressionConfig()));PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();// 分页插件paginationInnerInterceptor.setOptimizeJoin(false);paginationInnerInterceptor.setOverflow(true);paginationInnerInterceptor.setDbType(DbType.POSTGRE_SQL);interceptor.addInnerInterceptor(paginationInnerInterceptor);return interceptor;}
}

     2. 数据权限拦截器
    @Slf4j
    @Component
    public class DataPressionConfig implements DataPermissionHandler {@Overridepublic Expression getSqlSegment(Expression where, String mappedStatementId) {try {if(null==mappedStatementId){return null;}Class<?> mapperClazz = Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(".")));String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);// 获取自身类中的所有方法,不包括继承。与访问权限无关Method[] methods = mapperClazz.getDeclaredMethods();for (Method method : methods) {DataScope dataScopeAnnotationMethod = method.getAnnotation(DataScope.class);if(null==dataScopeAnnotationMethod){continue;}//spring aoc里拿方法参数Parameter[] parameters= method.getParameters();if (parameters.length > 0) {log.info("方法参数:" +  dataScopeAnnotationMethod.oneselfScopeName() );}if (ObjectUtils.isEmpty(dataScopeAnnotationMethod) || !dataScopeAnnotationMethod.enabled()) {continue;}if (method.getName().equals(methodName) || (method.getName() + "_COUNT").equals(methodName) || (method.getName() + "_count").equals(methodName)) {return buildDataScopeByAnnotation(dataScopeAnnotationMethod,mappedStatementId);}}} catch (ClassNotFoundException e) {e.printStackTrace();}return null;}/*** DataScope注解方式,拼装数据权限** @param dataScope* @return*/private Expression buildDataScopeByAnnotation(DataScope dataScope,String mapperId) {Map<String, Object> params = DataPermissionContext.getParams();if (params == null || params.isEmpty()) {return null;}Object areaCodes = params.get(mapperId);List<String> dataScopeDeptIds=  (List<String>) areaCodes;// 获取注解信息String tableAlias = dataScope.tableAlias();String areaCodes= dataScope.areaCodes();Expression expression = buildDataScopeExpression(tableAlias,   areaCodes, dataScopeDeptIds);return expression == null ? null : new Parenthesis(expression);}/*** 拼装数据权限** @param tableAlias        表别名* @param oneselfScopeName  本人限制范围的字段名称* @param dataScopeDeptIds  数据权限部门ID集合,去重* @return*/private Expression buildDataScopeExpression(String tableAlias,  String areaCodes, List<String> dataScopeDeptIds) {/*** 构造部门里行政区划 area_code 的in表达式。*/try {String sql=tableAlias + "." + areaCodes+" in (";for(String areaCode:dataScopeDeptIds){sql+="'"+areaCode+"',";}sql=sql.substring(0,sql.length()-1)+")";Expression selectExpression = CCJSqlParserUtil.parseCondExpression(sql, true);return selectExpression;} catch (JSQLParserException e) {throw new RuntimeException(e);}}}

    3. 存储spring aop切面拿到的数据

         

      public class DataPermissionContext {private static final ThreadLocal<Map<String, Object>> CONTEXT = new ThreadLocal<>();public static void setParams(Map<String, Object> params) {CONTEXT.set(params);}public static Map<String, Object> getParams() {return CONTEXT.get();}public static void clear() {CONTEXT.remove();}
      }

         4. 定义数据权限注解
        @Inherited
        @Target({ElementType.METHOD, ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface DataScope {/*** 是否生效,默认true-生效*/boolean enabled() default true;/*** 表别名*/String tableAlias() default "";/*** 本人限制范围的字段名称*/String areaCodes() default "area_code";}
        5. 实现AOP切面
        @Slf4j
        @Aspect
        @Component
        public class DataAspet {@Pointcut("@annotation(DataScope)")public void logPoinCut() {}@Before("logPoinCut()")public void saveSysLog(JoinPoint joinPoint) {//从切面织入点处通过反射机制获取织入点处的方法MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取切入点所在的方法Method method = signature.getMethod();DataScope scope = method.getAnnotation(DataScope.class);Map<String, Object> paramValues = new HashMap<>();Object[] args = joinPoint.getArgs();Parameter[] parameters = method.getParameters();if(null!=parameters&&parameters.length>0) {//添加到最后一个参数log.info("参数名称:{}",signature.getName()+":"+signature.getDeclaringTypeName());paramValues.put(signature.getDeclaringTypeName()+"."+signature.getName(), args[parameters.length-1]);DataPermissionContext.setParams(paramValues);}else{DataPermissionContext.setParams(null);}}}

        6. mapper里注解使用数据权限

        tableAlias里你的sql里面要插入数据权限字段的表别名。

        比如:

        select count(*) as total,d.type as type from bus_device d group by d.type

        spring aop 会读取mapper方法最后一个参数,然后切入Sql变成

        select count(*) as total,d.type as type from bus_device d where

        d.area_code in(#{areaCodes} )  group by d.type

            @DataScope(tableAlias = "d")List<DeviceTypeCountDto> listByApplicationCategory(@Param(  @Param("type") String type,List<String> areaCodes);
        

        注意事项

        1. 性能考虑:数据权限过滤会增加SQL复杂度,可能影响查询性能,特别是对于大数据量表。可以考虑添加适当的索引优化。

        2. SQL注入风险:在拼接SQL时要特别注意防止SQL注入,建议使用预编译参数。

        3. 缓存问题:如果使用了缓存,需要注意数据权限可能导致缓存命中率下降或数据泄露问题。

        4. 多租户场景:在多租户系统中,数据权限通常需要与租户隔离一起考虑。

        5. 复杂查询:对于复杂的多表关联查询,数据权限条件可能需要更精细的控制。

        通过以上方案,我们可以灵活地在MyBatis-Plus中实现各种数据权限控制需求,根据项目实际情况选择最适合的实现方式。

        相关文章:

      1. 【排错】ubuntu挂载硬盘mount报错 unknown filesystem type ‘LVM2_member‘.
      2. 华为OD机考-数字螺旋矩阵(JAVA 2025B卷)
      3. 6.10[A]BB84 量子
      4. [Java 基础]Math 类
      5. 如何使用deepseek满血版
      6. Docker Swarm overlay 和 docker_gwbridge
      7. 如何开启自己计算机远程桌面连接功能? 给别人或异地访问
      8. Spring声明式事务生效是有条件滴!
      9. 基于 HTML5 的画图工具
      10. Windows 安装 Maven
      11. C#最佳实践:为何优先使用readonly而非const
      12. 【Kubernetes】---污点(Taint)与容忍(Toleration)
      13. Java基于局域网的聊天室系统设计与实现,附源码+论文
      14. QMultiMapQHashQList使用区别
      15. 类复制.省略 class.copy.elision
      16. Qt工作总结06 < QMap<> 和QVector<QPair>、以及QPair<>和QMakePair<> >
      17. 远程io模块在汽车流水线的应用
      18. 【Python】Python办公自动化需要你了解什么?会什么?
      19. AI 赋能 Java 开发:从通宵达旦到高效交付的蜕变之路
      20. DD3118完整版参数规格书|DD3118 3.0读卡器控制方案|DD3118高速3.0读卡器芯片
      21. 福州市人民政府官网/成都网站关键词推广优化
      22. 做哪种类型的网站赚钱呢/seo网络营销外包
      23. 搭建品牌电商网站怎么做/网页搜索引擎优化技术
      24. 咪豆建站/自动连点器
      25. 网站备案 备注/刷排名seo
      26. 抽奖网站怎么做的/搜索引擎大全入口