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

SpringBoot接口防抖(防重復提交)的實現方法

 更新時間:2024年11月27日 09:57:19   作者:陸卿之SIN  
SpringBoot接口防抖主要通過前端和后端兩種方式實現,前端通過JavaScript控制用戶操作,后端通過攔截器、過濾器等機制控制請求頻率,文中介紹的非常詳細,感興趣的可以了解一下

概念

Spring Boot接口防抖(Debouncing)的概念是指在處理請求時,通過一定的機制來防止用戶頻繁觸發(fā)同一接口請求,以防止重復提交或頻繁請求的情況發(fā)生。

在Web應用中,用戶可能會因為網絡延遲、操作失誤或者意外多次點擊提交按鈕,導致相同的請求被發(fā)送多次,從而引發(fā)數據的重復處理或者系統(tǒng)資源的浪費。接口防抖的目的就是在一定程度上限制這種重復請求的發(fā)生,保證系統(tǒng)的穩(wěn)定性和數據的一致性。

接口防抖通??梢酝ㄟ^以下幾種方式實現:

  • 前端防抖: 在前端頁面通過JavaScript等客戶端技術實現,對用戶的操作進行控制,例如利用定時器或者延遲執(zhí)行的方式來合并多個相同操作,確保只發(fā)送一次請求。
  • 后端防抖: 在后端服務器端實現,通過攔截器、過濾器等機制對相同請求的執(zhí)行頻率進行控制,攔截并處理重復的請求,防止其繼續(xù)向下執(zhí)行。

接口防抖通常需要考慮以下幾個方面:

  • 時間間隔設置: 確定兩次相同請求之間的時間間隔,即防抖的時間閾值,通常以毫秒為單位。
  • 處理方式: 當檢測到重復請求時,需要確定如何處理,可以是直接忽略、返回錯誤提示或者采取其他適當的措施。
  • 線程安全: 如果應用是多線程的或者是分布式的,需要考慮線程安全和分布式環(huán)境下的數據共享和同步問題,確保防抖機制的正確性和可靠性。

如何確定接口是重復

確定接口是否重復,一般可以通過以下幾種方式:

  • 請求參數比較: 比較接口請求的參數是否完全相同。如果接口的請求參數都一致,那么可以認為是相同的請求。
  • 請求路徑和請求方法比較: 比較接口的請求路徑(URL)和請求方法(GET、POST等)是否完全相同。如果請求路徑和請求方法都一致,那么可以認為是相同的請求。
  • 請求頭比較: 比較接口的請求頭信息是否完全相同。請求頭包含了很多關于請求的元數據,如用戶代理、授權信息等。如果請求頭信息完全相同,那么可以認為是相同的請求。
  • 請求體比較: 對于具有請求體的POST、PUT等請求,可以比較請求體的內容是否完全相同。如果請求體內容一致,那么可以認為是相同的請求。
  • IP地址和用戶標識比較: 可以通過客戶端的IP地址和用戶標識來判斷請求是否來自同一個客戶端。如果兩個請求具有相同的IP地址和用戶標識,那么可以認為是相同的請求。

根據時間戳來防抖

DebounceController.java

package com.sin.controller;// 需要先在pom.xml中添加Spring Web依賴

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.concurrent.ConcurrentHashMap;

/**
 * @createTime 2024/6/4 11:17
 * @createAuthor SIN
 * @use 時間戳防抖
 */
@Controller
@RequestMapping("/api")
public class DebounceController {

    // 用于存儲接口請求的時間戳
    private final ConcurrentHashMap<String, Long> requestTimestamps = new ConcurrentHashMap<>();

    @PostMapping("/submit")
    @ResponseBody
    public String submit() {
        // 接口路徑為"/api/submit",模擬防抖處理
        String key = "/api/submit";

        // 獲取當前時間戳
        long currentTimestamp = System.currentTimeMillis();

        // 上一次請求的時間戳
        Long lastTimestamp = requestTimestamps.get(key);

        // 如果上一次請求時間不為空,并且與當前時間間隔小于5000毫秒(5秒),則認為是重復請求,直接返回提示
        if (lastTimestamp != null && currentTimestamp - lastTimestamp < 5000) {
            return "重復提交,請稍后再試!";
        }

        // 記錄當前請求時間戳
        requestTimestamps.put(key, currentTimestamp);

        // 返回處理結果
        return "提交成功!";
    }
}

  • 第一次提交

在這里插入圖片描述

  • 第二次提交

在這里插入圖片描述

分布式下如何做防抖

