MyBatis-plus sql拦截器

因为业务需求,重新写了一套数据权限。项目中用的是mybtis-plus,正好MyBatis-Plus提供了插件数据权限插件 | MyBatis-Plus,那就根据文档来实现这个需求。

实现:

实现MultiDataPermissionHandler

首先创建MultiDataPermissionHandler的实现类,里面的内容自己做校验

@Slf4j
public class CustomDataPermissionHandler implements MultiDataPermissionHandler {

    // 岗位id
    private static final String CEO = "1";
    private static final String SE = "2";
    private static final String HR = "3";
    private static final String USER = "4";

    // 免校验表
    private static final String SYS = "sys";
    private static final Set<String> EXEMPT_TABLES = Set.of(
            "xxx不过滤的表",
            "xxx不过滤的表"
    );


    @Override
    public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {

        try {
            if (IgnoreWhereConditionAspect.shouldIgnoreWhereCondition()) {
                return null;
            }

            // 免校验表
            if (isExemptTable(table.getName())) {
                return null;
            }

            // 获取当前用户 未登录不做校验
            SysUser user = getCurrentUser();
            if (user == null || user.isAdmin()) {
                return null;
            }

            /* 获取岗位ids,总管可以看全部数据,主管可以看到自己小组的数据,其他只可以看到自己的数据 */
            List<String> postIds = getUserPostIds(user);
            if (postIds.contains(CEO)) {
                return null;
            }
            return postIds.contains(SE) ? parseGroupDataExpression(user.getGroupId(), getTableName(table)) : parseOwnDataExpression(getTableName(table));
        } catch (JSQLParserException e) {
            log.error("数据权限过滤失败," ,e);
            return null;
        }
    }

    /**
     * 获取表名
     * @param table 表
     * @return 表名
     */
    private String getTableName(Table table) {
        return String.valueOf(table.getAlias() == null ? table.getName() : table.getAlias().getName());
    }

    /**
     * 获取用户岗位ids
     * @param user 当前操作的用户
     * @return 岗位ids
     */
    private List<String> getUserPostIds(SysUser user) {
        if (user.getPostIds() == null) {
            throw new ServiceException(POST_MUST_NOT_NULL);
        }
        return Arrays.asList(user.getPostIds());
    }

    /**
     * 获取当前用户
     * @return 当前用户
     */
    private SysUser getCurrentUser() {
        try {
            return SecurityUtils.getUser();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 根据表名判断是否免校验
     * @param tableName 表名
     * @return 是否免校验
     */
    private boolean isExemptTable(String tableName) {
        return tableName.startsWith(SYS) || EXEMPT_TABLES.contains(tableName);
    }

    /**
     * 解析组数据表达式
     * @param groupId 组id
     * @return 组数据表达式
     */
    private Expression parseGroupDataExpression(String groupId, String tableName) throws JSQLParserException {
        return CCJSqlParserUtil.parseCondExpression(
                tableName + ".create_by in (select sug.user_id from sys_user su " +
                        "join sys_user_group sug on sug.user_id = su.id " +
                        "where sug.group_id = " + groupId + ")"
        );
    }

    /**
     * 解析自己的数据表达式
     * @return 自己的数据表达式
     */
    private Expression parseOwnDataExpression(String name) throws JSQLParserException {
        return CCJSqlParserUtil.parseCondExpression(name + ".create_by = " + SecurityUtils.getUserId());
    }

这里实现了MultiDataPermissionHandler接口的getSqlSegment方法,根据文档说明不需要拼接where条件的直接返回null就可以,需要拼接where添加的拼接相应的条件

注册数据权限拦截器

@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {

        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        interceptor.addInnerInterceptor(paginationInnerInterceptor());
        // 乐观锁插件
        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
        // 阻断插件
        interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
        // 数据权限插件
        interceptor.addInnerInterceptor(new DataPermissionInterceptor(new CustomDataPermissionHandler()));

        return interceptor;
    }

    /**
     * 分页插件,自动识别数据库类型 https://baomidou.com/guide/interceptor-pagination.html
     */
    public InnerInterceptor paginationInnerInterceptor() {
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        // 设置数据库类型为mysql
        paginationInnerInterceptor.setDbType(DbType.MYSQL);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        paginationInnerInterceptor.setMaxLimit(-1L);
        return paginationInnerInterceptor;
    }

    /**
     * 乐观锁插件 https://baomidou.com/guide/interceptor-optimistic-locker.html
     */
    public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
        return new OptimisticLockerInnerInterceptor();
    }

    /**
     * 如果是对全表的删除或更新操作,就会终止该操作 https://baomidou.com/guide/interceptor-block-attack.html
     */
    public InnerInterceptor blockAttackInnerInterceptor() {
        return new BlockAttackInnerInterceptor();
    }
}

新建注解IgnoreWhereCondition

表示是否忽略sql拦截,默认为true,如果有些接口不需要鉴权就在controller上添加这个注解

@Target(ElementType.METHOD) // 该注解应用于方法
@Retention(RetentionPolicy.RUNTIME) // 运行时可用
public @interface IgnoreWhereCondition {
    boolean value() default true; // 默认值为 true,表示忽略
}

新建AOP切面 IgnoreWhereConditionAspect

/**
 * AOP切面,用于处理带有 @IgnoreWhereCondition 注解的方法。
 * 通过ThreadLocal存储当前方法是否需要忽略SQL拦截的状态。
 */
@Aspect
@Component
public class IgnoreWhereConditionAspect {

