欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot基于過濾器和內(nèi)存實現(xiàn)重復請求攔截功能

 更新時間:2023年01月30日 16:16:56   作者:loginfo  
這篇文章主要介紹了SpringBoot基于過濾器和內(nèi)存實現(xiàn)重復請求攔截,這里我們使用過濾器的方式對進入服務器的請求進行過濾操作,實現(xiàn)對相同客戶端請求同一個接口的過濾,需要的朋友可以參考下

對于一些請求服務器的接口,可能存在重復發(fā)起請求,如果是查詢操作倒是并無大礙,但是如果涉及到寫入操作,一旦重復,可能對業(yè)務邏輯造成很嚴重的后果,例如交易的接口如果重復請求可能會重復下單。

這里我們使用過濾器的方式對進入服務器的請求進行過濾操作,實現(xiàn)對相同客戶端請求同一個接口的過濾。

 @Slf4j
 @Component
 public class IRequestFilter extends OncePerRequestFilter {
     @Resource
     private FastMap fastMap;
 ?
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
         String address = attributes != null ? attributes.getRequest().getRemoteAddr() : UUID.randomUUID().toString();
         if (Objects.equals(request.getMethod(), "GET")) {
             StringBuilder str = new StringBuilder();
             str.append(request.getRequestURI()).append("|")
                     .append(request.getRemotePort()).append("|")
                     .append(request.getLocalName()).append("|")
                     .append(address);
             String hex = DigestUtil.md5Hex(new String(str));
             log.info("請求的MD5值為:{}", hex);
             if (fastMap.containsKey(hex)) {
                 throw new IllegalStateException("請求重復,請稍后重試!");
             }
             fastMap.put(hex, 10 * 1000L);
             fastMap.expired(hex, 10 * 1000L, (key, val) -> System.out.println("map:" + fastMap + ",刪除的key:" + key + ",線程名:" + Thread.currentThread().getName()));
         }
         log.info("請求的 address:{}", address);
         chain.doFilter(request, response);
     }
 }

通過繼承Spring中的OncePerRequestFilter過濾器,確保在一次請求中只通過一次filter,而不需要重復的執(zhí)行

