需求场景
最近项目有个数据权限的业务需求,要求大致为每个单位只能查看本级单位及下属单位的数据,例如:一个集团军下属十二个旅,那么军级用户可以看到所有数据,而每个旅则只能看到本旅部的数据,以此类推;
解决方案之改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<String, String> 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;
}
}