在分布式環(huán)境下,防抖(防重復提交)需要考慮多個節(jié)點之間的數據同步和并發(fā)控制。以下是一種在分布式環(huán)境下實現防抖的方法:

  • 使用分布式緩存: 可以使用分布式緩存來存儲接口請求的時間戳信息。常見的分布式緩存系統(tǒng)包括Redis、Memcached等。通過在緩存中存儲請求的時間戳,并設置適當的過期時間,可以實現簡單的防抖功能。
  • 使用分布式鎖: 在處理防抖邏輯時,可以使用分布式鎖來確保同一時刻只有一個節(jié)點可以執(zhí)行特定的代碼塊。當某個節(jié)點獲取到鎖時,執(zhí)行防抖邏輯并更新緩存中的時間戳信息,其他節(jié)點在嘗試獲取鎖時可以判斷緩存中的時間戳信息,從而避免重復提交。

分布式緩存

pom.xml

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

application.yml

spring:
  data:
    redis:
      host: 192.168.226.134
      password: 123456

RedisDebounceController.java

package com.sin.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

/**
 * @createTime 2024/6/4 11:17
 * @createAuthor SIN
 * @use 分布式緩存(Redis)防抖
 */
@RestController
@RequestMapping("/api")
public class RedisDebounceController {

    private static final String REQUEST_KEY = "submit:request";

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @PostMapping("/redisSubmit")
    public String submit() {
        // 檢查Redis中是否存在請求標記
        if (redisTemplate.hasKey(REQUEST_KEY)) {
            return "重復提交,請稍后再試!";
        }

        // 將請求標記寫入Redis,并設置過期時間
        redisTemplate.opsForValue().set(REQUEST_KEY, "1", 5, TimeUnit.SECONDS);

        // 返回處理結果
        return "提交成功!";
    }
}
  • 第一次提交

在這里插入圖片描述

  • 第二次提交

在這里插入圖片描述

在這里插入圖片描述

使用了固定的鍵名"submit:request"來存儲接口請求的標記,Redis中是否存在請求標記,如果存在則認為是重復提交,直接返回提示信息。如果不存在請求標記,則將請求標記寫入Redis,并設置過期時間為5秒,以確保在此時間內同一個接口不能重復提交

分布式鎖

RedisLockDebounceController.java

package com.sin.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @createTime 2024/6/4 11:29
 * @createAuthor SIN
 * @use 使用分布式鎖防抖
 */
@RestController
@RequestMapping("/api")
public class RedisLockDebounceController {
    private static final long LOCK_EXPIRE_TIME = 10000L; // 鎖的過期時間,單位毫秒
    private static final long DEBOUNCE_TIME = 10000L; // 防抖時間,單位毫秒

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @PostMapping("/redis/lock")
    public String acquireLock(String key) {
        String lockKey = key; // 鎖的鍵名為傳入的 key 參數
        String requestId = String.valueOf(System.currentTimeMillis()); // 請求 ID 為當前時間戳的字符串形式

        /**
         * Lua 腳本的作用是嘗試獲取分布式鎖。它通過 SETNX 命令嘗試在 Redis 中設置一個鍵的值,如果設置成功,則進一步設置該鍵的過期時間,并返回 true 表示獲取鎖成功;如果設置失敗,則表示鎖已被其他客戶端獲取,返回 false 表示獲取鎖失敗。
         * RedisScript<Boolean>: Spring Data Redis 提供的用于執(zhí)行 Lua 腳本的接口
         * DefaultRedisScript<>(script,Boolean.class):RedisScript 的實例化操作,
         *          script 參數是一個字符串類型的 Lua 腳本,表示要執(zhí)行的 Redis 操作。
         *          Boolean.class 參數指定了腳本執(zhí)行后的返回類型為布爾值。
         * if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 then: Redis 的 SETNX 命令,用于在 Redis 中設置一個鍵的值,但只有在該鍵不存在時才設置成功。
         *          KEYS[1] 表示 Lua 腳本中傳入的鍵的數組,這里取第一個鍵。
         *          ARGV[1] 表示 Lua 腳本中傳入的參數的數組,這里取第一個參數。
         *          如果 SETNX 返回值為 1,表示設置成功,即之前該鍵不存在,執(zhí)行 then 代碼塊中的操作。
         * redis.call('PEXPIRE', KEYS[1], ARGV[2]):如果 SETNX 操作成功,接著調用了 Redis 的 PEXPIRE 命令,用于設置鍵的過期時間。
         *          KEYS[1] 表示要設置過期時間的鍵,
         *          ARGV[2] 表示傳入的第二個參數,即鎖的過期時間。
         * return true:如果 SETNX 操作成功,并且設置了過期時間,最終返回 Lua 腳本執(zhí)行結果為 true,表示獲取鎖成功。
         * end:結束 if 條件語句塊。
         * return false:如果 SETNX 操作失敗,即之前該鍵已存在,或者設置過程中出現異常,最終返回 Lua 腳本執(zhí)行結果為 false,表示獲取鎖失敗。
         */
        RedisScript<Boolean> script = new DefaultRedisScript<>(
                "if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 then " +
                        "redis.call('PEXPIRE', KEYS[1], ARGV[2]) " +
                        "return true " +
                        "end " +
                        "return false", Boolean.class);

        // 創(chuàng)建一個包含元素的列表,該元素時LockKey即為鎖的鍵名
        List<String> keys = Collections.singletonList(lockKey);
        /**
         * 執(zhí)行redis的操作
         * script:之前創(chuàng)建的RedisScript的對象,用于執(zhí)行Lua腳本
         * keys:Lua腳本中的Keys參數,即為鍵的數組,只有一個鍵,即鎖的鍵名
         * requestId:Lua 腳本中的 ARGV 參數,即參數的數組,傳入了請求 ID,用于標識這次獲取鎖的請求
         * String.valueOf(LOCK_EXPIRE_TIME):Lua 腳本中的 ARGV 參數,即參數的數組。傳入了鎖的過期時間,以毫秒為單位
         */
        Boolean result = redisTemplate.execute(script, keys, requestId, String.valueOf(LOCK_EXPIRE_TIME));

        // 如果 result 不為 null,并且為真(即成功獲取了鎖)
        if (result != null && result) {
            try {
                // 模擬處理邏輯
                Thread.sleep(1000);

                // 檢查是否在防抖時間內有重復請求
                if (isDuplicateRequest(key)) {
                    return "重復提交,請稍后再試!";
                }

                // 返回處理結果
                return "獲取鎖成功!";
            //捕獲可能發(fā)生的線程中斷異常,
            } catch (InterruptedException e) {
                // 將當前線程重新標記為中斷狀態(tài)
                Thread.currentThread().interrupt();
                return "獲取鎖時發(fā)生異常:" + e.getMessage();
            } finally {
                // 釋放鎖
                releaseLock(lockKey, requestId);
            }
        } else {
            return "獲取鎖失敗,請稍后再試!";
        }
    }

