Mybatis拦截器之数据权限过滤与分页集成

您所在的位置:网站首页 permission注解 Mybatis拦截器之数据权限过滤与分页集成

Mybatis拦截器之数据权限过滤与分页集成

#Mybatis拦截器之数据权限过滤与分页集成| 来源: 网络整理| 查看: 265

解决方案之改SQL 原sql SELECT a.id AS "id", a.NAME AS "name", a.sex_cd AS "sexCd", a.org_id AS "orgId", a.STATUS AS "status", a.create_org_id AS "createOrgId" FROM pty_person a WHERE a. STATUS = 0

org_id是单位的标识,也就是where条件里再加个单位标识的过滤。

改后sql SELECT a.id AS "id", a.NAME AS "name", a.sex_cd AS "sexCd", a.org_id AS "orgId", a.STATUS AS "status", a.create_org_id AS "createOrgId" FROM pty_person a WHERE a. STATUS = 0 and a.org_id LIKE concat(710701070102, '%')

当然通过这个办法也可以实现数据的过滤,但这样的话相比大家也都有同感,那就是每个业务模块 每个人都要进行SQL改动,这次是根据单位过滤、明天又再根据其他的属性过滤,意味着要不停的改来改去,可谓是场面壮观也,而且这种集体改造耗费了时间精力不说,还会有很多不确定因素,比如SQL写错,存在漏网之鱼等等。因此这个解决方案肯定是直接PASS掉咯;

解决方案之拦截器

由于项目大部分采用的持久层框架是Mybatis,也是使用的Mybatis进行分页拦截处理,因此直接采用了Mybatis拦截器实现数据权限过滤。

1、自定义数据权限过滤注解 PermissionAop,负责过滤的开关  package com.raising.framework.annotation; import java.lang.annotation.*; /** * 数据权限过滤自定义注解 * @author lihaoshan * @date 2018-07-19 * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface PermissionAop { String value() default ""; } 2、定义全局配置 PermissionConfig 类加载 权限过滤配置文件 package com.raising.framework.config; import com.raising.utils.PropertiesLoader; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; /** * 全局配置 * 对应 permission.properties * @author lihaoshan */ public class PermissionConfig { private static Logger logger = LoggerFactory.getLogger(PropertiesLoader.class); /** * 保存全局属性值 */ private static Map map = new HashMap(16); /** * 属性文件加载对象 */ private static PropertiesLoader loader = new PropertiesLoader( "permission.properties"); /** * 获取配置 */ public static String getConfig(String key) { if(loader == null){ logger.info("缺失配置文件 - permission.properties"); return null; } String value = map.get(key); if (value == null) { value = loader.getProperty(key); map.put(key, value != null ? value : StringUtils.EMPTY); } return value; } } 3、创建权限过滤的配置文件 permission.properties,用于配置需要拦截的DAO的 namespace

(由于注解@PermissionAop是加在DAO层某个接口上的,而我们分页接口为封装的公共BaseDAO,所以如果仅仅使用注解方式开关拦截的话,会影响到所有的业务模块,因此需要结合额外的配置文件)

# 需要进行拦截的SQL所属namespace permission.intercept.namespace=com.raising.modules.pty.dao.PtyGroupDao,com.raising.modules.pty.dao.PtyPersonDao 4、自定义权限工具类 根据 StatementHandler 获取Permission注解对象: package com.raising.utils.permission; import com.raising.framework.annotation.PermissionAop; import org.apache.ibatis.mapping.MappedStatement; import java.lang.reflect.Method; /** * 自定义权限相关工具类 * @author lihaoshan * @date 2018-07-20 * */ public class PermissionUtils { /** * 根据 StatementHandler 获取 注解对象 * @author lihaoshan * @date 2018-07-20 */ public static PermissionAop getPermissionByDelegate(MappedStatement mappedStatement){ PermissionAop permissionAop = null; try { String id = mappedStatement.getId(); String className = id.substring(0, id.lastIndexOf(".")); String methodName = id.substring(id.lastIndexOf(".") + 1, id.length()); final Class cls = Class.forName(className); final Method[] method = cls.getMethods(); for (Method me : method) { if (me.getName().equals(methodName) && me.isAnnotationPresent(PermissionAop.class)) { permissionAop = me.getAnnotation(PermissionAop.class); } } }catch (Exception e){ e.printStackTrace(); } return permissionAop; } } 5、创建分页拦截器 MybatisSpringPageInterceptor 或进行改造(本文是在Mybatis分页拦截器基础上进行的数据权限拦截改造,SQL包装一定要在执行分页之前,也就是获取到原始SQL后就进行数据过滤包装) 

