我將基于您提供的防抖切面代碼進行改進,主要優化點包括使用Redis實現分布式防抖、增強鍵生成策略、改進異常處理等。以下是完整的改進方案:
1. 改進的防抖注解 (Debounce.java
)
java
復制
下載
package com.weiyu.anno;import java.lang.annotation.*;@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Debounce {/*** 防抖時間(毫秒),默認5秒*/long value() default 5000;/*** 自定義鍵值(支持SpEL表達式)*/String key() default "";/*** 防抖鍵類型*/KeyType keyType() default KeyType.METHOD;/*** 錯誤提示信息*/String message() default "操作過于頻繁,請稍后再試";enum KeyType {METHOD, // 方法級別防抖(默認)USER, // 用戶級別防抖(需要用戶上下文)IP, // IP級別防抖PARAM, // 參數級別防抖(基于參數值)CUSTOM // 完全自定義鍵} }
2. 改進的防抖工具類 (DebounceUtil.java
)
java
復制
下載
package com.weiyu.utils;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils;import java.util.concurrent.TimeUnit;@Component public class DebounceUtil {@Autowiredprivate StringRedisTemplate redisTemplate;private static final ExpressionParser parser = new SpelExpressionParser();/*** 檢查并設置防抖鎖* @param key 防抖鍵* @param debounceTime 防抖時間(毫秒)* @return 是否允許操作(true:允許,false:防抖中)*/public boolean checkAndSet(String key, long debounceTime) {// 使用SETNX+EXPIRE原子操作Boolean result = redisTemplate.opsForValue().setIfAbsent(key, "1", debounceTime, TimeUnit.MILLISECONDS);return result != null && result;}/*** 生成防抖鍵* @param joinPoint 切點* @param keyType 鍵類型* @param customKey 自定義鍵表達式* @return 生成的防抖鍵*/public String generateKey(ProceedingJoinPoint joinPoint, Debounce.KeyType keyType, String customKey) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();String methodName = signature.getDeclaringTypeName() + "#" + signature.getName();// 1. 處理自定義SpEL表達式if (keyType == Debounce.KeyType.CUSTOM && StringUtils.hasText(customKey)) {return evaluateSpEL(joinPoint, customKey);}// 2. 處理其他鍵類型switch (keyType) {case USER:return "DEBOUNCE:USER:" + getCurrentUserId() + ":" + methodName;case IP:return "DEBOUNCE:IP:" + getClientIp() + ":" + methodName;case PARAM:return "DEBOUNCE:PARAM:" + methodName + ":" + Arrays.hashCode(joinPoint.getArgs());case CUSTOM: // 已處理,此處為fallbackcase METHOD:default:return "DEBOUNCE:METHOD:" + methodName;}}/*** 解析SpEL表達式*/private String evaluateSpEL(ProceedingJoinPoint joinPoint, String expression) {try {StandardEvaluationContext context = new StandardEvaluationContext();// 設置方法參數context.setVariable("args", joinPoint.getArgs());// 設置方法簽名context.setVariable("method", joinPoint.getSignature());// 設置目標對象context.setVariable("target", joinPoint.getTarget());Expression exp = parser.parseExpression(expression);return "DEBOUNCE:CUSTOM:" + exp.getValue(context, String.class);} catch (Exception e) {throw new IllegalArgumentException("Invalid SpEL expression: " + expression, e);}}/*** 獲取當前用戶ID(需要根據實際認證系統實現)*/private String getCurrentUserId() {// 示例:從Spring Security上下文中獲取Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication != null && authentication.isAuthenticated()) {return authentication.getName();}return "ANONYMOUS";}/*** 獲取客戶端IP*/private String getClientIp() {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();String ip = request.getHeader("X-Forwarded-For");if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;} }
3. 改進的防抖切面 (DebounceAspect.java
)
java
復制
下載
package com.weiyu.aop;import com.weiyu.anno.Debounce; import com.weiyu.exception.DebounceException; import com.weiyu.utils.DebounceUtil; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder;import java.lang.reflect.Method;/*** 改進的防抖切面(支持Redis分布式鎖)*/ @Aspect @Component public class DebounceAspect {@Autowiredprivate DebounceUtil debounceUtil;@Around("@annotation(debounceAnnotation)")public Object debounce(ProceedingJoinPoint joinPoint, Debounce debounceAnnotation) throws Throwable {// 獲取方法上的注解(處理繼承情況)if (debounceAnnotation == null) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();debounceAnnotation = AnnotationUtils.findAnnotation(method, Debounce.class);if (debounceAnnotation == null) {return joinPoint.proceed();}}// 生成防抖鍵String key = debounceUtil.generateKey(joinPoint,debounceAnnotation.keyType(),debounceAnnotation.key());// 獲取防抖時間long debounceTime = debounceAnnotation.value();// 檢查并設置防抖鎖if (!debounceUtil.checkAndSet(key, debounceTime)) {throw new DebounceException(debounceAnnotation.message());}// 執行目標方法try {return joinPoint.proceed();} catch (Throwable throwable) {// 異常時移除防抖鎖(可選)// debounceUtil.removeLock(key);throw throwable;}} }
4. 自定義防抖異常 (DebounceException.java
)
java
復制
下載
package com.weiyu.exception;/*** 防抖業務異常*/ public class DebounceException extends RuntimeException {private int code = 429; // 使用429狀態碼表示操作過于頻繁public DebounceException(String message) {super(message);}public int getCode() {return code;} }
5. 全局異常處理器 (GlobalExceptionHandler.java
)
java
復制
下載
package com.weiyu.exception;import com.weiyu.common.Result; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice public class GlobalExceptionHandler {/*** 處理防抖異常*/@ExceptionHandler(DebounceException.class)public Result handleDebounceException(DebounceException e) {return Result.error(e.getCode(), e.getMessage());} }
6. 統一響應對象 (Result.java
)
java
復制
下載
package com.weiyu.common;public class Result<T> {private int code;private String msg;private T data;// 成功靜態方法public static <T> Result<T> success(T data) {Result<T> result = new Result<>();result.setCode(200);result.setMsg("操作成功");result.setData(data);return result;}// 錯誤靜態方法public static <T> Result<T> error(int code, String msg) {Result<T> result = new Result<>();result.setCode(code);result.setMsg(msg);return result;}// Getters and Setters }
7. 使用示例
java
復制
下載
@RestController @RequestMapping("/api/download") public class DownloadController {// 基本方法級別防抖(5秒內只能調用一次)@Debounce@GetMapping("/basic")public Result<String> basicDownload() {// ... 下載邏輯return Result.success("下載成功");}// 用戶級別防抖(每個用戶5秒內只能下載一次)@Debounce(keyType = Debounce.KeyType.USER, value = 5000)@GetMapping("/user")public Result<String> userSpecificDownload() {// ... 下載邏輯return Result.success("下載成功");}// 自定義鍵防抖(基于文件ID)@Debounce(keyType = Debounce.KeyType.CUSTOM,key = "'FILE:' + #fileId", // SpEL表達式message = "該文件正在下載中,請稍后再試")@GetMapping("/file/{fileId}")public Result<String> fileDownload(@PathVariable String fileId) {// ... 下載邏輯return Result.success("下載成功");}// IP級別防抖(每個IP 10秒內只能下載一次)@Debounce(keyType = Debounce.KeyType.IP, value = 10000)@GetMapping("/ip-based")public Result<String> ipBasedDownload() {// ... 下載邏輯return Result.success("下載成功");} }
8. 前端適配方案
javascript
復制
下載
// 在響應攔截器中處理防抖異常 instance.interceptors.response.use(response => {// ... 正常處理},error => {if (error.response) {// 識別防抖異常(HTTP 429狀態碼)if (error.response.status === 429) {ElMessage.error(error.response.data?.msg || "操作過于頻繁,請稍后再試");return Promise.reject(error);}// 識別業務層防抖異常(code=429)const res = error.response.data;if (res && res.code === 429) {ElMessage.error(res.msg || "操作過于頻繁,請稍后再試");return Promise.reject(error);}}// ... 其他錯誤處理} );
改進亮點
-
分布式支持:
-
使用Redis實現分布式防抖鎖
-
原子操作(SETNX + EXPIRE)確保并發安全
-
-
靈活的鍵策略:
-
支持方法、用戶、IP、參數、自定義五種鍵類型
-
集成SpEL表達式引擎實現動態鍵生成
-
-
增強的異常處理:
-
自定義防抖異常類型(DebounceException)
-
統一使用429狀態碼(Too Many Requests)
-
可自定義錯誤消息
-
-
注解增強:
-
支持方法繼承的注解查找
-
可配置防抖時間和錯誤消息
-
支持SpEL表達式自定義鍵
-
-
集成Spring生態:
-
自動獲取請求上下文(IP、用戶信息)
-
通過全局異常處理器統一處理
-
-
更精確的防抖:
-
精確到毫秒級別的防抖控制
-
避免本地內存防抖的分布式問題
-
這個改進方案保持了原有代碼的結構和風格,同時增加了企業級應用所需的分布式支持和靈活性,特別適合在微服務架構中使用。