/**
* Copyright 2018 耘林养老 http://www.crm.cn
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.crm.common.aspect;
import io.crm.common.annotation.DataFilter;
import io.crm.common.entity.sys.SysUserEntity;
import io.crm.common.exception.RRException;
import io.crm.common.utils.Constant;
import io.crm.modules.sys.service.SysDeptService;
import io.crm.modules.sys.service.SysRoleDeptService;
import io.crm.modules.sys.shiro.ShiroUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 数据过滤,切面处理类
*
* @author zoushaohua@sportsii.cn
* @since 3.0.0 2018-09-17
*/
@Aspect
@Component
public class DataFilterAspect {
@Autowired
private SysDeptService sysDeptService;
@Autowired
private SysRoleDeptService sysRoleDeptService;
@Pointcut("@annotation(io.crm.common.annotation.DataFilter)")
public void dataFilterCut() {
}
@Before("dataFilterCut()")
public void dataFilter(JoinPoint point) throws Throwable {
// null值演示
String s=null;
System.out.println(s);
// 1 目标方法的入参被封装到了point对象中,通过下面的getArgs 可以得到目标方法的入参
Object params = point.getArgs()[0];
System.out.println("0>>>>>"+params);
Object[] args = point.getArgs();
for (Object arg : args) {
System.out.println("1>>>>>"+arg);
}
// 2 通过反射获取目标方法,以及目标方法所在的类的相关信息
Signature signature = point.getSignature();
System.out.println("2>>>>>"+signature);
String name = point.getSignature().getName();
System.out.println("2.1目标方法名为:>>>>>"+name);
System.out.println("2.2目标方法所属类的简单类名:" + point.getSignature().getDeclaringType().getSimpleName());
System.out.println("2.3目标方法所属类的类名:" +point.getSignature().getDeclaringTypeName());
// 3 获取被代理对象
Object target = point.getTarget();
System.out.println("3被代理的对象:>>>>>"+target);
// 4 获取代理对象,获取代理对象自己
Object aThis = point.getThis();
System.out.println("4代理对象自己:>>>>>"+aThis);
//所谓代理类其实就是内部调用了目标类的方法,两个不同的对象内存地址不可能一样,上面的目标类和代理类打印调用的是toString(),但是代理类
// 调用的其实是目标类的toString,所以toString一样,但是用==比较是不一样的,也就是false。如下面的方法验证所示
System.out.println(target == aThis);
// 这里做判断的意思是:目标方法必须要有入参,并且入参的类型是map类型,更为特殊的情况是,入参是map,但是map里面为空,也符合要求
// 如上面演示所示
if (params != null && params instanceof Map) {
SysUserEntity user = ShiroUtils.getUserEntity();
//如果不是超级管理员,则进行数据过滤
if (user.getUserId() != Constant.SUPER_ADMIN) {
// 上面验证params 是map的一个实例,类型进行强转
Map map = (Map) params;
Set<Long> deptIdList = new HashSet<>();
// 把数据过滤的sql(String字符串) 放入 请求参数map中去
map.put(Constant.SQL_FILTER, getSQLFilter(user, point, deptIdList));
// 把部门权限 list 放入 请求参数map中去
map.put(Constant.DEPT_AUTH, deptIdList);
}
return;
}
throw new RRException("数据权限接口,只能是Map类型参数,且不能为NULL");
}
/**
* 获取数据过滤的SQL
*/
private String getSQLFilter(SysUserEntity user, JoinPoint point, Set<Long> deptIdList) {
// 获取切点的方法签名
MethodSignature signature = (MethodSignature) point.getSignature();
// 5 根据方法签名对象获取方法对象
Method method = signature.getMethod();
System.out.println("++++++++++"+method);
DataFilter dataFilter = signature.getMethod().getAnnotation(DataFilter.class);
//调用注解类下面的方法,获取表的别名
String tableAlias = dataFilter.tableAlias();
if (StringUtils.isNotBlank(tableAlias)) {
tableAlias += ".";
}
// 如果设定该用户拥有角色对应的部门权限
if (dataFilter.roleDept()) {
// 获取根据用户对应的角色的对应的部门list
deptIdList.addAll(sysRoleDeptService.queryDeptIdList(user.getUserId()));
}
//用户子部门ID列表,2018年7月13号注释,解决可查询该用户权限不仅仅只受角色控制的问题,用户
//查询该用户所属部门列表后会将用户所属的部门列表也查询出来导致数据权限混乱
// 如果设定该用户拥有查看子部门数据的权限
if (dataFilter.subDept()) {
//用户角色对应的部门及其子部门ID列表(通过自定义函数获取,与通过嵌套查询获取的list数据,这里的所有list的element都是同级的,没有嵌套关系)
List<Long> subDeptIdList = sysDeptService.getSubDeptIdList(user.getDeptId());
deptIdList.addAll(subDeptIdList);
}
// 如果该用户不能查看任何部门数据,并且设定不能查看本人数据,返回“1=2” 返回作为数据库拼接的判定条件为否,不进行数据过滤
if (deptIdList.size() < 1 && !dataFilter.user()) {
return "1=2";
}
StringBuilder sqlFilter = new StringBuilder();
sqlFilter.append(" (");
if (deptIdList.size() > 0) {
// sql 语句的拼接 "dept_id in ((1,2,4,21,10,11,13)
sqlFilter.append(tableAlias).append(dataFilter.deptId()).append(" in (").append(StringUtils.join(deptIdList, ",")).append(")");
}
//没有本部门数据权限,也能查询本人数据 和上面的if 的补充,上面的deptIdList.size() < 1 具有条件隔离作用
if (dataFilter.user()) {
if (deptIdList.size() > 0) {
sqlFilter.append(" or ");
}
// sql语句的拼接" or user_Id=1
sqlFilter.append(tableAlias).append(dataFilter.userId()).append("=").append(user.getUserId());
}
sqlFilter.append(")");
// 根据不同的条件拼接出不同的条件语句
return sqlFilter.toString();
}
}
运行后结果:
0>>>>>{}
1>>>>>{}
2>>>>>List io.crm.modules.sys.service.impl.SysDeptServiceImpl.queryList(Map)
2.1目标方法名为:>>>>>queryList
2.2目标方法所属类的简单类名:SysDeptServiceImpl
2.3目标方法所属类的类名:io.crm.modules.sys.service.impl.SysDeptServiceImpl
3被代理的对象:>>>>>io.crm.modules.sys.service.impl.SysDeptServiceImpl@ddb84cb
4代理对象自己:>>>>>io.crm.modules.sys.service.impl.SysDeptServiceImpl@ddb84cb
false
5 获取方法对象++++++++++public java.util.List io.crm.modules.sys.service.impl.SysDeptServiceImpl.queryList(java.util.Map)
参考:JoinPoint的用法