通過獲取請求體中的數(shù)據(jù),計算出MD5值,存儲在基于內(nèi)存實現(xiàn)的FastMap中,F(xiàn)astMap的鍵為MD5值,value表示多久以內(nèi)不能重復請求,這里配置的是10s內(nèi)不能重復請求。通過調(diào)用FastMap的expired()方法,設置該請求的過期時間和過期時的回調(diào)函數(shù)

 @Component
 public class FastMap {
     /**
      * 按照時間順序保存了會過期key集合,為了實現(xiàn)快速刪除,結(jié)構(gòu):時間戳 -> key 列表
      */
     private final TreeMap<Long, List<String>> expireKeysMap = new TreeMap<>();
     /**
      * 保存會過期key的過期時間
      */
     private final Map<String, Long> keyExpireMap = new ConcurrentHashMap<>();
     /**
      * 保存鍵過期的回調(diào)函數(shù)
      */
     private final HashMap<String, ExpireCallback<String, Long>> keyExpireCallbackMap = new HashMap<>();
     private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
     /**
      * 數(shù)據(jù)寫鎖
      */
     private final Lock dataWriteLock = readWriteLock.writeLock();
     /**
      * 數(shù)據(jù)讀鎖
      */
     private final Lock dataReadLock = readWriteLock.readLock();
     private final ReentrantReadWriteLock expireKeysReadWriteLock = new ReentrantReadWriteLock();
     /**
      * 過期key寫鎖
      */
     private final Lock expireKeysWriteLock = expireKeysReadWriteLock.writeLock();
     /**
      * 過期key讀鎖
      */
     private final Lock expireKeysReadLock = expireKeysReadWriteLock.readLock();
     /**
      * 定時執(zhí)行服務(全局共享線程池)
      */
     private volatile ScheduledExecutorService scheduledExecutorService;
     /**
      * 100萬,1毫秒=100萬納秒
      */
     private static final int ONE_MILLION = 100_0000;
     /**
      * 構(gòu)造器,enableExpire配置是否啟用過期,不啟用排序
      */
     public FastMap() {
         this.init();
     }
     /**
      * 初始化
      */
     private void init() {
         // 雙重校驗構(gòu)造一個單例的scheduledExecutorService
         if (scheduledExecutorService == null) {
             synchronized (FastMap.class) {
                 if (scheduledExecutorService == null) {
                     // 啟用定時器,定時刪除過期key,1秒后啟動,定時1秒, 因為時間間隔計算基于nanoTime,比timer.schedule更靠譜
                     scheduledExecutorService = new ScheduledThreadPoolExecutor(1, runnable -> {
                         Thread thread = new Thread(runnable, "expireTask-" + UUID.randomUUID());
                         thread.setDaemon(true);
                         return thread;
                     });
                 }
             }
         }
     }
     public boolean containsKey(Object key) {
         dataReadLock.lock();
         try {
             return this.keyExpireMap.containsKey(key);
         } finally {
             dataReadLock.unlock();
         }
     }
     public Long put(String key, Long value) {
         dataWriteLock.lock();
         try {
             return this.keyExpireMap.put(key, value);
         } finally {
             dataWriteLock.unlock();
         }
     }
     public Long remove(Object key) {
         dataWriteLock.lock();
         try {
             return this.keyExpireMap.remove(key);
         } finally {
             dataWriteLock.unlock();
         }
     }
     public Long expired(String key, Long ms, ExpireCallback<String, Long> callback) {
         // 對過期數(shù)據(jù)寫上鎖
         expireKeysWriteLock.lock();
         try {
             // 使用nanoTime消除系統(tǒng)時間的影響,轉(zhuǎn)成毫秒存儲降低timeKey數(shù)量,過期時間精確到毫秒級別
             Long expireTime = (System.nanoTime() / ONE_MILLION + ms);
             this.keyExpireMap.put(key, expireTime);
             List<String> keys = this.expireKeysMap.get(expireTime);
             if (keys == null) {
                 keys = new ArrayList<>();
                 keys.add(key);
                 this.expireKeysMap.put(expireTime, keys);
             } else {
                 keys.add(key);
             }
             if (callback != null) {
                 // 設置的過期回調(diào)函數(shù)
                 this.keyExpireCallbackMap.put(key, callback);
             }
             // 使用延時服務調(diào)用清理key的函數(shù),可以及時調(diào)用過期回調(diào)函數(shù)
             // 同key重復調(diào)用,會產(chǎn)生多個延時任務,就是多次調(diào)用清理函數(shù),但是不會產(chǎn)生多次回調(diào),因為回調(diào)取決于過期時間和回調(diào)函數(shù))
             scheduledExecutorService.schedule(this::clearExpireData, ms, TimeUnit.MILLISECONDS);
 ?
             //假定系統(tǒng)時間不修改前提下的過期時間
             return System.currentTimeMillis() + ms;
         } finally {
             expireKeysWriteLock.unlock();
         }
     }
     /**
      * 清理過期的數(shù)據(jù)
      * 調(diào)用時機:設置了過期回調(diào)函數(shù)的key的延時任務調(diào)用
      */
     private void clearExpireData() {
         // 查找過期key
         Long curTimestamp = System.nanoTime() / ONE_MILLION;
         Map<Long, List<String>> expiredKeysMap = new LinkedHashMap<>();
         expireKeysReadLock.lock();
         try {
             // 過期時間在【從前至此刻】區(qū)間內(nèi)的都為過期的key
             // headMap():獲取從頭到 curTimestamp 元素的集合:不包含 curTimestamp
             SortedMap<Long, List<String>> sortedMap = this.expireKeysMap.headMap(curTimestamp, true);
             expiredKeysMap.putAll(sortedMap);
         } finally {
             expireKeysReadLock.unlock();
         }
 ?
         for (Map.Entry<Long, List<String>> entry : expiredKeysMap.entrySet()) {
             for (String key : entry.getValue()) {
                 // 刪除數(shù)據(jù)
                 Long val = this.remove(key);
                 // 首次調(diào)用刪除(val!=null,前提:val存儲值都不為null)
                 if (val != null) {
                     // 如果存在過期回調(diào)函數(shù),則執(zhí)行回調(diào)
                     ExpireCallback<String, Long> callback;
                     expireKeysReadLock.lock();
                     try {
                         callback = this.keyExpireCallbackMap.get(key);
                     } finally {
                         expireKeysReadLock.unlock();
                     }
                     if (callback != null) {
                         // 回調(diào)函數(shù)創(chuàng)建新線程調(diào)用,防止因為耗時太久影響線程池的清理工作
                         // 這里為什么不用線程池調(diào)用,因為ScheduledThreadPoolExecutor線程池僅支持核心線程數(shù)設置,不支持非核心線程的添加
                         // 核心線程數(shù)用一個就可以完成清理工作,添加額外的核心線程數(shù)浪費了
                         new Thread(() -> callback.onExpire(key, val), "callback-thread-" + UUID.randomUUID()).start();
                     }
                 }
                 this.keyExpireCallbackMap.remove(key);
             }
             this.expireKeysMap.remove(entry.getKey());
         }
     }
 }

