news 2026/6/5 9:13:02

SpringBoot拦截器防重复提交实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot拦截器防重复提交实战

springboot防重复提交实现

    • 自定义注解
    • 拦截器逻辑
    • 配置拦截器
    • 抛异常代码
  • 开发中可能会经常遇到短时间内由于用户的重复点击导致几秒之内重复的请求,可能就是在这几秒之内由于各种问题,比如 网络 ,事务的隔离性等等问题导致了数据的重复等问题,因此在日常开发中必须规
    避这类的重复请求操作,今天就用拦截器处理一下这个问题

自定义注解

packagetest;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public@interfaceRepeatSubmit{/** * 默认失效时间时间(秒),小于或等于0表示不启用 */longseconds()default5;}

拦截器逻辑

packagetest;importcom.ruoyi.common.exception.RepeatSubmitException;importcom.sun.org.apache.xpath.internal.operations.Bool;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.core.annotation.AnnotationUtils;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Component;importorg.springframework.util.StringUtils;importorg.springframework.web.method.HandlerMethod;importorg.springframework.web.servlet.HandlerInterceptor;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.util.Objects;importjava.util.concurrent.TimeUnit;/** * 重复请求的拦截器 * * @Component:注解将当前类注入到IOC容器中 */@ComponentpublicclassRepeatSubmitInterceptorimplementsHandlerInterceptor{@AutowiredprivateStringRedisTemplatestringRedisTemplate;@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{if(handlerinstanceofHandlerMethod){//只拦截@RepeatSubmit注解HandlerMethodmethod=(HandlerMethod)handler;//标注在方法上的注解RepeatSubmitrepeatSubmitByMethod=AnnotationUtils.findAnnotation(method.getMethod(),RepeatSubmit.class);//标注在类上的注解RepeatSubmitrepeatSubmitByCls=AnnotationUtils.findAnnotation(method.getMethod().getDeclaringClass(),RepeatSubmit.class);//组合判断条件,根据自己项目实际需求来,这里只用简单的身份标识做拦截示例//没有限制重复提交,直接跳过if(Objects.isNull(repeatSubmitByMethod)&&Objects.isNull(repeatSubmitByCls))returntrue;//优先使用方法级注解,其次使用类级注解RepeatSubmitrepeatSubmit=Objects.nonNull(repeatSubmitByMethod)?repeatSubmitByMethod:repeatSubmitByCls;//验证注解配置的有效性:失效时间必须大于0if(repeatSubmit.seconds()<=0){returntrue;}//请求urlStringuri=request.getRequestURI();//构建更精确的Redis Key:结合用户标识防止不同用户间误判StringuserKey=getUserIdentifier(request);StringredisKey=userKey+":"+uri;//redis中存在返回false,不存在返回trueBooleanifAbsent=stringRedisTemplate.opsForValue().setIfAbsent(redisKey,"1",Objects.nonNull(repeatSubmitByMethod)?repeatSubmitByMethod.seconds():repeatSubmitByCls.seconds(),TimeUnit.SECONDS);//如果存在,表示已经请求过了,直接抛出异常,由全局异常进行处理返回指定信息if(ifAbsent!=null&&!ifAbsent){thrownewRepeatSubmitException();}returntrue;}returnHandlerInterceptor.super.preHandle(request,response,handler);}/** * 获取用户唯一标识 * 优先级:Token > SessionId > IP地址 */privateStringgetUserIdentifier(HttpServletRequestrequest){//尝试从请求头获取TokenStringtoken=request.getHeader("Authorization");if(StringUtils.hasText(token)){returntoken;}//尝试获取SessionIdStringsessionId=request.getRequestedSessionId();if(StringUtils.hasText(sessionId)){returnsessionId;}//使用IP地址作为兜底方案Stringip=getClientIp(request);returnStringUtils.hasText(ip)?ip:"unknown";}/** * 获取客户端真实IP */privateStringgetClientIp(HttpServletRequestrequest){Stringip=request.getHeader("X-Forwarded-For");if(StringUtils.hasText(ip)&&!"unknown".equalsIgnoreCase(ip)){//多次反向代理后会有多个IP值,第一个为真实IPintindex=ip.indexOf(',');if(index!=-1){returnip.substring(0,index);}returnip;}ip=request.getHeader("X-Real-IP");if(StringUtils.hasText(ip)&&!"unknown".equalsIgnoreCase(ip)){returnip;}ip=request.getHeader("Proxy-Client-IP");if(StringUtils.hasText(ip)&&!"unknown".equalsIgnoreCase(ip)){returnip;}ip=request.getHeader("WL-Proxy-Client-IP");if(StringUtils.hasText(ip)&&!"unknown".equalsIgnoreCase(ip)){returnip;}returnrequest.getRemoteAddr();}}

配置拦截器

packagetest;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;publicclassWebConfigimplementsWebMvcConfigurer{@AutowiredprivateRepeatSubmitInterceptorrepeatSubmitInterceptor;@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){finalString[]commonExclude={"/error","/files/**"};registry.addInterceptor(repeatSubmitInterceptor).excludePathPatterns(commonExclude);}}

抛异常代码

packagecom.ruoyi.common.exception;/** * 重复提交异常 * * @author ruoyi */publicclassRepeatSubmitExceptionextendsRuntimeException{privatestaticfinallongserialVersionUID=1L;/** * 错误提示 */privateStringmessage;/** * 空构造方法 */publicRepeatSubmitException(){this("不允许重复提交,请稍后再试");}publicRepeatSubmitException(Stringmessage){super(message);this.message=message;}@OverridepublicStringgetMessage(){returnmessage;}publicRepeatSubmitExceptionsetMessage(Stringmessage){this.message=message;returnthis;}}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/5 9:10:22

Go 1.18+ 开发环境配置全攻略:GOPROXY选哪个?GO111MODULE到底怎么设?

Go 1.18 开发环境配置全攻略&#xff1a;GOPROXY选哪个&#xff1f;GO111MODULE到底怎么设&#xff1f;在Go语言生态中&#xff0c;环境变量的配置一直是开发者绕不开的话题。尤其是随着Go Modules的全面普及和Go 1.18版本带来的新特性&#xff0c;如何正确配置GOPROXY和GO111M…

作者头像 李华
网站建设 2026/6/5 9:08:09

Mythos可控推理协议:大模型门控式释放与推理流控实践

1. 项目概述&#xff1a;一次被刻意“收窄”的能力跃迁如果你最近关注大模型前沿动态&#xff0c;大概率已经看到“Anthropic发布Mythos”这个消息在技术圈小范围炸开。但真正值得细品的&#xff0c;不是它“发布了”&#xff0c;而是它“怎么发布的”——用一份编号为TAI #200…

作者头像 李华