自定义SQL拦截器

package com.demo.common.interceptor;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.demo.common.annotation.Permission;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;


/**
 * <p>
 * <code>SqlInterceptor</code>
 * </p>
 * Description:
 * <p>
 * org.apache.ibatis.executor.Executor;
 * 是 Mybatis 的内部执行器,它负责调用 StatementHandler 操作数据库,并把结果集通过 ResultSetHandler 进行自动映射,另外,它还处理了二级缓存的操作。
 * Executor创建StatementHandler对象,
 * 同时,创建ParameterHandler和ResultSetHandler对象,而ParameterHandler和ResultSetHandler都依赖TypeHandler;
 *
 * <p>
 * org.apache.ibatis.executor.statement.StatementHandler;
 * 是 Mybatis 直接和数据库执行 sql 脚本的对象,另外,它也实现了 Mybatis 的一级缓存。
 *
 * <p>
 * org.apache.ibatis.executor.parameter.ParameterHandler;
 * 是 Mybatis 实现 sql 入参设置的对象。
 *
 * <p>
 * org.apache.ibatis.executor.resultset.ResultSetHandler;
 * 是 Mybatis 把 ResultSet 集合映射成 POJO 的接口对象。处理查询结果集;
 *
 * @date 2020-09-22 下午 01:46
 */
@Slf4j
@SuppressWarnings("rawtypes")
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PermissionSqlInterceptor<c> implements Interceptor {

    /** mapper层数据权限  @Param 注解的value值 */
    public static final String PARAM_VALUE_PERMISSION_DATA = "PD";

    /** 忽略不存在的字段 */
    static ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取拦截方法的参数
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];

        // 获取 Class Method
        String id = ms.getId();
        String clazzName = id.substring(0, id.lastIndexOf('.'));
        String mapperMethod = id.substring(id.lastIndexOf('.') + 1);
        Class<?> clazz = Class.forName(clazzName);
        Method[] methods = clazz.getMethods();

        // 是否有Permission注解 false:无;true:有
        boolean hasPermissionAnnotation = false;
        if (!ArrayUtils.isEmpty(methods)) {
            for (Method method : methods) {
                String methodName = method.getName();
                if (mapperMethod.equals(methodName)) {
                    Permission permission = method.getAnnotation(Permission.class);
                    if (permission != null && permission.usePermission()) {
                        hasPermissionAnnotation = true;
                    }
                    break;
                }
            }
        }

        if (hasPermissionAnnotation) {
            Object parameterObject = args[1];
            BoundSql boundSql = ms.getBoundSql(parameterObject);
            String oldSql = boundSql.getSql();

            Map methodParamMap = (Map) parameterObject;
            PermissionData permissionData = (PermissionData) methodParamMap.get(PARAM_VALUE_PERMISSION_DATA);
            // 核心方法  拼接sql
            String newSql = modifySql(oldSql, permissionData);
            // 重新new一个查询语句对像
            BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
            // 把新的查询放到statement里
            MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
            for (ParameterMapping mapping : boundSql.getParameterMappings()) {
                String prop = mapping.getProperty();
                if (boundSql.hasAdditionalParameter(prop)) {
                    newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
                }
            }
            // 重要!!  重新赋值  把值传递到下去
            args[0] = newMs;
            // 如果直接返回executor.query() 会导致PageHelper等其他拦截器生效
        }
        // 其作用是将拦截器责任链向后传递
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

    /**
     * TODO
     * <p>修改sql核心方法</p>
     * <p>各系统可根据业务实际情况重写此方法</p>
     * <p>异常处理,根据实际情况抛出异常</p>
     *
     * @param oldSql
     * @param data
     * @return
     * @date 2020-09-22 下午 06:19
     */
    protected String modifySql(String oldSql, PermissionData data) throws Exception {
        String permissionSql = StringUtils.EMPTY;

        if (data == null) {
            throw new NullPointerException("==> PermissionSqlInterceptor.modifySql,参数错误");
        }

        String id = data.getPermissionId();
        if (StringUtils.isBlank(id)) {
            throw new NullPointerException("==> PermissionSqlInterceptor.modifySql,菜单ID为空");
        }

        String userAccount = data.getCurrentUserAccount();
        if (StringUtils.isBlank(userAccount)) {
            throw new NullPointerException("==> PermissionSqlInterceptor.modifySql,当前登录人为空");
        }
		
		// TODO  各系统可根据业务实际情况重写此方法


        return "select * from ( " + oldSql + " ) tmp_permission " + permissionSql;
    }


    /**
     * 重新生成一个 MappedStatement
     *
     * @param ms
     * @param newSqlSource
     * @return
     * @date 2020-09-23 下午 02:26
     */
    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    public static class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}
package com.demo.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * <p>
 * <code>Permission</code>
 * </p>
 * Description: 权限注解
 *
 * @date 2020-09-22 下午 02:03
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Permission {
    /** 是否使用数据权限 */
    boolean usePermission() default true;

}
package com.demo.common.interceptor;

import com.google.common.collect.Lists;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;
import java.util.List;

/**
 * <p>
 * <code>PermissionData</code>
 * </p>
 * Description: 数据权限动态sql mapper层传递数据实体
 *
 * @date 2020-09-25 上午 09:15
 */
@Data
public class PermissionData implements Serializable {

    private static final long serialVersionUID = 4982167779930712419L;
    /** 当前登录人账号,必填 */
    private String currentUserAccount;
    /** 菜单id,必填 */
    private String permissionId;
    /** 动态sql数据,非必填 */
    private List<SqlKeyValue> sqlKeyValueList;

    public PermissionData(String currentUserAccount) {
        this.currentUserAccount = currentUserAccount;
    }

