关于springboot:SpringBoot之用拦截器避免重复请求

35次阅读

共计 6860 个字符,预计需要花费 18 分钟才能阅读完成。

拦截器

什么是拦截器

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 我的项目,如下图:

增加依赖

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");
    }
}

测试

正文完
 0