前言:自定义注解,通过aop切面前置通知,对请求接口进行权限控制
1,创建枚举类
package org.springblade.sample.annotationCommon;import lombok.AllArgsConstructor;
import lombok.Getter;import java.util.Arrays;
import java.util.Optional;/*** @Title: PermissionAnnotationEnum* @Author it—xtm* @Package AnnotationCommon* @Date 2025/8/5 21:21* @description: 权限枚举类,定义系统中常用的权限控制类型*/
@Getter
@AllArgsConstructor
public enum PermissionAnnotationEnum {/*** 全部权限:可以查看所有数据*/ALL(1, "全部数据可见"),/*** 仅本人可见:只能查看自己创建的数据*/OWN(2, "仅本人可见"),/*** 本部门可见:只能查看本部门数据*/OWN_DEPT(3, "所在机构可见"),/*** 本部门及子部门可见*/OWN_DEPT_CHILD(4, "所在机构及子级机构可见"),/*** 自定义权限:根据自定义条件过滤数据*/CUSTOM(5, "自定义权限范围"),/*** 无权限:不能查看任何数据*/NONE(6, "无权限访问");/*** 权限类型编码*/private final Integer type;/*** 权限描述*/private final String description;/*** 根据类型编码获取枚举实例** @param type 权限类型编码* @return 对应的枚举实例,若不存在则返回空*/public static PermissionAnnotationEnum getByType(Integer type) {if (type == null) {return null;}return Arrays.stream(values()).filter(enumItem -> enumItem.getType().equals(type)).findFirst().orElse(null);}}
2,创建注解
package org.springblade.sample.annotationCommon;import java.lang.annotation.*;
/*** @Title: PermissionAnnotationEnum* @Author it—xtm* @Package AnnotationCommon* @Date 2025/8/5 21:21* @description: 权限注解*/@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited // 允许注解被子类继承
@Documented // 生成JavaDoc时会包含该注解说明
public @interface PermissionAnnotation {PermissionAnnotationEnum type() default PermissionAnnotationEnum.ALL; //权限 类型String[] menuValue();// 需要的菜单编号标识String apiValue();// 需要的api标识boolean isIgnoreRole() default false;// 是否忽略String[] ignoreRoleValue() default {"administrator", "admin"}; //管理员直接忽略}
3,创建切面
package org.springblade.sample.annotationCommon;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;import java.util.Arrays;/*** @Title: AnnotationCommon.AnnotationAspect* @Author it-xtm* @Package PACKAGE_NAME* @Date 2025/8/5 21:45* @description: 权限注解切面,包含各种通知类型*/
@Aspect
@Component
@Slf4j
public class AnnotationAspect {/*** 方法执行前执行 - 前置通知* @param joinPoint 切入点对象,提供了关于当前执行方法的信息* @param permissionAnnotation 注解对象,包含了注解的属性值*/@Before("@annotation(permissionAnnotation)")public void before(JoinPoint joinPoint, PermissionAnnotation permissionAnnotation) {log.info("===== 前置通知开始 =====");log.info("目标方法: {}.{}",joinPoint.getTarget().getClass().getName(),joinPoint.getSignature().getName());log.info("方法参数:{}", Arrays.toString(permissionAnnotation.menuValue()));log.info("方法参数:{}", permissionAnnotation.type());log.info("方法参数:{}", permissionAnnotation.apiValue());log.info("方法参数:{}", Arrays.toString(permissionAnnotation.ignoreRoleValue()));log.info("方法参数:{}", permissionAnnotation.isIgnoreRole());log.info("方法参数:{}", permissionAnnotation.type().getType());log.info("方法参数:{}", permissionAnnotation.type().getDescription());log.info("方法参数:{}", PermissionAnnotationEnum.getByType(permissionAnnotation.type().getType()).getDescription());log.info("===== 前置通知结束 =====");}/*** 环绕通知 - 可以控制目标方法的执行* @param proceedingJoinPoint 可执行的切入点对象* @param permissionAnnotation 注解对象* @return 目标方法的返回值* @throws Throwable 可能抛出的异常*/@Around("@annotation(permissionAnnotation)")public Object around(ProceedingJoinPoint proceedingJoinPoint, PermissionAnnotation permissionAnnotation) throws Throwable {log.info("===== 环绕通知开始 =====");log.info("环绕通知 - 执行目标方法前");// 可以在这里进行权限验证等逻辑boolean hasPermission = checkPermission(permissionAnnotation);if (!hasPermission) {log.warn("权限不足,无法执行方法: {}", proceedingJoinPoint.getSignature().getName());throw new SecurityException("没有执行该操作的权限");}// 执行目标方法long startTime = System.currentTimeMillis();Object result = proceedingJoinPoint.proceed(); // 执行目标方法long endTime = System.currentTimeMillis();log.info("环绕通知 - 执行目标方法后");log.info("方法执行耗时: {}ms", (endTime - startTime));log.info("===== 环绕通知结束 =====");return result;}/*** 后置通知 - 无论方法是否正常执行都会执行* @param joinPoint 切入点对象* @param permissionAnnotation 注解对象*/@After("@annotation(permissionAnnotation)")public void after(JoinPoint joinPoint, PermissionAnnotation permissionAnnotation) {log.info("===== 后置通知开始 =====");log.info("目标方法: {}.{} 执行完成",joinPoint.getTarget().getClass().getName(),joinPoint.getSignature().getName());log.info("清理资源或记录日志等操作");log.info("===== 后置通知结束 =====");}/*** 返回后通知 - 方法正常返回后执行* @param joinPoint 切入点对象* @param permissionAnnotation 注解对象* @param result 方法返回值*/@AfterReturning(pointcut = "@annotation(permissionAnnotation)", returning = "result")public void afterReturning(JoinPoint joinPoint, PermissionAnnotation permissionAnnotation, Object result) {log.info("===== 返回后通知开始 =====");log.info("目标方法: {}.{} 正常返回",joinPoint.getTarget().getClass().getName(),joinPoint.getSignature().getName());log.info("方法返回值: {}", result);log.info("可以在这里处理返回结果");log.info("===== 返回后通知结束 =====");}/*** 异常通知 - 方法抛出异常时执行* @param joinPoint 切入点对象* @param permissionAnnotation 注解对象* @param ex 抛出的异常*/@AfterThrowing(pointcut = "@annotation(permissionAnnotation)", throwing = "ex")public void afterThrowing(JoinPoint joinPoint, PermissionAnnotation permissionAnnotation, Exception ex) {log.error("===== 异常通知开始 =====", ex);log.error("目标方法: {}.{} 抛出异常",joinPoint.getTarget().getClass().getName(),joinPoint.getSignature().getName());log.error("异常信息: {}", ex.getMessage());log.error("可以在这里记录异常日志或进行异常处理");log.error("===== 异常通知结束 =====");}/*** 权限检查逻辑* @param permissionAnnotation 权限注解* @return 是否有权限*/private boolean checkPermission(PermissionAnnotation permissionAnnotation) {// 实际应用中这里应该实现真实的权限检查逻辑log.info("执行权限检查: {}", permissionAnnotation.apiValue());// 简单示例:默认有权限return true;}
}
4,实现示例
@GetMapping("/list")@ApiOperationSupport(order = 2)@ApiOperation(value = "分页", notes = "参数")@PermissionAnnotation(menuValue = {"test","test2"}, apiValue = "annotation_test")public R<IPage<>> list() {return R.data(null);}
注:将注解加入接口处进行调用(当接口被调用时,前置通知进行拦截判断权限)