藉助redis實現對IP限流

来源:https://www.cnblogs.com/sykfrt/archive/2022/08/31/16642352.html
-Advertisement-
Play Games

背景: 若依前後端分離項目(vue+springboot+springmvc+mybatis),redis。 需求: 藉助redis實現對IP限流。 實現: 參考 https://blog.csdn.net/qq_33762302/article/details/116258617 代碼如下: IP ...


背景:   若依前後端分離項目(vue+springboot+springmvc+mybatis),redis。   需求:   藉助redis實現對IP限流。 實現: 參考  
 https://blog.csdn.net/qq_33762302/article/details/116258617
  代碼如下:   IPLimiter.java 定義註解類,將註解定義在需要分流IP的介面上
 1 import java.lang.annotation.*;
 2 
 3 @Target(ElementType.METHOD)
 4 @Retention(RetentionPolicy.RUNTIME)
 5 @Documented
 6 public @interface IpLimiter {
 7     /**
 8      * 放行ip
 9      */
10     String[] ipAdress() default {""};
11     /**
12      * 單位時間限制通過請求數
13      */
14     long limit() default 10;
15     /**
16      * 單位時間,單位秒
17      */
18     long time() default 1;
19     /**
20      * 達到限流提示語
21      */
22     String message();
23 
24     /**
25      * 是否鎖住IP的同時鎖住URI
26      */
27     boolean lockUri() default false;
28 }

 

