springboot AOP 接口限流(基于IP的接口限流和黑白名單)

使用 Spring Boot 自定義注解和AOP實現基于IP的接口限流和黑白名單

在我們日常開發的項目中為了保證系統的穩定性,很多時候我們需要對系統做限流處理,它可以有效防止惡意請求對系統造成過載。常見的限流方案主要有:

網關限流: NGINX、Zuul 等 API 網關
服務器端限流: 服務端接口限流
令牌桶算法: 通過定期生成令牌放入桶中,請求需要消耗令牌才能通過
熔斷機制: Hystrix、Resilience4j 等

本文將詳細介紹 Spring Boot 通過自定義注解和 AOP(面向切面編程),實現基于 IP 的限流和黑白名單功能,包括如何使用 Redis 存儲限流和黑名單信息。

項目搭建

添加必要的依賴。在 pom.xml 文件中添加以下內容:

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
</dependencies>

配置 application.yml 加入 redis 配置

spring:#redisredis:# 地址host: 127.0.0.1# 端口,默認為6379port: 6379# 數據庫索引database: 0# 密碼password: password# 連接超時時間timeout: 10slettuce:pool:# 連接池中的最小空閑連接min-idle: 0# 連接池中的最大空閑連接max-idle: 8# 連接池的最大數據庫連接數max-active: 8# #連接池最大阻塞等待時間(使用負值表示沒有限制)max-wait: -1ms

自定義限流注解

創建一個自定義注解 RateLimit :

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimit {//限制次數int limit() default 5;//限制時間 秒int timeout() default 60;
}

編寫限流切面

