SpringBoot之用攔截器避免重覆請求

来源:https://www.cnblogs.com/okokabcd/archive/2022/11/21/16913680.html
-Advertisement-
Play Games

我們結合運算符重載知識實現string 類 在自己實現的String類中可以參考C++中string的方法 例如構造,加法,大小比較,長度,[] 等操作. 當前的MyString 類中,暫時不加入迭代器,我們將在下一節中加入迭代器的代碼. #include <iostream> using name ...


攔截器

什麼是攔截器

Spring MVC中的攔截器(Interceptor)類似於Servlet中的過濾器(Filter),它主要用於攔截用戶請求並作相應的處理。例如通過攔截器可以進行許可權驗證、記錄請求信息的日誌、判斷用戶是否登錄等。

如何自定義攔截器

自定義一個攔截器非常簡單,只需要實現HandlerInterceptor這個介面即可,這個介面有三個可實現的方法

  1. preHandle()方法:該方法會在控制器方法前執行,其返回值表示是否知道如何寫一個介面。中斷後續操作。當其返回值為true時,表示繼續向下執行;當其返回值為false時,會中斷後續的所有操作(包括調用下一個攔截器和控制器類中的方法執行等)。

  2. postHandle()方法:該方法會在控制器方法調用之後,且解析視圖之前執行。可以通過此方法對請求域中的模型和視圖做出進一步的修改。

  3. afterCompletion()方法:該方法會在整個請求完成,即視圖渲染結束之後執行。可以通過此方法實現一些資源清理、記錄日誌信息等工作。

如何讓攔截器在Spring Boot中生效

想要在Spring Boot生效其實很簡單,只需要定義一個配置類,實現WebMvcConfigurer這個介面,並且實現其中的addInterceptors()方法即可,代碼如下:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private XXX xxx;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 不攔截的uri
        final String[] commonExclude = {}};
        registry.addInterceptor(xxx).excludePathPatterns(commonExclude);
    }
}

用攔截器規避重覆請求

需求

開發中可能會經常遇到短時間內由於用戶的重覆點擊導致幾秒之內重覆的請求,可能就是在這幾秒之內由於各種問題,比如網路事務的隔離性等等問題導致了數據的重覆等問題,因此在日常開發中必須規避這類的重覆請求操作,今天就用攔截器簡單的處理一下這個問題。

思路

在介面執行之前先對指定介面(比如標註某個註解的介面)進行判斷,如果在指定的時間內(比如5秒)已經請求過一次了,則返回重覆提交的信息給調用者。

根據什麼判斷這個介面已經請求了?

根據項目的架構可能判斷的條件也是不同的,比如IP地址用戶唯一標識請求參數請求URI等等其中的某一個或者多個的組合。

這個具體的信息存放在哪?

由於是短時間內甚至是瞬間並且要保證定時失效,肯定不能存在事務性資料庫中了,因此常用的幾種資料庫中只有Redis比較合適了。

實現

Docker啟動一個Redis

docker pull redis:7.0.4

docker run -itd \
	--name redis \
	-p 6379:6379 \
	redis:7.0.4

創建一個Spring Boot項目

使用idea的Spring Initializr來創建一個Spring Boot項目,如下圖:

image.png

添加依賴

pom.xml文件如下

<?xml version="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>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.example</groupId>
    <artifactId>springboot_06</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_06</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--spring redis配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <!-- 1.5的版本預設採用的連接池技術是jedis  2.0以上版本預設連接池是lettuce, 在這裡採用jedis,所以需要排除lettuce的jar -->
            <exclusions>
                <exclusion>
                    <groupId>redis.clients</groupId>
                    <artifactId>jedis</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

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

</project>

配置Redis

application.properties

spring.redis.host=127.0.0.1
spring.redis.database=1
spring.redis.port=6379

定義一個註解

package com.example.springboot_06.intercept;

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

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
    /**
     * 預設失效時間5秒
     *
     * @return
     */
    long seconds() default 5;
}

創建一個攔截器

package com.example.springboot_06.intercept;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 重覆請求的攔截器
 *
 * @Component:該註解將其註入到IOC容器中
 */