    public PermissionData(String currentUserAccount, List<SqlKeyValue> sqlKeyValueList) {
        this.currentUserAccount = currentUserAccount;
        this.sqlKeyValueList = sqlKeyValueList;
    }

    public void clear() {
        this.currentUserAccount = StringUtils.EMPTY;
        this.sqlKeyValueList = Lists.newArrayList();
    }

    public static PermissionData empty() {
        return new PermissionData(StringUtils.EMPTY, Lists.newArrayList());
    }

    @Data
    public static class SqlKeyValue implements Serializable {
        private static final long serialVersionUID = 8321308244032368498L;
        /** 动态sql的key,与uap中数据权限规则的字段名匹配 */
        private String key;
        /** 动态sql的value值 */
        private Object value;

        public SqlKeyValue(String key, Object value) {
            this.key = key;
            this.value = value;
        }
    }
}
package com.demo.common.interceptor;

import lombok.Data;

import java.io.Serializable;

/**
 * <p>
 * <code>PermissionRule</code>
 * </p>
 * Description:
 *
 * @date 2020-09-26 下午 03:44
 */
@Data
public class PermissionRule implements Serializable {
    private static final long serialVersionUID = 2291165317076536867L;
    /** 规则唯一id */
    private String id;
    /** 对应的菜单id */
    private String permissionId;
    /** 规则名称 */
    private String ruleName;
    /** 字段 */
    private String ruleColumn;
    /** 条件 */
    private String ruleConditions;
    /** 规则值 */
    private String ruleValue;
    /** 状态值   1:有效     0:无效 */
    private String status;
}
package com.demo.common.interceptor;

import org.apache.commons.lang3.StringUtils;

/**
 * <p>
 * <code>QueryRuleEnum</code>
 * </p>
 * Description: Query 规则 常量
 * @date 2020-09-27 下午 04:50
 */
public enum QueryRuleEnum {

    GT(">", "大于"),
    GE(">=", "大于等于"),
    LT("<", "小于"),
    LE("<=", "小于等于"),
    EQ("=", "等于"),
    NE("!=", "不等于"),
    IN("IN", "包含"),
    LIKE("LIKE", "全模糊"),
    LEFT_LIKE("LEFT_LIKE", "左模糊"),
    RIGHT_LIKE("RIGHT_LIKE", "右模糊"),
    SQL_RULES("USE_SQL_RULES", "自定义SQL片段");

    private String value;


    private String msg;

    QueryRuleEnum(String value, String msg) {
        this.value = value;
        this.msg = msg;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public static QueryRuleEnum getByValue(String value) {
        if (StringUtils.isBlank(value)) {
            return null;
        }
        for (QueryRuleEnum val : values()) {
            if (val.getValue().equals(value)) {
                return val;
            }
        }
        return null;
    }
}
使用方法如下:

修改配置文件mybatis-config.xml,plugins标签的最后 添加以下,放在PageHelper插件后面

 放在第一位  必须优先执行  因为PageHelper 插件直接返回的数据  如果不是优先执行会导致自定义拦截器不生效
<plugin interceptor="com.demo.common.interceptor.PermissionSqlInterceptor"/>

注意:pagehaper插件版本   4.1.6  与 4.2.1  差异较大 本插件依赖4.2.1版本
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper</artifactId>
	<version>4.2.1</version>
</dependency>


使用示例如下,注解加到mapper上

    /**
     * 根据搜索条件获取人员列表
     *
     * @param name 姓名/账号
     * @return
     * @date 2019-04-11 下午 05:07
     */
    @Permission
    List<AdEmployee> getByName(@Param(PermissionSqlInterceptor.PARAM_VALUE_PERMISSION_DATA) PermissionData permissionData,
                               @Param("name") String name, @Param("emp") AdEmployee employee);

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis-Plus是一个基于MyBatis的增强工具,提供了许多便捷的功能来简化开发。自定义拦截器是MyBatis-Plus提供的一项功能,可以在SQL执行前后进行一些自定义的操作。 要增加自定义拦截器,你需要按照以下步骤进行操作: 1. 创建一个实现了`Interceptor`接口的自定义拦截器类,该接口定义了拦截器的核心方法`intercept`和`plugin`。 2. 在`intercept`方法中,你可以编写自己的逻辑来处理SQL执行前后的操作。例如,你可以在SQL执行前打印日志,或者在SQL执行后对结果进行处理。 3. 在`plugin`方法中,你需要使用`Plugin`类的`wrap`方法来包装你的自定义拦截器,并返回一个新的代理对象。这个代理对象会拦截MyBatis的方法调用,并在适当的时机调用你的自定义拦截器的方法。 4. 在MyBatis的配置文件中,通过`<plugins>`标签配置你的自定义拦截器。将你的自定义拦截器添加到MyBatis的拦截器链中。 下面是一个示例代码,演示了如何增加自定义拦截器: ```java public class CustomInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 在SQL执行前的逻辑处理 System.out.println("Before executing SQL"); // 执行原始的方法调用 Object result = invocation.proceed(); // 在SQL执行后的逻辑处理 System.out.println("After executing SQL"); return result; } @Override public Object plugin(Object target) { // 使用Plugin类的wrap方法包装自定义拦截器,并返回一个新的代理对象 return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 可以在这里设置一些属性 } } ``` 在MyBatis的配置文件中,添加以下配置: ```xml <plugins> <plugin interceptor="com.example.CustomInterceptor"/> </plugins> ``` 这样,你就成功地增加了一个自定义拦截器。当MyBatis执行SQL时,你的自定义拦截器的`intercept`方法会被调用,并执行你定义的逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值