    private static final ThreadLocal<Boolean> ignoreWhereConditionHolder = ThreadLocal.withInitial(() -> false);

    /**
     * 在方法执行前检查是否有 @IgnoreWhereCondition 注解,并设置ThreadLocal中的状态。
     * @param joinPoint JoinPoint
     */
    @Before("@annotation(com.sh.framework.annotation.IgnoreWhereCondition)")
    public void beforeMethod(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        IgnoreWhereCondition annotation = method.getAnnotation(IgnoreWhereCondition.class);
        ignoreWhereConditionHolder.set(annotation.value());
    }

    /**
     * 获取当前线程中是否应该忽略SQL拦截。
     * 如果需要忽略,则清除ThreadLocal中的状态。
     * @return boolean
     */
    public static boolean shouldIgnoreWhereCondition() {
        Boolean b = ignoreWhereConditionHolder.get();
        if (b) clear();
        return b;
    }

    public static void clear() {
        ignoreWhereConditionHolder.remove();
    }
}

使用注解

@SelectRecordAnnotate(value = "分页查询付款申请")
@Operation(summary = "分页查询付款申请")
@GetMapping("selectPage")
@IgnoreWhereCondition
public AjaxResult selectPage(SiHePage page, CostPayReqVO costPayReqVO) {
    return success(costPayReqService.selectListPage(page, costPayReqVO));
}
相关推荐
吴天德少侠15 分钟前
设计模式中的关联和依赖区别
java·开发语言·设计模式
道友老李19 分钟前
【Java】多线程和高并发编程(三):锁(下)深入ReentrantReadWriteLock
java·系统架构
小黑子不会打篮球1 小时前
hackdudo2解题过程
网络·学习·web安全·网络安全
geovindu1 小时前
java: framework from BLL、DAL、IDAL、MODEL、Factory using postgresql 17.0
java·开发语言·postgresql
缘来是黎2 小时前
Python 进阶:生成器与上下文管理器
java·前端·python
m0_748240252 小时前
【Spring Boot】统一数据返回
java·spring boot·后端
陈老师还在写代码2 小时前
介绍下SpringBoot常用的依赖项
java·spring boot·后端
程序猿零零漆2 小时前
《从入门到精通:蓝桥杯编程大赛知识点全攻略》(十一)-回文日期、移动距离、日期问题
java·算法·蓝桥杯
众乐乐_20082 小时前
JVM栈帧中|局部变量表、操作数栈、动态链接各自的任务是什么?
java·开发语言·jvm
m0_748239333 小时前
深入解析Spring Boot中的@ConfigurationProperties注解
java·spring boot·后端