自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

SpringBoot 3.3 接口防抖的一些實(shí)現(xiàn)方案,超贊!

開發(fā) 前端
在本文中,我們深入探討了幾種常見(jiàn)的接口防抖策略——時(shí)間窗口、令牌桶、滑動(dòng)窗口,并展示了如何在 Spring Boot 3.3 項(xiàng)目中實(shí)現(xiàn)這些策略。

在現(xiàn)代 Web 應(yīng)用中,前端與后端的交互頻繁而復(fù)雜,用戶的操作如按鈕點(diǎn)擊、表單提交等,都會(huì)引發(fā)向后端發(fā)送請(qǐng)求。雖然這些請(qǐng)求中的多數(shù)是有意義的,但一些場(chǎng)景下,用戶的誤操作或網(wǎng)絡(luò)波動(dòng)可能導(dǎo)致同一請(qǐng)求在短時(shí)間內(nèi)被重復(fù)觸發(fā)。這種重復(fù)請(qǐng)求不僅會(huì)增加服務(wù)器的負(fù)擔(dān),消耗寶貴的資源,還可能引發(fā)數(shù)據(jù)不一致性的問(wèn)題。因此,如何有效地防止接口被頻繁調(diào)用,成為了開發(fā)者必須解決的問(wèn)題。

接口防抖是一種常見(jiàn)的優(yōu)化手段,通過(guò)延遲請(qǐng)求的處理或限制請(qǐng)求的頻率,來(lái)確保在一定時(shí)間內(nèi)只執(zhí)行一次操作,從而減少服務(wù)器負(fù)擔(dān)。本文將深入探討幾種常見(jiàn)的接口防抖策略及其在 Spring Boot 3.3 項(xiàng)目中的實(shí)現(xiàn),并展示如何通過(guò)配置來(lái)靈活選擇不同的防抖方案。此外,還將介紹如何在前端使用 Jquery 實(shí)現(xiàn)按鈕的防抖點(diǎn)擊,從而進(jìn)一步優(yōu)化用戶體驗(yàn)。

什么是接口防抖

接口防抖(Debounce)是一種前后端結(jié)合的技術(shù)手段,主要用于防止在短時(shí)間內(nèi)多次觸發(fā)同一操作。通常情況下,用戶可能會(huì)因?yàn)榫W(wǎng)絡(luò)延遲、誤點(diǎn)擊等原因在短時(shí)間內(nèi)多次發(fā)送相同的請(qǐng)求,如果不加以控制,這些請(qǐng)求會(huì)同時(shí)傳遞到服務(wù)器,導(dǎo)致服務(wù)器處理多次同一業(yè)務(wù)邏輯,從而造成資源浪費(fèi)甚至系統(tǒng)崩潰。

防抖技術(shù)可以通過(guò)延遲處理或限制頻率,確保在指定時(shí)間內(nèi)同一操作只被執(zhí)行一次。例如,用戶在點(diǎn)擊按鈕時(shí),如果短時(shí)間內(nèi)多次點(diǎn)擊,只有第一個(gè)請(qǐng)求會(huì)被處理,后續(xù)請(qǐng)求將被忽略。這種機(jī)制不僅可以優(yōu)化服務(wù)器性能,還能提升用戶體驗(yàn)。

運(yùn)行效果:

圖片圖片

圖片圖片

若想獲取項(xiàng)目完整代碼以及其他文章的項(xiàng)目源碼,且在代碼編寫時(shí)遇到問(wèn)題需要咨詢交流,歡迎加入下方的知識(shí)星球。

項(xiàng)目基礎(chǔ)配置

pom.xml 配置

首先,在 pom.xml 中配置 Spring Boot、Thymeleaf、Bootstrap,以及其他相關(guān)依賴:

<?xml versinotallow="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.3.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.icoderoad</groupId>
	<artifactId>debounce</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>debounce</name>
	<description>Demo project for Spring Boot</description>
	
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-web</artifactId>
	    </dependency>
	    
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-thymeleaf</artifactId>
	    </dependency>

	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-data-redis</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-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