IpLimterHandler.java 註解AOP處理。

  1 import com.missionex.common.annotation.IpLimiter;
  2 import com.missionex.common.core.domain.AjaxResult;
  3 import com.missionex.common.utils.DateUtils;
  4 import com.missionex.common.utils.SecurityUtils;
  5 import com.missionex.common.utils.http.IPAddressUtils;
  6 import org.aspectj.lang.ProceedingJoinPoint;
  7 import org.aspectj.lang.Signature;
  8 import org.aspectj.lang.annotation.Around;
  9 import org.aspectj.lang.annotation.Aspect;
 10 import org.aspectj.lang.reflect.MethodSignature;
 11 import org.slf4j.Logger;
 12 import org.slf4j.LoggerFactory;
 13 import org.springframework.beans.factory.annotation.Autowired;
 14 import org.springframework.core.io.ClassPathResource;
 15 import org.springframework.data.redis.core.StringRedisTemplate;
 16 import org.springframework.data.redis.core.script.DefaultRedisScript;
 17 import org.springframework.scripting.support.ResourceScriptSource;
 18 import org.springframework.stereotype.Component;
 19 import org.springframework.web.context.request.RequestContextHolder;
 20 import org.springframework.web.context.request.ServletRequestAttributes;
 21 
 22 import javax.annotation.PostConstruct;
 23 import javax.servlet.http.HttpServletRequest;
 24 import java.util.ArrayList;
 25 import java.util.List;
 26 
 27 @Aspect
 28 @Component
 29 public class IpLimterHandler {
 30 
 31     private static final Logger LOGGER = LoggerFactory.getLogger("request-limit");
 32 
 33     @Autowired
 34     StringRedisTemplate redisTemplate;
 35 
 36 
 37     /**
 38      * getRedisScript 讀取腳本工具類
 39      * 這裡設置為Long,是因為ipLimiter.lua 腳本返回的是數字類型
 40      */
 41     private DefaultRedisScript<Long> getRedisScript;
 42 
 43     @PostConstruct
 44     public void init() {
 45         getRedisScript = new DefaultRedisScript<>();
 46         getRedisScript.setResultType(Long.class);
 47         getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("ipLimiter.lua")));
 48         LOGGER.info("IpLimterHandler[分散式限流處理器]腳本載入完成");
 49     }
 50 
 51     /**
 52      * 這個切點可以不要,因為下麵的本身就是個註解
 53      */
 54 //    @Pointcut("@annotation(com.jincou.iplimiter.annotation.IpLimiter)")
 55 //    public void rateLimiter() {}
 56 
 57     /**
 58      * 如果保留上面這個切點,那麼這裡可以寫成
 59      * @Around("rateLimiter()&&@annotation(ipLimiter)")
 60      */
 61     @Around("@annotation(ipLimiter)")
 62     public Object around(ProceedingJoinPoint proceedingJoinPoint, IpLimiter ipLimiter) throws Throwable {
 63         if (LOGGER.isDebugEnabled()) {
 64             LOGGER.debug("IpLimterHandler[分散式限流處理器]開始執行限流操作");
 65         }
 66         String userIp = null;
 67         String requestURI = null;
 68         try {
 69             // 獲取請求信息
 70             HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
 71             requestURI = request.getRequestURI();
 72             String requestMethod = request.getMethod();
 73             String remoteAddr = request.getRemoteAddr();
 74             // 獲取請求用戶IP
 75             userIp = IPAddressUtils.getIpAdrress(request);
 76             if (userIp == null) {
 77                 return AjaxResult.error("運行環境存在風險");
 78             }
 79         } catch (Exception e) {
 80             LOGGER.error("獲取request出錯=>" + e.getMessage());
 81             if (userIp == null) {
 82                 return AjaxResult.error("運行環境存在風險");
 83             }
 84         }
 85         Signature signature = proceedingJoinPoint.getSignature();
 86         if (!(signature instanceof MethodSignature)) {
 87             throw new IllegalArgumentException("the Annotation @IpLimter must used on method!");
 88         }
 89         /**
 90          * 獲取註解參數
 91          */
 92         // 放行模塊IP
 93         String[] limitIp = ipLimiter.ipAdress();
 94         int len;
 95         if (limitIp != null && (len = limitIp.length) != 0) {
 96             for (int i = 0; i < len; i++) {
 97                 if (limitIp[i].equals(userIp)) {
 98                     return proceedingJoinPoint.proceed();
 99                 }
100             }
101         }
102         // 限流閾值
103         long limitTimes = ipLimiter.limit();
104         // 限流超時時間
105         long expireTime = ipLimiter.time();
106         boolean lockUri = ipLimiter.lockUri();
107         if (LOGGER.isDebugEnabled()) {
108             LOGGER.debug("IpLimterHandler[分散式限流處理器]參數值為-limitTimes={},limitTimeout={}", limitTimes, expireTime);
109         }
110         // 限流提示語
111         String message = ipLimiter.message();
112         /**
113          * 執行Lua腳本
114          */
115         List<String> ipList = new ArrayList();
116         // 設置key值為註解中的值
117         if (lockUri) {
118             ipList.add(userIp+requestURI);
119         } else {
120             ipList.add(userIp);
121         }
122         /**
123          * 調用腳本並執行
124          */
125         try {
126             Object x = redisTemplate.execute(getRedisScript, ipList, expireTime+"", limitTimes+"");
127             Long result = (Long) x;
128             if (result == 0) {
129                 Long userId = null;
130                 try {
131                     userId = SecurityUtils.getLoginUser().getAppUser().getId();
132                 } catch (Exception e) {
133 
134                 }
135                 LOGGER.info("[分散式限流處理器]限流執行結果-ip={}-介面={}-用戶ID={}-result={}-time={},已被限流", userIp,requestURI == null?"未知":requestURI
136                         ,userId==null?"用戶未登錄":userId,result, DateUtils.getTime());
137                 // 達到限流返回給前端信息
138                 return AjaxResult.error(message);
139             }
140             if (LOGGER.isDebugEnabled()) {
141                 LOGGER.debug("IpLimterHandler[分散式限流處理器]限流執行結果-result={},請求[正常]響應", result);
142             }
143             return proceedingJoinPoint.proceed();
144         } catch (Exception e) {
145             LOGGER.error("限流錯誤",e);
146             return proceedingJoinPoint.proceed();
147         }
148 
149     }
150 }

 

 

ipLimiter.lua 腳本,放在resources文件夾中。

 1 -獲取KEY
 2 local key1 = KEYS[1]
 3 
 4 local val = redis.call('incr', key1)
 5 local ttl = redis.call('ttl', key1)
 6 
 7 --獲取ARGV內的參數並列印
 8 local expire = ARGV[1]
 9 local times = ARGV[2]
10 
11 redis.log(redis.LOG_DEBUG,tostring(times))
12 redis.log(redis.LOG_DEBUG,tostring(expire))
13 
14 redis.log(redis.LOG_NOTICE, "incr "..key1.." "..val);
15 if val == 1 then
16     redis.call('expire', key1, tonumber(expire))
17 else
18     if ttl == -1 then
19         redis.call('expire', key1, tonumber(expire))
20     end
21 end
22 
23 if val > tonumber(times) then
24     return 0
25 end
26 return 1

 