使用 AOP 實現限流邏輯,并增加 IP 黑白名單判斷 , 使用 Redis 來存儲和檢查請求次數及黑名單信息。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;@Aspect
@Component
public class RateLimitAspect {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate HttpServletRequest request;//定義黑名單key前綴private static final String BLACKLIST_KEY_PREFIX = "blacklist:";//定義白名單key前綴private static final String WHITELIST_KEY_PREFIX = "whitelist:";@Around("@annotation(rateLimit)")public Object rateLimit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {//獲取IP// String ip =request.getRemoteAddr();/*** 沒有經過代理使用: request.getRemoteAddr();*經過nginx代理使用: request.getHeader("X-Real-IP");**/String ip =IpUtil.getIpAddress(request);  //黑名單則直接異常if (isBlacklisted(ip)) {throw new RuntimeException("超出訪問限制已加入黑名單,1小時后再訪問");}//如果是白名單下的不做限制if (isWhitelisted(ip)) {return joinPoint.proceed();}String key = generateKey(joinPoint, ip);int limit = rateLimit.limit();int timeout = rateLimit.timeout();String countStr = redisTemplate.opsForValue().get(key);int count = countStr == null ? 0 : Integer.parseInt(countStr);if (count < limit) {redisTemplate.opsForValue().set(key, String.valueOf(count + 1), timeout, TimeUnit.SECONDS);return joinPoint.proceed();} else {addToBlacklist(ip);throw new RuntimeException("超出請求限制IP已被列入黑名單");}}// 判斷是否在黑名單列表內private boolean isBlacklisted(String ip) {return redisTemplate.hasKey(BLACKLIST_KEY_PREFIX + ip);}// 是否在白名單內private boolean isWhitelisted(String ip) {return redisTemplate.hasKey(WHITELIST_KEY_PREFIX + ip);}// 添加ip到白名單內private void addToBlacklist(String ip) {redisTemplate.opsForValue().set(BLACKLIST_KEY_PREFIX + ip, "true", 1, TimeUnit.HOURS);}// redis key 拼接private String generateKey(ProceedingJoinPoint joinPoint, String ip) {String methodName = joinPoint.getSignature().getName();String className = joinPoint.getTarget().getClass().getName();return className + ":" + methodName + ":" + ip;}
}
/*** IP工具類*/
public class IpUtil {/*** 獲取ip* @param request 請求* @return {@link String }*/public static String getIpAddress(HttpServletRequest request) {String ipAddress = null;try {ipAddress = request.getHeader("x-forwarded-for");if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("WL-Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getRemoteAddr();if (ipAddress.equals("127.0.0.1")) {// 根據網卡取本機配置的IPInetAddress inet = null;try {inet = InetAddress.getLocalHost();} catch (UnknownHostException e) {e.printStackTrace();}ipAddress = inet.getHostAddress();}}// 對于通過多個代理的情況,第一個IP為客戶端真實IP,多個IP按照','分割if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()// = 15if (ipAddress.indexOf(",") > 0) {ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));}}} catch (Exception e) {ipAddress="";}// ipAddress = this.getRequest().getRemoteAddr();return ipAddress;}/*** 獲取網關ip* @param request 請求* @return {@link String }*/public static String getGatwayIpAddress(ServerHttpRequest request) {HttpHeaders headers = request.getHeaders();String ip = headers.getFirst("x-forwarded-for");if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {// 多次反向代理后會有多個ip值,第一個ip才是真實ipif (ip.indexOf(",") != -1) {ip = ip.split(",")[0];}}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = headers.getFirst("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = headers.getFirst("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = headers.getFirst("HTTP_CLIENT_IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = headers.getFirst("HTTP_X_FORWARDED_FOR");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = headers.getFirst("X-Real-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddress().getAddress().getHostAddress();}return ip;}
}

Controller中使用限流注解

創建一個簡單的限流測試Controller,并在需要限流的方法上使用 @RateLimit 注解:,需要編寫異常處理,返回RateLimitAspect異常信息,并以字符串形式返回

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api")
public class TestController {@RateLimit(limit = 5, timeout = 60)@GetMapping("/limit")public String testRateLimit() {return "Request successful!";}
}

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

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

相關文章

OpenCV CUDA模塊中矩陣操作------范數(Norm)相關函數

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 在 OpenCV 的 CUDA 模塊中&#xff0c;與范數&#xff08;Norm&#xff09;相關的函數主要用于計算矩陣的范數或者兩個矩陣之間的差值范數。 主…

生成對抗網絡(Generative Adversarial Networks ,GAN)

生成對抗網絡是深度學習領域最具革命性的生成模型之一。 一 GAN框架 1.1組成 構造生成器&#xff08;G&#xff09;與判別器&#xff08;D&#xff09;進行動態對抗&#xff0c;實現數據的無監督生成。 G&#xff08;造假者&#xff09;&#xff1a;接收噪聲 ?&#xff0c…

httpclient請求出現403

問題 httpclient請求對方服務器報403&#xff0c;用postman是可以的 解決方案: request.setHeader( “User-Agent” ,“Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0” ); // 設置請求頭 原因&#xff1a; 因為沒有設置為瀏覽器形式&#…

嵌入式硬件篇---IIC

文章目錄 前言1. IC協議基礎1.1 物理層特性兩根信號線SCLSDA支持多主多從 標準模式電平 1.2 通信流程起始條件&#xff08;Start Condition&#xff09;從機地址&#xff08;Slave Address&#xff09;應答&#xff08;ACK/NACK&#xff09;數據傳輸&#xff1a;停止條件&#…

深入探討 Java 注解:從基礎到高級應用

Java 注解自 Java 5 引入以來,已成為現代 Java 開發中不可或缺的一部分。它們通過為代碼添加元數據,簡化了配置、增強了代碼可讀性,并支持了從編譯時驗證到運行時動態行為的多種功能。本文將全面探討 Java 注解的使用、定義和處理方式,并通過一個實際的插件系統示例展示其強…

力扣-105.從前序與中序遍歷序列構造二叉樹

題目描述 給定兩個整數數組 preorder 和 inorder &#xff0c;其中 preorder 是二叉樹的先序遍歷&#xff0c; inorder 是同一棵樹的中序遍歷&#xff0c;請構造二叉樹并返回其根節點。 class Solution { public:TreeNode* buildTree(vector<int>& preorder, vecto…

NoSQL數據庫技術與應用復習總結【看到最后】

第1章 初識NoSQL 1.1 大數據時代對數據存儲的挑戰 1.高并發讀寫需求 2.高效率存儲與訪問需求 3.高擴展性 1.2 認識NoSQL NoSQL--非關系型、分布式、不提供ACID的數據庫設計模式 NoSQL特點 1.易擴展 2.高性能 3.靈活的數據模型 4.高可用 NoSQL擁有一個共同的特點&am…

【ios越獄包安裝失敗?uniapp導出ipa文件如何安裝到蘋果手機】蘋果IOS直接安裝IPA文件

問題場景&#xff1a; 提示&#xff1a;ipa是用于蘋果設備安裝的軟件包資源 設備&#xff1a;iphone 13(未越獄) 安裝包類型&#xff1a;ipa包 調試工具&#xff1a;hbuilderx 問題描述 提要&#xff1a;ios包無法安裝 uniapp導出ios包無法安裝 相信有小伙伴跟我一樣&…

php數據導出pdf,然后pdf轉圖片,再推送釘釘群

public function takePdf($data_plan, $data_act, $file_name, $type){$pdf new \TCPDF(L); // L - 橫向 P-豎向// 設置文檔信息//$file_name 外協批價單;$pdf->SetCreator($file_name);$pdf->SetAuthor($file_name);$pdf->SetTitle($file_name);$pdf->SetSubjec…

每日算法-250513

每日算法 - 2024-05-13 記錄今天學習的算法題解。 2335. 裝滿杯子需要的最短總時長 題目 思路 貪心 這道題的關鍵在于每次操作盡可能多地減少杯子的數量。我們每次操作可以裝一杯或兩杯&#xff08;不同類型&#xff09;。為了最小化總時間&#xff0c;應該優先選擇裝兩杯不同…

城市生命線綜合管控系統解決方案-守護城市生命線安全

一、政策背景 國務院辦公廳《城市安全風險綜合監測預警平臺建設指南》?要求&#xff1a;將燃氣、供水、排水、橋梁、熱力、綜合管廊等納入城市生命線監測體系&#xff0c;建立"能監測、會預警、快處置"的智慧化防控機制。住建部?《"十四五"全國城市基礎…

分布式AI推理的成功之道

隨著AI模型逐漸成為企業運營的核心支柱&#xff0c;實時推理已成為推動這一轉型的關鍵引擎。市場對即時、可決策的AI洞察需求激增&#xff0c;而AI代理——正迅速成為推理技術的前沿——即將迎來爆發式普及。德勤預測&#xff0c;到2027年&#xff0c;超半數采用生成式AI的企業…

auto.js面試題及答案

以下是常見的 Auto.js 面試題及參考答案&#xff0c;涵蓋基礎知識、腳本編寫、運行機制、權限、安全等方面&#xff0c;適合開發崗位的技術面試準備&#xff1a; 一、基礎類問題 什么是 Auto.js&#xff1f;它的主要用途是什么&#xff1f; 答案&#xff1a; Auto.js 是一個…

C語言中的指定初始化器

什么是指定初始化器? C99標準引入了一種更靈活、直觀的初始化語法——指定初始化器(designated initializer), 可以在初始化列表中直接引用結構體或聯合體成員名稱的語法。通過這種方式,我們可以跳過某些不需要初始化的成員,并且可以以任意順序對特定成員進行初始化。這…

高德地圖在Vue3中的使用方法

1.地圖初始化 容器創建&#xff1a;通過 <div> 標簽定義地圖掛載點。 <div id"container" style"height: 300px; width: 100%; margin-top: 10px;"></div> 密鑰配置&#xff1a;綁定高德地圖安全密鑰&#xff0c;確保 API 合法調用。 參…

RabbitMQ發布訂閱模式深度解析與實踐指南

目錄 RabbitMQ發布訂閱模式深度解析與實踐指南1. 發布訂閱模式核心原理1.1 消息分發模型1.2 核心組件對比 2. 交換機類型詳解2.1 交換機類型矩陣2.2 消息生命周期 3. 案例分析與實現案例1&#xff1a;基礎廣播消息系統案例2&#xff1a;分級日志處理系統案例3&#xff1a;分布式…

中小型培訓機構都用什么教務管理系統?

在教育培訓行業快速發展的今天&#xff0c;中小型培訓機構面臨著學員管理復雜、課程體系多樣化、教學效果難以量化等挑戰。一個高效的教務管理系統已成為機構運營的核心支撐。本文將深入分析當前市場上適用于中小型培訓機構的教務管理系統&#xff0c;重點介紹愛耕云這一專業解…

C++虛函數食用筆記

虛函數定義與作用&#xff1a; virtual關鍵字聲明虛函數&#xff0c;虛函數可被派生類override(保證返回類型與參數列表&#xff0c;名字均相同&#xff09;&#xff0c;從而通過基類指針調用時&#xff0c;實現多態的功能 virtual關鍵字: 將函數聲明為虛函數 override關鍵…

運算放大器相關的電路

1運算放大器介紹 解釋&#xff1a;運算放大器本質就是一個放大倍數很大的元件&#xff0c;就如上圖公式所示 Vp和Vn相差很小但是放大后輸出還是會很大。 運算放大器不止上面的三個引腳&#xff0c;他需要獨立供電&#xff1b; 如圖比較器&#xff1a; 解釋&#xff1a;Vp&…

華為OD機試真題——通信系統策略調度(用戶調度問題)(2025B卷:100分)Java/python/JavaScript/C/C++/GO最佳實現

2025 B卷 100分 題型 本專欄內全部題目均提供Java、python、JavaScript、C、C++、GO六種語言的最佳實現方式; 并且每種語言均涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、3個測試用例以及綜合分析; 本文收錄于專欄:《2025華為OD真題目錄+全流程解析+備考攻略+經驗分…