前言:自定義注解,通過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);}
注:將注解加入接口處進行調用(當接口被調用時,前置通知進行攔截判斷權限)