自定义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;


/**
 * 

* SqlInterceptor *

* Description: *

* org.apache.ibatis.executor.Executor; * 是 Mybatis 的内部执行器,它负责调用 StatementHandler 操作数据库,并把结果集通过 ResultSetHandler 进行自动映射,另外,它还处理了二级缓存的操作。 * Executor创建StatementHandler对象, * 同时,创建ParameterHandler和ResultSetHandler对象,而ParameterHandler和ResultSetHandler都依赖TypeHandler; * *

* org.apache.ibatis.executor.statement.StatementHandler; * 是 Mybatis 直接和数据库执行 sql 脚本的对象,另外,它也实现了 Mybatis 的一级缓存。 * *

* org.apache.ibatis.executor.parameter.ParameterHandler; * 是 Mybatis 实现 sql 入参设置的对象。 * *

* 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 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 *

修改sql核心方法

*

各系统可根据业务实际情况重写此方法

*

异常处理,根据实际情况抛出异常

* * @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;

/**
 * 

* Permission *

* 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;

/**
 * 

* PermissionData *

* 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 sqlKeyValueList; public PermissionData(String currentUserAccount) { this.currentUserAccount = currentUserAccount; } public PermissionData(String currentUserAccount, List 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;

/**
 * 

* PermissionRule *

* 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;

/**
 * 

* QueryRuleEnum *

* 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 插件直接返回的数据  如果不是优先执行会导致自定义拦截器不生效


注意:pagehaper插件版本   4.1.6  与 4.2.1  差异较大 本插件依赖4.2.1版本

	com.github.pagehelper
	pagehelper
	4.2.1



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

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

 

你可能感兴趣的:(自定义SQL拦截器)