Mybatis-Plus自定义dataScpoe拦截器实现数据权限

使用AOP切面,自定义注解,自定义mybatisplus拦截器,使用 JSqlParser 自定拼接where条件。

1、自定义注解@DataScope;注解一般用于Service层或者DAO层(Mapper)

import java.lang.annotation.*;

/**
 * 数据权限过滤注解
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {

    /**
     * 用户表的别名
     *
     * @return
     */
    String userAlias() default "";

    /**
     * 用户字段名
     *
     * @return
     */
    String userColumn() default "";

    /**
     * 店铺表的别名
     *
     * @return
     */
    String shopAlias() default "";

    /**
     * 店铺字段名
     *
     * @return
     */
    String shopColumn() default "";


}

2、DataScopeAspect 定义切面

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 数据权限切面
 */
@Aspect
@Component
public class DataScopeAspect {

    @Before("@annotation(datascope)")
    public void doBefore(JoinPoint point, DataScope datascope) throws Throwable {
        resetContextHolders();
        initContextHolders(datascope);
    }

    protected void initContextHolders(DataScope dataScope) {
        AbstractDataScopeContextHolder.set(dataScope);
    }

    private void resetContextHolders() {
        AbstractDataScopeContextHolder.remove();
    }

}

3、AbstractDataScopeContextHolder 存储和获取dataScpoe上下文对象

/**
 * DataScope上下文对象
 */

public abstract class AbstractDataScopeContextHolder {
    private static final ThreadLocal<DataScope> CONTEXT = new InheritableThreadLocal<>();

    public static void set(DataScope dataScope) {
        CONTEXT.set(dataScope);
    }

    public static DataScope get() {
        return CONTEXT.get();
    }

    public static void remove() {
        CONTEXT.remove();
    }

}

4、DataScopeHandler 数据权限dataScope业务处理接口


import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.statement.select.PlainSelect;

/**
 * 数据权限处理接口
 */
public interface DataScopeHandler {

    /**
     * 处理数据权限sql
     *
     * @param plainSelect sql解析器
     * @param dataScope   数据范围注解
     * @throws JSQLParserException SQL解析异常
     */
    void handlerDataScopeSql(PlainSelect plainSelect, DataScope dataScope) throws JSQLParserException;

}

5、DataScopeHandlerAutoConfigure 实现 DataScopeHandler 数据权限业务处理类;代码参考:mybatis-mate-datascope/src/main/java/mybatis/mate/datascope/config/DataScopeConfig.java · baomidou/mybatis-mate-examples - Gitee.com

/**
 * 实现数据权限处理
 *
 */
@Configuration
public class DataScopeHandlerAutoConfigure {

    @Bean
    DataScopeHandler dataScopeHandler() {
        return new DataScopeHandler() {


            /**
             * 拼接到where条件
             *
             * @param plainSelect
             * @param expression
             */
            private void setWhere(PlainSelect plainSelect, Expression expression) {
                Expression where = plainSelect.getWhere();
                if (where == null) {
                    // 不存在 where 条件
                    plainSelect.setWhere(new Parenthesis(expression));
                } else {
                    // 存在 where 条件 and 处理
                    plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), expression));
                }
            }


            /**
             * 处理数据权限sql
             * @param plainSelect sql解析器
             * @param dataScope   数据范围注解
             * @throws JSQLParserException
             */
            @Override
            public void handlerDataScopeSql(PlainSelect plainSelect, DataScope dataScope) throws JSQLParserException {
               // todo 从登录用户中获取数据权限
                User user = getUser();

                // 1、仅限本人查看数据权限
                String userColumn = dataScope.userColumn();
                if (StrUtil.isNotEmpty(userColumn)) {
                    String userAlias = dataScope.userAlias();
                    String column;
                    if (StrUtil.isEmpty(userAlias)) {
                        column = String.format("%s", userColumn);
                    } else {
                        column = String.format("%s.%s", userAlias, userColumn);
                    }
                    EqualsTo expression = new EqualsTo();
                    expression.setLeftExpression(new Column(column));
                    expression.setRightExpression(new StringValue(user.getUserName()));
                    this.setWhere(plainSelect, expression);
                }

                // 2、店铺权限
                String shopColumn = dataScope.shopColumn();
                if (StrUtil.isNotEmpty(shopColumn)) {
                    String shopAlias = dataScope.shopAlias();
                    String column;
                    if (StrUtil.isEmpty(shopAlias)) {
                        column = String.format("%s", shopColumn);
                    } else {
                        column = String.format("%s.%s", shopAlias, shopColumn);
                    }
                    // 数据权限数据组装in条件
                    List<String> shops = user.getShops();
                    // 把集合转变为JSQLParser需要的元素列表
                    ItemsList itemsList = new ExpressionList(shops.stream().map(StringValue::new).collect(Collectors.toList()));
                    // 创建in表达式对象,传入列名及in范围列表
                    InExpression inExpression = new InExpression(new Column(column), itemsList);
                    this.setWhere(plainSelect, inExpression);
                }

                // 3、其他数据权限处理where条件 todo

            }

         

        };
    }

}