application.yml 配置

在 application.yml 中配置接口防抖策略的選擇:

server:
  port: 8080
  
spring:
  debounce:
    strategy: time-window # 可選值:time-window, token-bucket, sliding-window
    time-window:
      duration: 1000 # 時(shí)間窗口長(zhǎng)度(毫秒)
    token-bucket:
      capacity: 10  # 令牌桶容量
      refill-rate: 1 # 令牌補(bǔ)充速率(每秒)
    sliding-window:
      size: 5  # 滑動(dòng)窗口大小
      interval: 1000 # 時(shí)間間隔(毫秒)

接口防抖策略實(shí)現(xiàn)

定義 DebounceStrategy 接口

package com.icoderoad.debounce.strategy;

public interface DebounceStrategy {
    /**
     * 判斷當(dāng)前請(qǐng)求是否應(yīng)該被處理
     *
     * @param key 唯一標(biāo)識(shí)(如用戶ID、IP等)
     * @return 如果應(yīng)該處理請(qǐng)求,返回true;否則返回false
     */
    boolean shouldProceed(String key);
}

時(shí)間窗口防抖(time-window)

時(shí)間窗口防抖策略(Time Window Debounce)是最簡(jiǎn)單的一種防抖機(jī)制,它允許在一個(gè)固定的時(shí)間窗口內(nèi)只執(zhí)行一次操作。例如,在設(shè)置了 1 秒的時(shí)間窗口后,無(wú)論用戶在這一秒內(nèi)點(diǎn)擊多少次按鈕,系統(tǒng)只會(huì)響應(yīng)第一次點(diǎn)擊,其余的點(diǎn)擊將被忽略。時(shí)間窗口策略適用于那些短時(shí)間內(nèi)不希望重復(fù)操作的場(chǎng)景。

時(shí)間窗口策略實(shí)現(xiàn)

首先,實(shí)現(xiàn)時(shí)間窗口策略:

package com.icoderoad.debounce.strategy;

import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("timeWindowStrategy")
public class TimeWindowStrategy implements DebounceStrategy {

    private final long durationMillis;
    private final ConcurrentHashMap<String, Long> requestTimes = new ConcurrentHashMap<>();

    public TimeWindowStrategy(@Value("${spring.debounce.time-window.duration}") long durationMillis) {
        this.durationMillis = durationMillis;
    }

    @Override
    public boolean shouldProceed(String key) {
        long currentTime = System.currentTimeMillis();
        Long lastRequestTime = requestTimes.put(key, currentTime);

        if (lastRequestTime == null || currentTime - lastRequestTime >= durationMillis) {
            return true;
        } else {
            return false;
        }
    }
}

令牌桶防抖(token-bucket)

令牌桶防抖策略(Token Bucket Debounce)通過(guò)維護(hù)一個(gè)令牌桶,每次請(qǐng)求需要消耗一個(gè)令牌。當(dāng)令牌桶為空時(shí),請(qǐng)求將被拒絕或延遲處理。令牌會(huì)以固定的速率被重新生成,確保在長(zhǎng)時(shí)間內(nèi)的請(qǐng)求可以被平穩(wěn)處理。令牌桶策略適用于那些需要控制請(qǐng)求速率的場(chǎng)景,如 API 限流。

令牌桶策略實(shí)現(xiàn)

package com.icoderoad.debounce.strategy;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;

@Component("tokenBucketStrategy")
public class TokenBucketStrategy implements DebounceStrategy {

    private final int capacity;
    private final int refillRate;
    private final ConcurrentHashMap<String, Semaphore> tokenBuckets = new ConcurrentHashMap<>();

    public TokenBucketStrategy(
            @Value("${spring.debounce.token-bucket.capacity}") int capacity,
            @Value("${spring.debounce.token-bucket.refill-rate}") int refillRate) {
        this.capacity = capacity;
        this.refillRate = refillRate;
        startRefillTask();
    }

