我們結合運算符重載知識實現string 類 在自己實現的String類中可以參考C++中string的方法 例如構造,加法,大小比較,長度,[] 等操作. 當前的MyString 類中,暫時不加入迭代器,我們將在下一節中加入迭代器的代碼. #include <iostream> using name ...
攔截器
什麼是攔截器
Spring MVC中的攔截器(Interceptor)類似於Servlet中的過濾器(Filter),它主要用於攔截用戶請求並作相應的處理。例如通過攔截器可以進行許可權驗證、記錄請求信息的日誌、判斷用戶是否登錄等。
如何自定義攔截器
自定義一個攔截器非常簡單,只需要實現HandlerInterceptor這個介面即可,這個介面有三個可實現的方法
-
preHandle()
方法:該方法會在控制器方法前執行,其返回值表示是否知道如何寫一個介面。中斷後續操作。當其返回值為true
時,表示繼續向下執行;當其返回值為false
時,會中斷後續的所有操作(包括調用下一個攔截器和控制器類中的方法執行等)。 -
postHandle()
方法:該方法會在控制器方法調用之後,且解析視圖之前執行。可以通過此方法對請求域中的模型和視圖做出進一步的修改。 -
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");
}
}