@Slf4j
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {

    /**
     * Redis的API
     */
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * preHandler方法,在controller方法之前執行
     * <p>
     * 判斷條件僅僅是用了uri,實際開發中根據實際情況組合一個唯一識別的條件。
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            // 只攔截標註了@RepeatSubmit該註解
            HandlerMethod method = (HandlerMethod) handler;
            // 標註在方法上的@RepeatSubmit
            RepeatSubmit repeatSubmitByMethod = AnnotationUtils.findAnnotation(method.getMethod(), RepeatSubmit.class);
            // 標註在controler類上的@RepeatSubmit
            RepeatSubmit repeatSubmitByCls = AnnotationUtils.findAnnotation(method.getMethod().getDeclaringClass(), RepeatSubmit.class);
            // 沒有限制重覆提交,直接跳過
            if (Objects.isNull(repeatSubmitByMethod) && Objects.isNull(repeatSubmitByCls)) {
                log.info("isNull");
                return true;
            }

            // todo: 組合判斷條件,這裡僅僅是演示,實際項目中根據架構組合條件
            //請求的URI
            String uri = request.getRequestURI();

            //存在即返回false,不存在即返回true
            Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(uri, "",
                    Objects.nonNull(repeatSubmitByMethod) ? repeatSubmitByMethod.seconds() : repeatSubmitByCls.seconds(), TimeUnit.SECONDS);

            //如果存在,表示已經請求過了,直接拋出異常,由全局異常進行處理返回指定信息
            if (ifAbsent != null && !ifAbsent) {
                String msg = String.format("url:[%s]重覆請求", uri);
                log.warn(msg);
                // throw new RepeatSubmitException(msg);
                throw new Exception(msg);
            }
        }
        return true;
    }
}

配置攔截器

package com.example.springboot_06.config;
import com.example.springboot_06.intercept.RepeatSubmitInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private RepeatSubmitInterceptor repeatSubmitInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 不攔截的uri
        final String[] commonExclude = {"/error", "/files/**"};
        registry.addInterceptor(repeatSubmitInterceptor).excludePathPatterns(commonExclude);
    }
}

寫個測試Controller

package com.example.springboot_06.controller;

import com.example.springboot_06.intercept.RepeatSubmit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 標註了@RepeatSubmit註解,全部的介面都需要攔截
 *
 */
@Slf4j
@RestController
@RequestMapping("/user")
@RepeatSubmit
public class UserController {

    @RequestMapping("/save")
    public ResponseEntity save() {
        log.info("/user/save");
        return ResponseEntity.ok("save success");
    }
}

測試

image.png


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

-Advertisement-
Play Games
更多相關文章
  • 1.繪製Layout文件 首先新建一個layout文件, 命名為title_bar, 在裡面繪製標題欄, 我需要的是一個有返回鍵和當前頁面標題的titleBar 佈局代碼如下 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:andr ...
  • 故障 做開發項目做的好好的,點了下清理工具,Android虛擬機的視窗沒了 回到Device Manager可以看到虛擬機確實還打開著,但就是無法啟動界面 解決 多半是清理的時候清理掉了前臺,找了半天也沒找到再次打開前臺的方法,重新運行程式也可以正常運行,就是不彈出界面。考慮殺掉後臺,讓Emulat ...
  • 初始化地圖 引入 import * as esriLoader from 'esri-loader' 主要定義 private mapId: string = '' private map: any private mapview: any private markers: any = {} // ...
  • 前言: 學了三天,我們學習了 TS 的基本類型聲明,TS 的編譯,webpack 打包,其實也就差不多了,剩下的也就一些 類,繼承,構造函數,抽象類,泛型一些的,如果都細緻的講可能寫好久,感興趣的可以自己找資料細緻的去學一下 學習代碼或一個新語法,最好的方法無非就是做項目,從這個過程中學會如何去使用 ...
  • 對 Chrome 擴展功能熟悉的小伙伴,可能都有用過 Chrome 的 3D 展示頁面層級關係這個功能。 可以通過 控制台 --> 右邊的三個小點 --> More Tools --> Layers 打開。即可以看到頁面的一個 3D 層級關係,像是這樣: 這個功能有幾個不錯的作用: 頁面層級概覽 快 ...
  • 學習信息 學習形式:網路教學視頻 學習地址:https://www.bilibili.com/video/BV1Sy4y1C7ha/?spm_id_from=333.337.search-card.all.click 學習開始時間:2022年11月18日 01 初識 JavaScript 瀏覽器執行 ...
  • 先聲明一下:我所在的公司是一個小團隊,做物聯網相關的,前後端、硬體、測試加起來也就五六十個人左右;本人的崗位是Java開發(兼DBA、運維。。。);我進公司時整個項目的部署架構為 簡單jar包部署微服務集群形式;去年公司將部分服務使用docker進行部署;因為現在服務稍微有點多導致容器管理起來也比較 ...
  • # 1.索引(下標) print('1.索引') str_data = 'Python' # [索引(下標)]取索引的格式 # 正負索引 # 獲取單個數據 sub_str = str_data[4] print(sub_str) sub_str = str_data[-2] print(sub_st ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...