RedisConfig.java redis配置(該配置繼承了CachingConfigurerSupport)中對寫入redis的數據的序列化。

如果使用IP鎖的時候,錯誤出現在了AOP中的使用腳本寫入redis的時候(像什麼Long無法轉String的錯誤),基本是這邊序列化沒配好。

 1    @Bean
 2     @SuppressWarnings(value = { "unchecked", "rawtypes" })
 3     public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
 4     {
 5         RedisTemplate<Object, Object> template = new RedisTemplate<>();
 6         template.setConnectionFactory(connectionFactory);
 7         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 8         // 使用StringRedisSerializer來序列化和反序列化redis的key值
 9         template.setKeySerializer(new StringRedisSerializer());
10         template.setValueSerializer(serializer);
11         // Hash的key也採用StringRedisSerializer的序列化方式
12         template.setHashKeySerializer(new StringRedisSerializer());
13         template.setHashValueSerializer(serializer);
14         template.afterPropertiesSet();
15         return template;
16     }

 

使用示範。

1     @PostMapping("/test")
2     @IpLimiter(limit = 2, time = 5, message = "您訪問過於頻繁,請稍候訪問",lockUri = true)
3     public AjaxResult test(@RequestBody Map map){
4         //代碼......
5     }

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 鏡像倉庫管理 docker倉庫,用來管理鏡像。主要分為公共倉庫和私人倉庫。下麵介紹了公共倉庫Docker Hub、私人倉庫Registry和harbor。 DockerHUb倉庫管理 什麼是DockerHUb 保存和分發鏡像的最直接方法就是使用 Docker Hub。 ​ Docker Hub 是 ...
  • get邏輯: HashMap數據結構為數組加鏈表加紅黑樹、只有當鏈表數量大於8時、才將鏈表轉換為紅黑樹、時間複雜度由鏈表的O(N)轉換為紅黑樹的O(logN) // 主要看getNode下的方法、傳入key的hash值和key public V get(Object key) { Node<K,V> ...
  • 我們知道加密後的數據對模糊查詢不是很友好,本篇就針對加密數據模糊查詢這個問題來展開講一講實現的思路,希望對大家有所啟發。 為了數據安全我們在開發過程中經常會對重要的數據進行加密存儲,常見的有:密碼、手機號、電話號碼、詳細地址、銀行卡號、信用卡驗證碼等信息,這些信息對加解密的要求也不一樣,比如說密碼我 ...
  • 摘要:JVM優化的目標就是:儘可能讓對象都在新生代里分配和回收,儘量別讓太多對象頻繁進入老年代,避免頻繁對老年代進行垃圾回收,同時給系統充足的記憶體大小,避免新生代頻繁的進行垃圾回收。 本文分享自華為雲社區《千萬不要在生產環境使用這個版本的JDK,這不?記憶體又溢出了!快要裂開了!(建議收藏)》,作者: ...
  • 《Python編程快速上手》PDF高清版免費下載地址 內容簡介 · · · · · · 如今,人們面臨的大多數任務都可以通過編寫電腦軟體來完成。Python是一種解釋型、面向對象、動態數據類型的高級程式設計語言。通過Python編程,我們能夠解決現實生活中的很多任務。 本書是一本面向實踐的Pyth ...
  • “什麼是Java虛擬機,為什麼要使用”。 最近一個1年Java開發經驗的同學去面試阿裡,遇到這個問題向我求助。 大家好,我是Mic,一個工作14年的Java程式員。 那麼,這個問題,面試官希望考察什麼呢? 問題解析 Java虛擬機,是Java應用程式運行的平臺。 很多初學者,第一步基本上都是學習怎麼 ...
  • 大家好啊,我是字母哥,今天寫一篇關於etcd的文章,其實網上也有很多關於etcd的介紹,我就簡明扼要,總結提煉,期望大家通過這一篇文章掌握etcd的核心知識以及編碼技能! 本文首先用大白話給大家介紹一下etcd是什麼?這部分內容網上已經有很多了。 etcd有哪些應用場景?這些應用場景的核心原理是什麼 ...
  • 1、工廠模式 在各種BeanFactory以及ApplicationContext創建中都有用到 2、模板模式 在各種BeanFactory以及ApplicationContext創建中都有用到 3、代理模式 SpringAOP利用了AspectJ AOP實現的,Aspectj AOP的底層用的就是 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...