为了解决用户短时间内频繁访问一个接口,造成可能出现的数据异常或者为了减轻服务器的压力。必须对短时间内的频繁请求进行处理。
添加用到的依赖
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
自定义注解
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
/**
* 默认失效时间2秒
* @return
*/
long seconds() default 2;
}
自定义一个重复提交异常类
public class RepeatSubmitException extends RuntimeException{
public RepeatSubmitException(String msg){
super(msg);
}
}
创建一个重复请求拦截器
import com.example.demo.annotation.RepeatSubmit;
import com.example.demo.exception.RepeatSubmitException;
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 org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws RepeatSubmitException {
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))
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) {
throw new RepeatSubmitException("重复请求");
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
创建统一结果返回类
@Getter
@Setter
public class ResultResponse {
String msg;
public ResultResponse(String msg){
this.msg = msg;
}
}
创建全局统一异常处理类 处理重复请求异常
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 重复请求的异常
* @param ex
* @return
*/
@ExceptionHandler(RepeatSubmitException.class)
public ResultResponse onException(RepeatSubmitException ex){
log.error("错误异常:"+ex.getMessage());
return new ResultResponse("请勿重复提交");
}
}
测试
@RestController
//类上标注了@RepeatSubmit注解,该类全部请求都需要拦截
//@RepeatSubmit
public class LoginController {
/**
* 方法上标注@RepeatSubmit注解 只针对该请求进行拦截
*/
@RepeatSubmit
@GetMapping("/login")
public String toLogin(){
return "login success";
}
}
结果正常情况
错误情况