    @Override
    public boolean shouldProceed(String key) {
        Semaphore semaphore = tokenBuckets.computeIfAbsent(key, k -> new Semaphore(capacity));
        return semaphore.tryAcquire();
    }

    private void startRefillTask() {
        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000 / refillRate);
                    tokenBuckets.forEach((key, semaphore) -> semaphore.release(1));
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }).start();
    }
}

滑動(dòng)窗口防抖(sliding-window)

滑動(dòng)窗口防抖策略(Sliding Window Debounce)通過(guò)在一個(gè)固定的時(shí)間窗口內(nèi)統(tǒng)計(jì)請(qǐng)求次數(shù),并將其滑動(dòng)以覆蓋整個(gè)時(shí)間區(qū)間。只允許在這個(gè)窗口內(nèi)的一定次數(shù)的請(qǐng)求通過(guò)?;瑒?dòng)窗口策略適用于需要在一段時(shí)間內(nèi)精確控制請(qǐng)求次數(shù)的場(chǎng)景。

滑動(dòng)窗口策略實(shí)現(xiàn)

package com.icoderoad.debounce.strategy;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;

@Component("slidingWindowStrategy")
public class SlidingWindowStrategy implements DebounceStrategy {

    private final int maxSize;
    private final long intervalMillis;
    private final ConcurrentHashMap<String, LinkedBlockingQueue<Long>> requestTimestamps = new ConcurrentHashMap<>();

    public SlidingWindowStrategy(
            @Value("${spring.debounce.sliding-window.size}") int maxSize,
            @Value("${spring.debounce.sliding-window.interval}") long intervalMillis) {
        this.maxSize = maxSize;
        this.intervalMillis = intervalMillis;
    }

    @Override
    public boolean shouldProceed(String key) {
        long currentTime = System.currentTimeMillis();
        LinkedBlockingQueue<Long> timestamps = requestTimestamps.computeIfAbsent(key, k -> new LinkedBlockingQueue<>());

        synchronized (timestamps) {
            while (!timestamps.isEmpty() && currentTime - timestamps.peek() > intervalMillis) {
                timestamps.poll();
            }

            if (timestamps.size() < maxSize) {
                timestamps.offer(currentTime);
                return true;
            } else {
                return false;
            }
        }
    }
}

策略選擇器

package com.icoderoad.debounce.strategy;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class DebounceStrategySelector {

    @Value("${spring.debounce.strategy}")
    private String strategy;

    private final TimeWindowStrategy timeWindowStrategy;
    private final TokenBucketStrategy tokenBucketStrategy;
    private final SlidingWindowStrategy slidingWindowStrategy;

    @Autowired
    public DebounceStrategySelector(
    		TimeWindowStrategy timeWindowStrategy, 
    		TokenBucketStrategy tokenBucketStrategy, 
    		SlidingWindowStrategy slidingWindowStrategy) {
        this.timeWindowStrategy = timeWindowStrategy;
        this.tokenBucketStrategy = tokenBucketStrategy;
        this.slidingWindowStrategy = slidingWindowStrategy;
    }

    public DebounceStrategy select() {
        switch (strategy) {
            case "token-bucket":
                return tokenBucketStrategy;
            case "sliding-window":
                return slidingWindowStrategy;
            case "time-window":
            default:
                return timeWindowStrategy;
        }
    }
}

自定義注解

package com.icoderoad.debounce.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Debounce {
    String key() default "";
    long duration() default 1000;
}

AOP 實(shí)現(xiàn)

將防抖策略的選擇集成到 AOP 中:

package com.icoderoad.debounce.aspect;

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.stereotype.Component;

