老项目如何改造权限、数据权限系统(二 一 一AOP 实现)
AOP 实现:
1 序言
在这里,我们接着讲上一次的系统改造,我们需要自定义一个切面注解,来控制需要控制数据权限的接口。
主要思想就是 : - 去用户中心拿到 组织架构:谁 —管理一>谁; - 来查询数据和 人员绑定关系表: 什么(数据)一属于一>谁。 - 最终拿到 数据的ID 查实际数据
讯享网
2 实现
2.1 数据关系表
注意:
我这里是用的人员来控制数据权限的;有的是用的角色,看具体业务需求,如果是角色的话,就使用roleId啦实现
2.1.1撅个栗子🌰
讯享网CREATE TABLE `camera_permission` ( `id` int(11) NOT NULL AUTO_INCREMENT, `data_id` int(11) DEFAULT NULL COMMENT '数据id', `uuid` varchar(64) DEFAULT NULL, `tenant_code` varchar(255) DEFAULT NULL COMMENT '租户编码', `tenant_code_name` varchar(255) DEFAULT NULL COMMENT '租户名称', `app_code` varchar(255) DEFAULT NULL COMMENT '应用编码', `app_code_name` varchar(255) DEFAULT NULL COMMENT '应用名称', `org_code` varchar(255) DEFAULT NULL COMMENT '所属组织机构编码', `user_id` varchar(255) DEFAULT NULL COMMENT '用户id', `user_name` varchar(255) DEFAULT NULL COMMENT '用户名称', `org_code_name` varchar(255) DEFAULT NULL COMMENT '应用名称', `created_at` datetime DEFAULT NULL COMMENT '创建时间', `last_modified` datetime DEFAULT NULL COMMENT '最后更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
2.2 定义一个AOP注解
/ * 数据权限过滤自定义注解 * @author Mc * @date 2021-04-19 * */ @Target({METHOD}) @Retention(RUNTIME) @Documented public @interface PermissionAop { String value() default "required"; //需要权限控制 PermissionMethodEnum method() default PermissionMethodEnum.ELEMENT; int type() default 3; //控制的粗细力度 1 到应用 2是到租户 3是到人员 4是到角色 }
2.3 定义一个方法的枚举(用于在切面当中辨别 是那个业务模块在控制权限)
讯享网import lombok.Getter; / * @Author Mc * @Date 2021/4/7 上午11:23 * @Version 1.0 */ @Getter public enum PermissionMethodEnum { / * 监控有数据权限 */ CAMERA(2, "camera"), / * 传感器有数据权限 */ SENSOR(3, "sensor"); private Integer type; private String name; PermissionMethodEnum(Integer type, String name) { this.type = type; this.name = name; } public static PermissionMethodEnum getEnumByType(int type) { for (PermissionMethodEnum value : values()) { if (value.getType() == type) { return value; } } return null; } }
2.4 定义切面
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.google.common.collect.Lists; import com.paramland.smartmanger.config.mybatis.PermissionContext; import com.paramland.smartmanger.entity.*; import com.paramland.smartmanger.service.*; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import java.util.*; import java.util.stream.Collectors; @Aspect @Component @Lazy(false) @Slf4j public class PermissionAspect { @Autowired ICameraPermissionService iCameraPermissionService; @Autowired ISensorPermissionService iSensorPermissionService; @Pointcut("execution(public * *(..)) && @annotation(com.demo.spring.common.permission.PermissionAop) ") public void require() {} @Before("require() && @annotation(sv)") public void demoBefore(JoinPoint joinPoint, PermissionAop sv) throws Exception { String orgCode = "null"; String userName = "null"; List<Integer> idsList = null; Object[] args = joinPoint.getArgs(); String tableName =""; if(args!=null && args.length>0){ Object arg = args[0]; if (arg instanceof Map) { String s = JSON.toJSONString(arg); JSONObject parseObject = JSONObject.parseObject(s); if (parseObject.getString("orgCode") != null) { orgCode = parseObject.getString("orgCode"); } if (parseObject.getString("userName") != null) { userName = parseObject.getString("userName"); } if (parseObject.getJSONArray("ids") != null) { JSONArray ids = parseObject.getJSONArray("ids"); idsList = ids.toJavaList(Integer.class); } if (parseObject.getString("tableName") != null) { tableName=parseObject.getString("tableName"); } } } System.out.println(args.toString()); long id = Thread.currentThread().getId(); System.out.println("+++++++++++++++++++++ "+id+" +++++++++++++++++++"); PermissionMethodEnum method = sv.method(); int type = sv.type(); String value = sv.value(); //在这里调用用户中心 获取人员 List<String> userIds = getUserIds(); switch (method) { case CAMERA :{ QueryWrapper<CameraPermission> cameraPermissionQueryWrapper = new QueryWrapper<>(); createWrapper(cameraPermissionQueryWrapper,type,userIds,orgCode,userName); List<CameraPermission> list = iCameraPermissionService.list(cameraPermissionQueryWrapper); if (list!=null && !list.isEmpty()) { List<Integer> collect = list.stream().map(CameraPermission::getDataId).collect(Collectors.toList()); if(CollectionUtils.isNotEmpty(idsList)){ collect.retainAll(idsList); } setDataIds(id,collect,value); } break; } case SENSOR :{ QueryWrapper<SensorPermission> sensorPermissionQueryWrapper = new QueryWrapper<>(); createWrapper(sensorPermissionQueryWrapper,type,userIds,orgCode,userName); List<SensorPermission> list = iSensorPermissionService.list(sensorPermissionQueryWrapper); if (list!=null && !list.isEmpty()) { List<Integer> collect = list.stream().map(SensorPermission::getDataId).collect(Collectors.toList()); if(CollectionUtils.isNotEmpty(idsList)){ collect.retainAll(idsList); } setDataIds(id,collect,value); } break; } default:{ break; } } //PermissionContext permissionContext1 = RecorderUtils.get(id); // System.out.println("@before 开始"); // System.out.println("注解参数:"+sv.value()+":"); // System.out.println("目标方法的参数对象:" + Arrays.toString(joinPoint.getArgs())); // System.out.println("被代理的对象:" + joinPoint.getTarget()); // System.out.println("代理对象:" + joinPoint.getThis()); // System.out.println("目标方法名为:" + joinPoint.getSignature().getName()); // System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName()); // System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName()); // System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers())); } private List<Integer> getUserIds(){ //在这里调用用户中心 获取人员 return null; } //记录数据 用于切面传递(这个是重点) private synchronized void setDataIds(long id, List<?> co, String value){ List<?> collect = co.stream().distinct().collect(Collectors.toList()); PermissionContext permissionContext = new PermissionContext(); permissionContext.setRe(value); permissionContext.setDataIds(collect); System.out.println("+++++++++++++++++++++ "+id+" +++++++++++++++++++"); PermissionDataContext.permissionData.set(permissionContext); } //创建查询条件 控制粗细力度 private void createWrapper(QueryWrapper<?> clazzQueryWrapper,int type, List<String> code,String orgCode,String userName){ switch (type) { case 1 :{ clazzQueryWrapper.in("tenant_code",code); break; } case 2 :{ clazzQueryWrapper.in("org_code",orgCode); break; } case 3 :{ if(!"null".equals(orgCode)){ clazzQueryWrapper.eq("org_code",orgCode); } if (!"null".equals(userName)) { clazzQueryWrapper.like("user_name",userName); } else { clazzQueryWrapper.in("user_id",code).or().eq("user_id",null).or().eq("org_code",null); } break; } case 4 :{ clazzQueryWrapper.in("role_id",code); break; } default:{ break; } } } @After("require() && @annotation(sv)") public void demoAfter(JoinPoint joinPoint, PermissionAop sv) { long id = Thread.currentThread().getId(); System.out.println("+++++++++++++++++++++ "+id+" +++++++++++++++++++"); PermissionContext permissionContext = RecorderUtils.get(id); if (permissionContext!=null){ RecorderUtils.remove(id); } //.... } }
2.4 使用 ThreadLocal 传递全局参数
注意:
我自己之前写了一个全局类 来传递参数 使用的currentHashMap 实现的感兴趣的可以 聊聊 这里 推荐使用下面这个 比较简单 ;
讯享网/ * @Author Mc * @Date 2021/4/23 上午10:51 * @Version 1.0 */ public class PermissionDataContext { public static ThreadLocal permissionData = new ThreadLocal(); }
3 总结
AOP的 暂时实现就到此结束 里面还是有些细节需要注意 涉及到实际业务 就不做公示了,下面说说里面的技术要点
3.1 切面如何取到 方法的参数 ?
//这个就可以取到 Object[] args = joinPoint.getArgs(); //传参类型不一样 获取要用不一样的来装 Object arg = args[0];
3.2 ThreadLocal 如何使用?
3.2.1概念
讯享网1、ThreadLocal提供线程局部变量。这些变量与普通的变量不同之处在于,每个访问这种变量的线程(通过它的get或set方法)都有自己的、独立初始化的变量副本。 2、ThreadLocal实例通常是希望将状态关联到一个线程的类的私有静态字段(比如,user ID 或者 Transaction ID 等等)。 3、ThreadLocal是一种变量类型,我们称之为“线程局部变量” 4、每个线程访问这种变量的时候都会创建该变量的副本,这个变量副本为线程私有 5、ThreadLocal类型的变量一般用private static加以修饰
3.2.2 ThreadLocal 主要操作: get set remove
/ * 返回当前线程对ThreadLocal变量的“初始值” * 这个方法将在线程第一次访问变量(通过调用get方法)时被调用,如果之前已经调用过了就不会再调了 * * @return the initial value for this thread-local */ protected T initialValue() { return null; } / * 设置当前线程的ThreadLocal变量的副本为指定的值 * * @param value the value to be stored in the current thread's copy of this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } / * 返回当前线程的ThreadLocal变量副本的值 * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } / * 删除当前线程的ThreadLocal变量副本的值 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
3.3 AOP 的主要知识点:
3.3.1Spring AOP术语
通知(Advice)包含了需要用于多个应用对象的横切行为,完全听不懂,没关系,通俗一点说就是定义了“什么时候”和“做什么”。
连接点(Join Point)是程序执行过程中能够应用通知的所有点。
切点(Poincut)是定义了在“什么地方”进行切入,哪些连接点会得到通知。显然,切点一定是连接点。
切面(Aspect)是通知和切点的结合。通知和切点共同定义了切面的全部内容——是什么,何时,何地完成功能。
引入(Introduction)允许我们向现有的类中添加新方法或者属性。
织入(Weaving)是把切面应用到目标对象并创建新的代理对象的过程,分为编译期织入、类加载期织入和运行期织入。
3.3.2 Spring Boot使用AOP需要添加spring-boot-starter-aop依赖,如下:
讯享网<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
作用是把当前类标识为一个切面供容器读取
@AfterReturning
后置增强,相当于AfterReturningAdvice,方法退出时执行
@AfterThrowing
异常抛出增强,相当于ThrowsAdvice
@After
final增强,不管是抛出异常或者正常退出都会执行
@Around
环绕增强,相当于MethodInterceptor
3.3.4:关于切面PointCut的切入点
execution切点函数
execution函数用于匹配方法执行的连接点,语法为:
execution(方法修饰符(可选) 返回类型 方法名 参数 异常模式(可选))
参数部分允许使用通配符:
- 匹配任意字符,但只能匹配一个元素
… 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用
- 必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类
除了execution(),Spring中还支持其他多个函数,这里列出名称和简单介绍,以方便根据需要进行更详细的查询
@annotation()
表示标注了指定注解的目标类方法

例如 @annotation(org.springframework.transaction.annotation.Transactional) 表示标注了@Transactional的方法
args()
通过目标类方法的参数类型指定切点
例如 args(String) 表示有且仅有一个String型参数的方法
@args()
通过目标类参数的对象类型是否标注了指定注解指定切点
如 @args(org.springframework.stereotype.Service) 表示有且仅有一个标注了@Service的类参数的方法
within()
通过类名指定切点
如 with(examples.chap03.Horseman) 表示Horseman的所有方法
target()
通过类名指定,同时包含所有子类
如 target(examples.chap03.Horseman) 且Elephantman extends Horseman,则两个类的所有方法都匹配
@within()
匹配标注了指定注解的类及其所有子类
如 @within(org.springframework.stereotype.Service) 给Horseman加上@Service标注,则Horseman和Elephantman 的所有方法都匹配
@target()
所有标注了指定注解的类
如 @target(org.springframework.stereotype.Service) 表示所有标注了@Service的类的所有方法
this()
大部分时候和target()相同,区别是this是在运行时生成代理类后,才判断代理类与指定的对象类型是否匹配
逻辑运算符
表达式可由多个切点函数通过逻辑运算组成
&&
与操作,求交集,也可以写成and
例如 execution(* chop(…)) && target(Horseman) 表示Horseman及其子类的chop方法
||
或操作,求并集,也可以写成or
例如 execution(* chop(…)) || args(String) 表示名称为chop的方法或者有一个String型参数的方法
!
非操作,求反集,也可以写成not
例如 execution(* chop(…)) and !args(String) 表示名称为chop的方法但是不能是只有一个String型参数的方法
execution常用于匹配特定的方法,如update时怎么处理,或者匹配某些类,如所有的controller类,是一种范围较大的切面方式,多用于日志或者事务处理等。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/121351.html