用Caffeine和自定義注解+AOP優雅實現本地防抖接口限流

一、背景與需求

在實際項目開發中,經常遇到接口被前端高頻觸發按鈕被多次點擊或者接口重復提交的問題,導致服務壓力變大、數據冗余、甚至引發冪等性/安全風險。

常規做法是前端節流/防抖、后端用Redis全局限流、或者API網關限流。但在很多場景下:

  • 接口只要求單機(本地)防抖,不需要全局一致性;

  • 只想讓同一個業務對象(同一手機號、同一業務ID、唯一標識)在自定義設置秒內只處理一次

  • 想要注解式配置,讓代碼更優雅、好維護。

這個時候,Caffeine+自定義注解+AOP的本地限流(防抖)方案非常合適。


二、方案設計

1. Caffeine介紹

Caffeine 是目前Java領域最熱門、性能最高的本地內存緩存庫,QPS可達百萬級,適用于低延遲、高并發、短TTL緩存場景。
在本地限流、防抖、接口去重等方面天然有優勢。

2. 自定義注解+AOP

用自定義注解(如@DebounceLimit)標記要防抖的接口,AOP切面攔截后判斷是否需要限流,核心思路是:

  • 以唯一標識作為key;

  • 每次訪問接口,先查詢本地Caffeine緩存;

  • 如果key在2秒內已被處理過,則直接攔截;

  • 否則執行業務邏輯,并記錄處理時間。

這種方式無侵入、代碼簡潔、可擴展性強,適合絕大多數本地場景。

效果圖如下:


三、完整實現步驟

1.Pom依賴如下

        <dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.9.3</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency>

2. 定義自定義注解

 
import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DebounceLimit {/*** 唯一key(支持SpEL表達式,如 #dto.id)*/String key();/*** 防抖時間,單位秒*/int ttl() default 2;/*** 是否返回上次緩存的返回值*/boolean returnLastResult() default true;
}


3. 配置Caffeine緩存Bean

 
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.TimeUnit;@Configuration
public class DebounceCacheConfig {@Beanpublic Cache<String, Object> debounceCache() {return Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).maximumSize(100_000).build();}
}


4. 編寫AOP切面

 
import com.github.benmanes.caffeine.cache.Cache;
import com.lps.anno.DebounceLimit;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
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.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;@Slf4j
@Aspect
@Component
public class DebounceLimitAspect {@Autowiredprivate Cache<String, Object> debounceCache;private final ExpressionParser parser = new SpelExpressionParser();@Around("@annotation(debounceLimit)")public Object around(ProceedingJoinPoint pjp, DebounceLimit debounceLimit) throws Throwable {// 1. 獲取方法、參數MethodSignature methodSignature = (MethodSignature) pjp.getSignature();Method method = methodSignature.getMethod();Object[] args = pjp.getArgs();String[] paramNames = methodSignature.getParameterNames();StandardEvaluationContext context = new StandardEvaluationContext();for (int i = 0; i < paramNames.length; i++) {context.setVariable(paramNames[i], args[i]);}// 2. 解析SpEL表達式得到唯一keyString key = parser.parseExpression(debounceLimit.key()).getValue(context, String.class);String cacheKey = method.getDeclaringClass().getName() + "." + method.getName() + ":" + key;long now = System.currentTimeMillis();DebounceResult<Object> debounceResult = (DebounceResult<Object>) debounceCache.getIfPresent(cacheKey);if (debounceResult != null && (now - debounceResult.getTimestamp() < debounceLimit.ttl() * 1000L)) {String methodName = pjp.getSignature().toShortString();log.error("接口[{}]被限流, key={}", methodName, cacheKey);// 是否返回上次結果if (debounceLimit.returnLastResult() && debounceResult.getResult() != null) {return debounceResult.getResult();}// 統一失敗響應,可自定義異常或返回結構return new RuntimeException("操作過于頻繁,請稍后再試!");}Object result = pjp.proceed();debounceCache.put(cacheKey, new DebounceResult<>(result, now));return result;}@Getterstatic class DebounceResult<T> {private final T result;private final long timestamp;public DebounceResult(T result, long timestamp) {this.result = result;this.timestamp = timestamp;}}
}


5. 控制器里直接用注解實現防抖

 
@RestController
@RequiredArgsConstructor
@Slf4j
public class DebounceControl {private final UserService userService;@PostMapping("/getUsernameById")@DebounceLimit(key = "#dto.id", ttl = 10)public String test(@RequestBody User dto) {log.info("在{}收到了請求,參數為:{}", DateUtil.now(), dto);return userService.getById(dto.getId()).getUsername();}
}

只要加了這個注解,同一個id的請求在自定義設置的秒內只處理一次,其他直接被攔截并打印日志。


四、擴展與注意事項

  1. SpEL表達式靈活

    • 可以用 #dto.id#dto.mobile#paramName等,非常適合多參數、復雜唯一性業務場景。