import com.icoderoad.debounce.strategy.DebounceStrategy;
import com.icoderoad.debounce.strategy.DebounceStrategySelector;
import com.icoderoad.debounce.strategy.SlidingWindowStrategy;
import com.icoderoad.debounce.strategy.TimeWindowStrategy;
import com.icoderoad.debounce.strategy.TokenBucketStrategy;

@Aspect
@Component
public class DebounceAspect {

    private final DebounceStrategySelector strategySelector;

    @Autowired
    public DebounceAspect(DebounceStrategySelector strategySelector) {
        this.strategySelector = strategySelector;
    }

    @Around("@annotation(debounce)")
    public Object around(ProceedingJoinPoint joinPoint, Debounce debounce) throws Throwable {
        DebounceStrategy strategy = strategySelector.select();
        String key = debounce.key();
        if (strategy instanceof TimeWindowStrategy) {
            if (((TimeWindowStrategy) strategy).shouldProceed(key)) {
                return joinPoint.proceed();
            }
        } else if (strategy instanceof TokenBucketStrategy) {
            if (((TokenBucketStrategy) strategy).shouldProceed(key)) {
                return joinPoint.proceed();
            }
        } else if (strategy instanceof SlidingWindowStrategy) {
            if (((SlidingWindowStrategy) strategy).shouldProceed(key)) {
                return joinPoint.proceed();
            }
        }

        throw new RuntimeException("請(qǐng)求頻率過(guò)高,請(qǐng)稍后再試。");
    }
}

自定義異常類

首先,可以定義一個(gè)自定義異常類 TooManyRequestsException:

package com.icoderoad.debounce.exception;

public class TooManyRequestsException extends RuntimeException {
    public TooManyRequestsException(String message) {
        super(message);
    }
}

創(chuàng)建錯(cuò)誤響應(yīng)體

定義一個(gè)簡(jiǎn)單的 ErrorResponse 類,用于返回錯(cuò)誤信息:

package com.icoderoad.debounce.exception;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ErrorResponse {

	private String message;
}

創(chuàng)建自定義異常處理

使用 @ControllerAdvice 和 @ExceptionHandler 處理 TooManyRequestsException 異常,并返回自定義的響應(yīng)體和狀態(tài)碼:

package com.icoderoad.debounce.handler;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import com.icoderoad.debounce.exception.ErrorResponse;
import com.icoderoad.debounce.exception.TooManyRequestsException;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(TooManyRequestsException.class)
    public ResponseEntity<Object> handleTooManyRequestsException(TooManyRequestsException ex) {
        // 返回 429 狀態(tài)碼和自定義錯(cuò)誤信息
        return ResponseEntity
                .status(HttpStatus.TOO_MANY_REQUESTS)
                .body(new ErrorResponse("請(qǐng)求頻率過(guò)高,請(qǐng)稍后再試!"));
    }

}

接口控制器實(shí)現(xiàn) DebounceController

package com.icoderoad.debounce.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.icoderoad.debounce.aspect.Debounce;

@RestController
public class DebounceController {

    @Debounce(key = "debounceTest")
    @GetMapping("/api/debounce-test")
    public ResponseEntity<String> debounceTest() {
        return ResponseEntity.ok("請(qǐng)求成功!");
    }
}

視圖控制器

package com.icoderoad.debounce.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {

    @GetMapping("/")
    public String index() {
        return "index";
    }
    
}

前端實(shí)現(xiàn)

前端使用 Bootstrap 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的按鈕觸發(fā)接口調(diào)用的頁(yè)面,并展示防抖效果。

在src/main/resources/templates目錄下創(chuàng)建文件 index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>接口防抖測(cè)試</title>
    <!-- 使用HTTP協(xié)議引入Bootstrap和jQuery -->
    <link rel="stylesheet" >
    <script src="http://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
<body>
<div class="container mt-5">
    <h1 class="mb-4">接口防抖測(cè)試</h1>
    
    <!-- 按鈕,點(diǎn)擊后觸發(fā)請(qǐng)求 -->
    <button id="debounceButton" class="btn btn-primary btn-lg">觸發(fā)請(qǐng)求</button>

    <!-- 用于顯示響應(yīng)結(jié)果的div -->
    <div id="responseMessage" class="mt-3 alert" style="display:none;"></div>
