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));
}
相关推荐
程序猿小D14 分钟前
第三百八十九节 JavaFX教程 - JavaFX WebEngine
java·eclipse·intellij-idea·vr·javafx
self-discipline6342 小时前
【Java】Java核心知识点与相应面试技巧(七)——类与对象(二)
java·开发语言·面试
wei3872452322 小时前
java笔记02
java·开发语言·笔记
nuc-1272 小时前
sqli-labs学习记录8
数据库·学习·sqli-labs
zjj5873 小时前
Docker使用ubuntu
java·docker·eureka
士别三日&&当刮目相看3 小时前
JAVA学习*简单的代理模式
java·学习·代理模式
今夜有雨.3 小时前
HTTP---基础知识
服务器·网络·后端·网络协议·学习·tcp/ip·http
ShareBeHappy_Qin3 小时前
设计模式——设计模式理念
java·设计模式
长流小哥5 小时前
Qt 动画学习 入门Qt,怎能少了学动画!
开发语言·qt·学习