FastMap通過ScheduledExecutorService接口實現(xiàn)定時線程任務的方式對請求處于過期時間的自動刪除。

到此這篇關于SpringBoot基于過濾器和內(nèi)存實現(xiàn)重復請求攔截的文章就介紹到這了,更多相關SpringBoot重復請求攔截內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • JAVA進階之HashMap底層實現(xiàn)解析

    JAVA進階之HashMap底層實現(xiàn)解析

    Hashmap是java面試中經(jīng)常遇到的面試題,大部分都會問其底層原理與實現(xiàn),為了能夠溫故而知新,特地寫了這篇文章,以便時時學習
    2021-11-11
  • java?map的key值轉(zhuǎn)駝峰命名的方法

    java?map的key值轉(zhuǎn)駝峰命名的方法

    這篇文章主要介紹了java?map的key值轉(zhuǎn)駝峰,通過實例代碼介紹了Map把“_”形式的key轉(zhuǎn)化為駝峰形式,本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2023-12-12
  • Springboot集成Springbrick實現(xiàn)動態(tài)插件的步驟詳解

    Springboot集成Springbrick實現(xiàn)動態(tài)插件的步驟詳解

    這篇文章主要介紹了Springboot集成Springbrick實現(xiàn)動態(tài)插件的詳細過程,文中的流程通過代碼示例介紹的非常詳細,感興趣的同學可以參考一下
    2023-06-06
  • Activiti7與Spring以及Spring Boot整合開發(fā)

    Activiti7與Spring以及Spring Boot整合開發(fā)

    這篇文章主要介紹了Activiti7與Spring以及Spring Boot整合開發(fā),在Activiti中核心類的是ProcessEngine流程引擎,與Spring整合就是讓Spring來管理ProcessEngine,有感興趣的同學可以參考閱讀
    2023-03-03
  • java獲取ip地址與網(wǎng)絡接口的方法示例

    java獲取ip地址與網(wǎng)絡接口的方法示例

    這篇文章主要給大家介紹了關于利用java如何獲取ip地址與網(wǎng)絡接口的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。
    2018-01-01
  • Java負載均衡算法實現(xiàn)之輪詢和加權輪詢

    Java負載均衡算法實現(xiàn)之輪詢和加權輪詢

    網(wǎng)上找了不少負載均衡算法的資源,都不夠全面,后來自己結(jié)合了網(wǎng)上的一些算法實現(xiàn),下面這篇文章主要給大家介紹了關于Java負載均衡算法實現(xiàn)之輪詢和加權輪詢的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-04-04
  • 前端發(fā)送的請求Spring如何返回一個文件詳解

    前端發(fā)送的請求Spring如何返回一個文件詳解

    這篇文章主要給大家介紹了關于前端發(fā)送的請求Spring如何返回一個文件的相關資料,文中通過代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2024-09-09
  • Java導入導出csv格式文件完整版詳解(附代碼)

    Java導入導出csv格式文件完整版詳解(附代碼)

    在Java中你可以使用不同的庫來導出CSV格式的文件,這篇文章主要給大家介紹了關于Java導入導出csv格式文件的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-07-07
  • 基于Java實現(xiàn)互聯(lián)網(wǎng)實時聊天系統(tǒng)(附源碼)

    基于Java實現(xiàn)互聯(lián)網(wǎng)實時聊天系統(tǒng)(附源碼)

    Netty?是一個利用?Java?的高級網(wǎng)絡的能力,隱藏其背后的復雜性而提供一個易于使用的?API?的客戶端/服務器框架。本文將利用它實現(xiàn)互聯(lián)網(wǎng)實時聊天系統(tǒng),感興趣的可以了解一下
    2022-09-09
  • Spring boot部署發(fā)布到linux的操作方法

    Spring boot部署發(fā)布到linux的操作方法

    這篇文章主要介紹了Spring boot部署發(fā)布到linux的操作方法,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2017-05-05

最新評論