</div>

<!-- 引入Bootstrap的JS文件 -->
<script src="http://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js"></script>

<script>
    /**
     * 防抖函數(shù)
     * @param func 需要防抖的函數(shù)
     * @param delay 延遲時(shí)間,單位毫秒
     * @returns {Function}
     */
    function debounce(func, delay) {
        let timer = null;
        return function(...args) {
            if (timer) clearTimeout(timer);
            timer = setTimeout(() => {
                func.apply(this, args);
                timer = null;
            }, delay);
        };
    }

    $(document).ready(function () {
        const $button = $('#debounceButton');
        const $responseDiv = $('#responseMessage');

        /**
         * 發(fā)送請(qǐng)求的函數(shù)
         */
        function sendRequest() {
            $.ajax({
                url: '/api/test',
                method: 'GET',
                success: function(data) {
                    $responseDiv
                        .removeClass('alert-danger')
                        .addClass('alert-success')
                        .text(data)
                        .show();
                },
                error: function(xhr) {
                    $responseDiv
                        .removeClass('alert-success')
                        .addClass('alert-danger')
                        .text('請(qǐng)求過(guò)于頻繁,請(qǐng)稍后再試!')
                        .show();
                }
            });
        }

        // 使用防抖函數(shù)包裝sendRequest,防止頻繁點(diǎn)擊
        const debouncedSendRequest = debounce(sendRequest, 500); // 500毫秒內(nèi)只執(zhí)行一次

        $button.on('click', debouncedSendRequest);
    });
</script>
</body>
</html>

總結(jié)

在本文中,我們深入探討了幾種常見(jiàn)的接口防抖策略——時(shí)間窗口、令牌桶、滑動(dòng)窗口,并展示了如何在 Spring Boot 3.3 項(xiàng)目中實(shí)現(xiàn)這些策略。通過(guò)配置文件的靈活選擇,可以在不同場(chǎng)景下使用不同的防抖策略,從而優(yōu)化系統(tǒng)的性能和穩(wěn)定性。此外,我們還介紹了如何在前端頁(yè)面中使用 Jquery 實(shí)現(xiàn)按鈕的防抖點(diǎn)擊,進(jìn)一步防止用戶重復(fù)操作。通過(guò)這些防抖手段,可以有效地降低系統(tǒng)的負(fù)載,提高用戶體驗(yàn),避免潛在的系統(tǒng)風(fēng)險(xiǎn)。

責(zé)任編輯:武曉燕 來(lái)源: 路條編程
相關(guān)推薦

2024-08-29 15:26:21

2024-05-28 09:26:46

2024-09-05 09:38:55

SpringActuator應(yīng)用程序

2024-06-14 09:30:58

2024-09-09 11:35:35

2022-05-15 22:08:58

ReactHookdebounce

2021-06-10 20:17:04

云網(wǎng)融合超融合

2021-08-10 15:37:34

鴻蒙HarmonyOS應(yīng)用

2009-08-06 16:01:30

C#接口成員

2024-01-30 10:11:00

SpringBoot項(xiàng)目開發(fā)

2015-08-28 09:29:37

Volley框架

2021-02-24 15:16:45

微服務(wù)架構(gòu)數(shù)據(jù)

2009-11-25 13:07:53

2021-03-30 10:46:42

SpringBoot計(jì)數(shù)器漏桶算法

2021-09-12 07:33:23

python管理編程

2023-12-12 10:54:55

MySQL模式InnoDB

2022-04-02 14:43:59

Promethues監(jiān)控

2017-11-03 09:40:27

數(shù)據(jù)庫(kù)MySQLMHA

2024-09-03 10:44:32

2009-06-18 14:54:52

Spring AOP
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)