首先看数据权限拦截核心代码:

获取需要进行拦截的DAO层namespace拼接串; 获取当前mapped所属namespace; 判断配置文件中的namespace是否包含当前的mapped所属的namespace,如果包含则继续,否则直接放行; 获取数据权限注解对象,及注解的值; 判断注解值是否为DATA_PERMISSION_INTERCEPT,是则拦截、并进行过滤SQL包装,否则放行; 根据包装后的SQL查分页总数,不能使用原始SQL进行查询; 执行请求方法,获取拦截后的分页结果;

执行流程图:

拦截器源码: package com.raising.framework.interceptor; import com.raising.StaticParam; import com.raising.framework.annotation.PermissionAop; import com.raising.framework.config.PermissionConfig; import com.raising.modules.sys.entity.User; import com.raising.utils.JStringUtils; import com.raising.utils.UserUtils; import com.raising.utils.permission.PermissionUtils; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.statement.RoutingStatementHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.plugin.*; import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import java.util.Map; import java.util.Properties; /** * 分页拦截器 * @author GaoYuan * @author lihaoshan 增加了数据权限的拦截过滤 * @datetime 2017/12/1 下午5:43 */ @Intercepts({ @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }), @Signature(method = "query", type = Executor.class, args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) }) public class MybatisSpringPageInterceptor implements Interceptor { private static final Logger log = LoggerFactory.getLogger(MybatisSpringPageInterceptor.class); public static final String MYSQL = "mysql"; public static final String ORACLE = "oracle"; /**数据库类型,不同的数据库有不同的分页方法*/ protected String databaseType; @SuppressWarnings("rawtypes") protected ThreadLocal pageThreadLocal = new ThreadLocal(); public String getDatabaseType() { return databaseType; } public void setDatabaseType(String databaseType) { if (!databaseType.equalsIgnoreCase(MYSQL) && !databaseType.equalsIgnoreCase(ORACLE)) { throw new PageNotSupportException("Page not support for the type of database, database type [" + databaseType + "]"); } this.databaseType = databaseType; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { String databaseType = properties.getProperty("databaseType"); if (databaseType != null) { setDatabaseType(databaseType); } } @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public Object intercept(Invocation invocation) throws Throwable { // 控制SQL和查询总数的地方 if (invocation.getTarget() instanceof StatementHandler) { Page page = pageThreadLocal.get(); //不是分页查询 if (page == null) { return invocation.proceed(); } RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget(); StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate"); BoundSql boundSql = delegate.getBoundSql(); Connection connection = (Connection) invocation.getArgs()[0]; // 准备数据库类型 prepareAndCheckDatabaseType(connection); MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement"); String sql = boundSql.getSql(); /** 单位数据权限拦截 begin */ //获取需要进行拦截的DAO层namespace拼接串 String interceptNamespace = PermissionConfig.getConfig("permission.intercept.namespace"); //获取当前mapped的namespace String mappedStatementId = mappedStatement.getId(); String className = mappedStatementId.substring(0, mappedStatementId.lastIndexOf(".")); if(JStringUtils.isNotBlank(interceptNamespace)){ //判断配置文件中的namespace是否与当前的mapped namespace匹配,如果包含则进行拦截,否则放行 if(interceptNamespace.contains(className)){ //获取数据权限注解对象 PermissionAop permissionAop = PermissionUtils.getPermissionByDelegate(mappedStatement); if (permissionAop != null){ //获取注解的值 String permissionAopValue = permissionAop.value(); //判断注解是否开启拦截 if(StaticParam.DATA_PERMISSION_INTERCEPT.equals(permissionAopValue) ){ if(log.isInfoEnabled()){ log.info("数据权限拦截【拼接SQL】..."); } //返回拦截包装后的sql sql = permissionSql(sql); ReflectUtil.setFieldValue(boundSql, "sql", sql); } else { if(log.isInfoEnabled()){ log.info("数据权限放行..."); } } } } } /** 单位数据权限拦截 end */ if (page.getTotalPage() > -1) { if (log.isTraceEnabled()) { log.trace("已经设置了总页数, 不需要再查询总数."); } } else { Object parameterObj = boundSql.getParameterObject(); /// MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement"); queryTotalRecord(page, parameterObj, mappedStatement, sql,connection); } String pageSql = buildPageSql(page, sql); if (log.isDebugEnabled()) { log.debug("分页时, 生成分页pageSql......"); } ReflectUtil.setFieldValue(boundSql, "sql", pageSql); return invocation.proceed(); } else { // 查询结果的地方 // 获取是否有分页Page对象 Page page = findPageObject(invocation.getArgs()[1]); if (page == null) { if (log.isTraceEnabled()) { log.trace("没有Page对象作为参数, 不是分页查询."); } return invocation.proceed(); } else { if (log.isTraceEnabled()) { log.trace("检测到分页Page对象, 使用分页查询."); } } //设置真正的parameterObj invocation.getArgs()[1] = extractRealParameterObject(invocation.getArgs()[1]); pageThreadLocal.set(page); try { // Executor.query(..) Object resultObj = invocation.proceed(); if (resultObj instanceof List) { /* @SuppressWarnings({ "unchecked", "rawtypes" }) */ page.setResults((List) resultObj); } return resultObj; } finally { pageThreadLocal.remove(); } } } protected Page findPageObject(Object parameterObj) { if (parameterObj instanceof Page) { return (Page) parameterObj; } else if (parameterObj instanceof Map) { for (Object val : ((Map) parameterObj).values()) { if (val instanceof Page) { return (Page) val; } } } return null; } /** * * 把真正的参数对象解析出来 * Spring会自动封装对个参数对象为Map对象 * 对于通过@Param指定key值参数我们不做处理,因为XML文件需要该KEY值 * 而对于没有@Param指定时,Spring会使用0,1作为主键 * 对于没有@Param指定名称的参数,一般XML文件会直接对真正的参数对象解析, * 此时解析出真正的参数作为根对象 * * @param parameterObj * @return */ protected Object extractRealParameterObject(Object parameterObj) { if (parameterObj instanceof Map) { Map parameterMap = (Map) parameterObj; if (parameterMap.size() == 2) { boolean springMapWithNoParamName = true; for (Object key : parameterMap.keySet()) { if (!(key instanceof String)) { springMapWithNoParamName = false; break; } String keyStr = (String) key; if (!"0".equals(keyStr) && !"1".equals(keyStr)) { springMapWithNoParamName = false; break; } } if (springMapWithNoParamName) { for (Object value : parameterMap.values()) { if (!(value instanceof Page)) { return value; } } } } } return parameterObj; } protected void prepareAndCheckDatabaseType(Connection connection) throws SQLException { if (databaseType == null) { String productName = connection.getMetaData().getDatabaseProductName(); if (log.isTraceEnabled()) { log.trace("Database productName: " + productName); } productName = productName.toLowerCase(); if (productName.indexOf(MYSQL) != -1) { databaseType = MYSQL; } else if (productName.indexOf(ORACLE) != -1) { databaseType = ORACLE; } else { throw new PageNotSupportException("Page not support for the type of database, database product name [" + productName + "]"); } if (log.isInfoEnabled()) { log.info("自动检测到的数据库类型为: " + databaseType); } } } /** * * 生成分页SQL * * * @param page * @param sql * @return */ protected String buildPageSql(Page page, String sql) { if (MYSQL.equalsIgnoreCase(databaseType)) {


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3