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));
}
相关推荐
DKPT14 分钟前
Java桥接模式实现方式与测试方法
java·笔记·学习·设计模式·桥接模式
好奇的菜鸟2 小时前
如何在IntelliJ IDEA中设置数据库连接全局共享
java·数据库·intellij-idea
满昕欢喜2 小时前
SQL Server从入门到项目实践(超值版)读书笔记 20
数据库·sql·sqlserver
DuelCode3 小时前
Windows VMWare Centos Docker部署Springboot 应用实现文件上传返回文件http链接
java·spring boot·mysql·nginx·docker·centos·mybatis
优创学社23 小时前
基于springboot的社区生鲜团购系统
java·spring boot·后端
好好研究3 小时前
学习栈和队列的插入和删除操作
数据结构·学习
幽络源小助理3 小时前
SpringBoot基于Mysql的商业辅助决策系统设计与实现
java·vue.js·spring boot·后端·mysql·spring
猴哥源码3 小时前
基于Java+springboot 的车险理赔信息管理系统
java·spring boot
YuTaoShao4 小时前
【LeetCode 热题 100】48. 旋转图像——转置+水平翻转
java·算法·leetcode·职场和发展
新中地GIS开发老师4 小时前
新发布:26考研院校和专业大纲
学习·考研·arcgis·大学生·遥感·gis开发·地理信息科学