6、DataScopeInnerInterceptor 定义Mybatis-Plus拦截器


import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.extern.log4j.Log4j2;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.List;

/**
 * Mybatis-Plus数据权限拦截器插件
 */
@Log4j2
public class DataScopeInnerInterceptor implements InnerInterceptor {

    private final DataScopeHandler dataScopeHandler;

    public DataScopeInnerInterceptor(DataScopeHandler dataScopeHandler) {
        this.dataScopeHandler = dataScopeHandler;
    }

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 获取登录用户,或获取其他条件,不执行数据权限  todo 
        User user = getUser();
        if (null == user) {
            if (log.isInfoEnabled()) {
                log.info("未登录无user不执行数据权限");
                return;
            }
        }
        if (user.getAdmin()) {
            if (log.isInfoEnabled()) {
                log.info("管理员不执行数据权限");
            }
            return;
        }
        // todo ........

        if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) {
            return;
        }
        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);

        // 原始SQL
        String originalSql = mpBs.sql();
        if (log.isInfoEnabled()) {
            log.warn("Original SQL: " + originalSql);
        }

        try {
            Statement statement = CCJSqlParserUtil.parse(originalSql);

            if (statement instanceof Select) {
                Select select = (Select) statement;
                // 解析SQL
                this.processSelect(select);
                final String parserSql = statement.toString();
                mpBs.sql(parserSql);
                if (log.isInfoEnabled()) {
                    log.warn("parser SQL: " + parserSql);
                }
            }
        } catch (JSQLParserException e) {
            log.error("Failed to process, Error SQL: {}", originalSql, e);
            throw ExceptionUtils.mpe("Failed to process, Error SQL: %s", e, originalSql);
        }
    }

    /**
     * 解析sql
     *
     * @param select
     */
    protected void processSelect(Select select) {
        // 处理sqlBody
        this.processSelectBody(select.getSelectBody());
        List<WithItem> withItemsList = select.getWithItemsList();
        if (!CollectionUtils.isEmpty(withItemsList)) {
            withItemsList.forEach(this::processSelectBody);
        }
    }

    /**
     * 处理sqlBody
     *
     * @param selectBody
     */
    protected void processSelectBody(SelectBody selectBody) {
        if (selectBody == null) {
            return;
        }
        if (selectBody instanceof PlainSelect) {
            // 处理 PlainSelect
            this.processPlainSelect((PlainSelect) selectBody);
        } else if (selectBody instanceof WithItem) {
            // With关键字
            WithItem withItem = (WithItem) selectBody;
            /**
             * jsqlparser 4.3版本 使用 {@code withItem.getSubSelect().getSelectBody())} 代替 {@code withItem.getSelectBody()}
             */
            processSelectBody(withItem.getSubSelect().getSelectBody());
        } else {
            // 集合操作 UNION(并集) MINUS(差集)
            SetOperationList operationList = (SetOperationList) selectBody;
            List<SelectBody> selectBodyList = operationList.getSelects();
            if (CollectionUtils.isNotEmpty(selectBodyList)) {
                selectBodyList.forEach(this::processSelectBody);
            }
        }
    }


    /**
     * 处理 PlainSelect
     *
     * @param plainSelect
     */
    protected void processPlainSelect(PlainSelect plainSelect) {
        DataScope dataScope = AbstractDataScopeContextHolder.get();
        if (dataScope != null) {
            try {
                dataScopeHandler.handlerDataScopeSql(plainSelect, dataScope);
            } catch (JSQLParserException e) {
                throw ExceptionUtils.mpe("Failed to process, Error SQL: %s", e);
            }
        }

    }


}

7、MybatisPlusAutoConfigure  把自定义的拦截器添加到MybatisPlus

@Configuration
public class MybatisPlusAutoConfigure {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(
                            DataScopeHandler dataScopeHandler) {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(
            new DataScopeInnerInterceptor(dataScopeHandler)
        );
        return interceptor;
    }
}

8、测试使用MybatisPlus写法,同样支持Mybatis 的xml中自己写sql,自己试一下吧。我自己都在用没问题

@DataScope(userColumn = "user_id")
public Map<String, Object> queryPage() {

	IPage<OperateLog> page = new Page<>(pageNo, pageSize);

	// 打印sql 查看,sql已经拼接 and user_id = '123'
	IPage<User> pageData = this.lambdaQuery().page(page);

	Map<String, Object> map = new HashMap<>();
	map.put("total", pageData.getTotal());
	map.put("list", pageData.getRecords());
	return map;
}

没有啰嗦理论,直接上干货,请提出宝贵意见共同改变世界^_^

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值