    /**
     * 防止重復請求
     * @param key 鍵,即鎖的鍵名
     * @return
     */
    private boolean isDuplicateRequest(String key) {
        // 檢查是否在防抖時間內有重復請求
        String lastRequestTime = redisTemplate.opsForValue().get("lastRequestTime:" + key); // 獲取上次請求時間
        long currentTime = System.currentTimeMillis(); // 當前時間戳
        // 如果上次請求時間不為 null(即 Redis 中存在上次請求時間),且當前時間距離上次請求時間小于防抖時間 DEBOUNCE_TIME(10000L),則認為發(fā)生了重復請求,返回 true。
        if (lastRequestTime != null && currentTime - Long.parseLong(lastRequestTime) < DEBOUNCE_TIME) { // 如果防抖時間內有重復請求,則返回 true
            return true;
        } else {
            // 如果沒有發(fā)生重復請求,則將當前時間戳保存到 Redis 中,作為上次請求時間。同時設置了過期時間 DEBOUNCE_TIME(10000L),以毫秒為單位。
            redisTemplate.opsForValue().set("lastRequestTime:" + key, String.valueOf(currentTime), DEBOUNCE_TIME, TimeUnit.MILLISECONDS); // 否則將當前時間作為上次請求時間并設置過期時間,返回 false
            return false;
        }
    }

    /**
     * 釋放鎖
     * @param lockKey 接受鎖的鍵
     * @param requestId 請求標識作為參數
     */
    private void releaseLock(String lockKey, String requestId) {
        // 釋放鎖。腳本中的 KEYS[1] 和 ARGV[1] 會分別被傳入 keys 和 requestId 參數替換
        String releaseLockScript = "if redis.call('GET', KEYS[1]) == ARGV[1] then " +
                "return redis.call('DEL', KEYS[1]) " +
                "else " +
                "return 0 " +
                "end";
        // 將 Lua 腳本字符串轉換為 RedisScript 對象,指定了返回類型為 Long
        RedisScript<Long> script = new DefaultRedisScript<>(releaseLockScript, Long.class);
        // 創(chuàng)建了一個包含鎖鍵的列表,作為 Lua 腳本的 KEYS 參數。
        List<String> keys = Collections.singletonList(lockKey);
        // 執(zhí)行 Lua 腳本,傳入了腳本對象、鍵列表和請求標識作為參數,從而釋放了鎖
        redisTemplate.execute(script, keys, requestId);
    }
}


  • 第一次訪問

在這里插入圖片描述

  • 第二次訪問

在這里插入圖片描述

在這里插入圖片描述

到此這篇關于SpringBoot接口防抖(防重復提交)的實現方法的文章就介紹到這了,更多相關SpringBoot接口防抖內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論