  2. returnLastResult適合有“緩存返回結果”的場景

    • 比如查詢接口、表單重復提交直接復用上次的返回值。

  3. 本地限流僅適用于單機環境

    • 多節點部署建議用Redis分布式限流,原理一樣。

  4. 緩存key建議加上方法簽名

    • 避免不同接口之間key沖突。

  5. Caffeine最大緩存、過期時間應根據業務并發和內存合理設置

    • 絕大多數接口幾千到幾萬key都沒壓力。


五、適用與不適用場景

適用:

  • 單機接口防抖/限流

  • 短時間重復提交防控

  • 按業務唯一標識維度防刷

  • 秒殺、報名、投票等接口本地保護

不適用:

  • 分布式場景(建議用Redis或API網關限流)

  • 需要全局一致性的業務

  • 內存非常敏感/極端高并發下,需結合Redis做混合限流


六、總結

Caffeine + 注解 + AOP的本地限流防抖方案,實現簡單、代碼優雅、性能極高、擴展靈活
?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/81594.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/81594.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/81594.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【IP101】紋理特征提取與分析:從統計方法到深度表征的系統解析

紋理分析詳解 &#x1f3a8; 紋理分析就像是給圖像做"指紋識別"&#xff01;每種紋理都有其獨特的"指紋"&#xff0c;就像木紋的條紋、布料的編織、草地的隨機分布一樣。讓我們一起來探索這個既有趣又實用的圖像處理領域吧&#xff01; 目錄 1. 什么是紋理…

機器學習中采樣哪些事

在機器學習中采樣主要分為兩種&#xff0c;過采樣&#xff08;Oversample&#xff09;和欠采樣(Undersample)。過采樣就是通過增加少數類樣本的數量來平衡數據集。而欠采樣就是通過減少多數類樣本的數量來平衡數據集。 通常在進行采樣中以下是幾種常用的方法: 1. 隨機采樣 隨…

fastadmin 數據導出,設置excel行高和限制圖片大小

fastadmin默認導出圖片全部都再一塊&#xff0c;而且不在單元格里 話不多說&#xff0c;上代碼 修改文件的路徑&#xff1a; /public/assets/js/require-table.js exportOptions: {fileName: export_ Moment().format("YYYY-MM-DD"),preventInjection: false,mso…

鴻蒙OSUniApp開發的商品詳情展示頁面(鴻蒙系統適配版)#三方框架 #Uniapp

使用UniApp開發的商品詳情展示頁面&#xff08;鴻蒙系統適配版&#xff09; 前言 隨著移動電商的普及&#xff0c;一個體驗良好的商品詳情頁對于提高用戶轉化率至關重要。本文將分享我在使用UniApp開發商品詳情頁時的實踐經驗&#xff0c;并特別關注如何適配鴻蒙系統&#xf…

redis中key的過期和淘汰

一、過期&#xff08;redis主動刪除&#xff09; 設置了ttl過期時間的key&#xff0c;在ttl時間到的時候redis會刪除過期的key。但是redis是惰性過期。惰性過期&#xff1a;redis并不會立即刪除過期的key&#xff0c;而是會在獲取key的時候判斷key是否過期&#xff0c;如果發現…

Qwen3 - 0.6B與Bert文本分類實驗:深度見解與性能剖析

Changelog [25/04/28] 新增Qwen3-0.6B在Ag_news數據集Zero-Shot的效果。新增Qwen3-0.6B線性層分類方法的效果。調整Bert訓練參數&#xff08;epoch、eval_steps&#xff09;&#xff0c;以實現更細致的觀察&#xff0c;避免嚴重過擬合的情況。 TODO&#xff1a; 利用Qwen3-0.6…

HTML應用指南:利用POST請求獲取全國京東快遞服務網點位置信息

京東快遞作為中國領先的智能供應鏈與綜合物流服務提供商,自2007年成立以來,始終致力于通過技術創新與高效運營,為客戶提供安全、可靠、快速的物流解決方案。京東快遞依托京東集團的強大資源支持,憑借其自營倉儲、干線運輸、末端配送一體化的物流網絡,在激烈的市場競爭中脫…

js中eval的用法風險與替代方案全面解析

1. 前言 在 JavaScript 里&#xff0c;eval是一個既強大又充滿爭議的函數。它為開發者提供了一種動態執行字符串代碼的能力&#xff0c;在某些特定場景下能發揮出獨特的作用。然而&#xff0c;由于其特殊的運行機制&#xff0c;也帶來了諸多潛在的風險和問題。本文將深入探討e…

antd樹結構

一、場景實現 1、左側為查詢條件&#xff0c;查詢條件為樹和多選。點擊查詢條件在右上方顯示搜索條件的內容&#xff0c;右上方查詢條件 tag 刪除后&#xff0c;左側條件也對應刪除。 2、樹結構&#xff1a;默認第一層下所有節點都展開。 1、頁面效果圖 2、查詢效果圖 二、前端…

Jenkins 安裝與配置指南

Jenkins 安裝與配置指南&#xff08;MD 示例&#xff09; markdown Jenkins 安裝與配置指南 ## 一、環境準備 1. **系統要求** - 操作系統&#xff1a;Linux/macOS/Windows - Java 版本&#xff1a;JDK 8 或更高&#xff08;建議 JDK 11&#xff09;2. **安裝方式** - **L…

[Linux性能優化] 線程卡頓優化。Linux加入USB(HID)熱插拔線程占用CPU優化。Linux中CPU使用率過高優化

文章目錄 [Linux性能優化] 線程卡頓優化。0、省流版本一、問題定位&#xff1a;CPU 資源分析二、線程卡頓現場復現線程優化前圖片 三、線程卡頓優化方向1.如果是輪詢方式2.如果是事件驅動方式 四、修改方式線程優化后圖片 [Linux性能優化] 線程卡頓優化。 0、省流版本 如果采…

ip與mac-數據包傳輸過程學習

你管這破玩意叫網絡&#xff1f; 內容來源于飛天閃客&#xff0c;以前沒有學習過網絡的相關基礎知識&#xff0c;只會去瞎設置&#xff0c;現在終于是弄明白了。 多臺電腦之間想要通信&#xff0c;可以直接通過一條網線進行連接。但是隨著網線的增加&#xff0c;這個就會比較…

數值分析知識重構

數值分析知識重構 一 Question 請構造一下數值分析中的誤差概念以及每一個具體數值方法的誤差是如何估計的&#xff1f; 二 Question 已知 n 1 n1 n1個數據點 ( x i , y i ) , i 0 , 1 , ? , n (x_i,y_i),i0,1,\cdots,n (xi?,yi?),i0,1,?,n,請使用多種方法建立數據之間…

使用 Qt QGraphicsView/QGraphicsScene 繪制色輪

使用 Qt QGraphicsView/QGraphicsScene 繪制色輪 本文介紹如何在 Qt 中利用 QGraphicsView 和 QGraphicsScene 實現基礎圓形繪制&#xff0c;以及進階的色輪&#xff08;Color Wheel&#xff09;效果。 色輪是色彩選擇器的常見控件&#xff0c;廣泛應用于圖形設計、繪畫和 UI …

移除鏈表元素數據結構oj題(力扣題206)

目錄 題目描述&#xff1a; 題目解讀&#xff08;分析&#xff09; 解決代碼 題目描述&#xff1a; 給你一個鏈表的頭節點 head 和一個整數 val &#xff0c;請你刪除鏈表中所有滿足 Node.val val 的節點&#xff0c;并返回 新的頭節點 。 題目解讀&#xff08;分析&#…

GLPK(GNU線性規劃工具包)中建模語言MathProg的使用

GNU MathProg是一種用于描述線性數學規劃模型的建模語言。用GNU MathProg語言編寫的模型描述由一組語句和數據塊組成。 在MathProg中&#xff0c;模型以集合、參數、變量、約束和目標(sets, parameters, variables, constraints, objectives稱為模型對象)的形式進行描述。 在Ma…

《Python星球日記》 第77天:模型部署與總結

名人說:路漫漫其修遠兮,吾將上下而求索。—— 屈原《離騷》 創作者:Code_流蘇(CSDN)(一個喜歡古詩詞和編程的Coder??) 目錄 一、模型部署技術1. 模型文件導出不同模型格式對比2. 使用Flask構建RESTful API3. 使用FastAPI構建高性能API4. 部署優化與最佳實踐二、部署架構…

【JavaWeb】MySQL(準備篇)

1 MySQL安裝 1.1 解壓 下載完成后我們得到的是一個壓縮包&#xff08;所有文件均在文末安裝包中&#xff09;&#xff0c;將其解壓&#xff0c;我們就可以得到MySQL 8.0.34 的軟件本體了(就是一個文件夾)&#xff0c;我們可以把它放在你想安裝的位置 。 1.2 配置 1.2.1 配置…

國產數據庫工具突圍:SQLynx如何解決Navicat的三大痛點?深度體驗報告

引言&#xff1a;Navicat的"中國困境" 當開發者面對達夢數據庫的存儲過程調試&#xff0c;或是在人大金倉中處理復雜查詢時&#xff0c;Navicat突然變得力不從心——這不是個例。 真實痛點&#xff1a;某政務系統遷移至OceanBase后&#xff0c;開發團隊發現Navicat無…

ETL數據集成產品選型需要關注哪些方面?

ETL&#xff08;Extract&#xff0c;Transform&#xff0c;Load&#xff09;工具作為數據倉庫和數據分析流程中的關鍵環節&#xff0c;其選型對于企業的數據戰略實施有著深遠的影響。谷云科技在 ETL 領域耕耘多年&#xff0c;通過自身產品的實踐應用&#xff0c;對 ETL 產品選型…