精华内容
下载资源
问答
  • 防重复提交

    2017-07-13 21:52:05
    防重复提交 造成重复提交的原因 由于服务器缓慢或网络延迟的原因,重复点击提交按钮. 已经提交成功,刷新成功页面(forward). 已经提交成功,通过回退,再次点击提交按钮. 完成防重复提交的方式 方案一:禁掉提交...

    防重复提交

    造成重复提交的原因

    由于服务器缓慢或网络延迟的原因,重复点击提交按钮.

    已经提交成功,刷新成功页面(forward).

    已经提交成功,通过回退,再次点击提交按钮.

    完成防重复提交的方式

    方案一:禁掉提交按钮

    方案二:使用重定项(不使用请求转发)

    方案三:使用AJAX无刷新提交

    方案四:使用令牌


    使用令牌机制 图表分析:



    package cn.itsource._03_resubmit;
    
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @WebServlet("/resubmit")
    public class ReSubmitServlet extends HttpServlet {
    
    		private static final long serialVersionUID = 1L;
    		
    		@Override
    		protected void service(HttpServletRequest req, HttpServletResponse resp)
    				throws ServletException, IOException {
    				//1.拿到请求中传过来的令牌
    				String token = req.getParameter("token");
    				//2.拿到session中的令牌
    				String sessionToken = (String)req.getSession().getAttribute("TOKEN_IN_SESSION");
    				//3.删除session中的令牌
    				req.getSession().removeAttribute("TOKEN_IN_SESSION");
    				//4.如果令牌不相等,就不执行后面的代码
    				if(token==null ||!token.equals(sessionToken)){
    					//对重复提交进行处理,一般选择会跳转到其它页面。不写了
    					System.out.println("别想重复提交");
    					return;
    				}
    			
    				String salary = req.getParameter("salary");
    				System.out.println("发工资给我:"+salary);
    		}
    }
    

    注意:令牌比较完后,必须删除session中的令牌。


    salary.jsp文件:

    <%@page import="java.util.UUID"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
        
    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>登录</title>
    
    </head>
    <body>
    
    	<%
    		//生成令牌
    		String token = UUID.randomUUID().toString();
    		//将令牌放到Session中
    		session.setAttribute("TOKEN_IN_SESSION", token);
    	%>
    
    	
    	<form action="/resubmit" method="post">
    		<input type="hidden" name="token" value="${TOKEN_IN_SESSION}" />
    		工资:<input type="number" name="salary" />
    		<input type="submit" value="发工资" />
    	</form>
    </body>
    </html>


    展开全文
  • 很多业务场景都需要防重复提交,比如提交订单,抢券,组团等场景。在这里,主要陈述下一般的防重复提交方式。具体归类,会分为新增场景,更新场景。重复提交可以分为几种类型,比如:短时间连续重复提交;不定时间...

    很多业务场景都需要防重复提交,比如提交订单,抢券,组团等场景。

    在这里,主要陈述下一般的防重复提交方式。具体归类,会分为新增场景,更新场景。重复提交可以分为几种类型,比如:

    短时间连续重复提交;

    不定时间重复提交;

    新增场景

    新增场景无论是短时间连续重复提交还是不定时间重复提交,都是相似的解决方案。

    数据库加唯一索引

    作为常规手段,一般都会在数据库表中根据业务场景设计唯一索引。

    执行步骤:

    插入前检查;

    执行insert ignore;

    插入后判断更新行数等方式来避免重复提交的问题;

    这种方式下,可以严格保证重复提交不会出问题。但是,性能要相对差一点。

    消息队列

    防重复提交的关键在于,并行变串行,消息队列也可以良好的承接这个需求。所有消息全部投入消息队列,然后逐一或者批量并行消费,在消费时因为可以进行校验与筛除,可以避免重复问题。此时不需要用唯一索引,因为请求直接投递到消息队列的缘故,接口的QPS可以非常高。对于仅需要保证投递成功,不关心处理结果的场景这个是非常适合的解决方案。

    分布式锁

    一般借助redis的带超时时间的NX锁。这种方式可以支持多个重复提交在超时时间内仅有一个提交会被处理。

    执行步骤如下:

    在获取锁之前检查是否已插入;

    然后获取NX锁;

    然后再检查一次是否插入;

    然后再执行数据库写入即可;

    检查2次,是为了规避ABA问题,防止提交事务成功,释放NX锁失败的情况导致重复的问题。这种方式超时时间要根据写数据库耗时来评估,留出一定倍数的超时时间空间,给db进行处理,然后查询动作需要从主库读,避免主从延迟带来读不到最新插入导致问题。这种方式有一定的隐患,比如db处理因为网络延迟,或者一个大事务在行锁等待,导致提交时间超过分布式锁的超时时间。这种情况可能造成重复提交的可能。建议配合spring事务的超时时间,合理设置事务超时,确保NX锁超时在事务超时后即可。这种方式可以及时返还请求方处理结果,对于需要有响应的场景比较适用。

    前端限制

    这种方式要看具体情况,有些业务场景可以仅允许用户提交一次;有些没有限制,允许用户提交多次。仅允许提交一次的情况下,前端按钮直接设置为不可点击即可。

    更新场景

    数据库锁记录

    这种场景有悲观锁,乐观锁2种方式。

    悲观锁

    可以在记录上加for update或者lock in share mode。lock in share mode允许共享读,加S锁,禁止写操作。for update不允许共享读,加X锁。在RC隔离级别下,这两者没有区别,因为RC级别读不加锁。不过为了性能,尽量不要使用这种级别的锁。

    乐观锁

    可以在表中引入版本列,通过version比较判断是否允许更新。

    无论乐观锁还是悲观锁,都只能保证短时间并发操作的唯一性。不定时间的请求,需要在业务逻辑上进行逻辑保证。

    展开全文
  • 客户端防表单重复提交和服务器端防重复提交

    客户端防表单重复提交和服务器端防重复提交


    客户端防表单重复提交

     为了防止用户在客户端重复提交表单,要分析从客户端和服务端对重复提交的表单就行处理,首先是客户端处理重复提交表单,使用JavaScript方法,第一种是只允许表单提交一次,后来的不能再提交,第二种是提交一次后按钮变成不可用,下面是代码的实现

    <html>  
    <head>  
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
    <title>Insert title here</title>  
    <script type="text/javascript">  
     
     var isCommit = false;  
     function doSubmit1(){  
        if(! isCommit){  
        isCommit = true;  
            return true;  
        }   else{  
        return false;  
        }  
     } 
     function doSubmit2(){//使提交按钮再提交完成后不可用  
        var input = document.getElementById("submit");  
        input.disabled='disabled';  
        return true;  
     }  
    </script>  
      
      
    </head>  
    <body>  
    <form action="/GetData.aspx" method="post" οnsubmit="return doSubmit()">  
    用户名:<input type="text" name="username"><br>  
    <input id="submit" type="submit" value="提交" >  
    </form>  
    </body>  
    </html> 

    客户端防表单重复提交还有一种方式就是由服务端生成一个唯一标识的token,服务端保存,同时发给客户端,客户端提交数据的时候包含该token,客户端提交一次后清除该token,服务端需检测该token是否处理过,一般不推荐这种做法,这样客户端同服务端多了一次交互。

    服务器端防重复提交

    但是仅仅在客户端进行处理是远远不够的,这叫只能防的了君子防不了小人,因为可以根据服务器的提交地址自己写一个表单或者去掉JavaScript代码进行自己的提交,这样仍旧无法阻止客户端的表单的重复提交。


    第一种方式,先判断存在与否

    有种有问题的做法是这样的,比如是插入数据到db的,在插入前查询数据库根据业务特征判断该记录是否存在,不存在则插入,貌似没问题,其实在客户端在很多情况下会有重复提交的情况,当第一个请求到了,但数据还没插入到db,第二个请求也来了,查询数据库时同样还是没有,
    但当插入数据库时,前一个请求的插入可能已经执行,这样二个同样的数据都插入了db,除非在db上加了唯一索引,这样第二个请求插入db时会报异常,处理掉这个异常即可

    这种先查询db,再操作的也可以结合lock来做,检查及插入包含在lock块中,这种情况lock块中的逻辑不要太复杂,耗时太多会让其它的请求等待,并发性能就低了。

    第二种方式,利用缓存来做判断

    根据上面的问题有一种做法是在接收到请求时在缓存中保存一个值(要能判别是否重复数据),在做数据处理前判断该缓存是否存在,不存在则处理,存在则说明是重复数据。这种方式在绝大时候是行得通的,因为缓存操作够快,但还是有机率出现重复操作的问题,除非还得加一个全局锁

    在判断前先获取锁,拿到锁的进入下一步,处理完了释放锁


    --- end ---


    展开全文
  • springboot 实现防重复提交和防重复点击背景同一条数据被用户点击了多次, 导致数据冗余, 需要防止弱网络等环境下的重复点击目标通过在指定的接口处添加注解, 实现根据指定的接口参数来防重复点击说明这里的重复点击...

    springboot 实现防重复提交和防重复点击

    背景

    同一条数据被用户点击了多次, 导致数据冗余, 需要防止弱网络等环境下的重复点击

    目标

    通过在指定的接口处添加注解, 实现根据指定的接口参数来防重复点击

    说明

    这里的重复点击是指在指定的时间段内多次点击按钮

    技术方案

    springboot + Redis 锁 + 注解

    使用 feign client 进行请求测试

    最终的使用实例

    1, 根据接口收到 PathVariable 参数判断唯一/**

    * 根据请求参数里的 PathVariable 里获取的变量进行接口级别防重复点击

    *

    * @param testId 测试 id

    * @param requestVo 请求参数

    * @return

    * @author daleyzou

    */

    @PostMapping("/test/{testId}")

    @NoRepeatSubmit(location="thisIsTestLocation",seconds=6)

    publicRsVothisIsTestLocation(@PathVariableIntegertestId,@RequestBodyRequestVorequestVo)throwsThrowable{

    // 睡眠 5 秒, 模拟业务逻辑

    Thread.sleep(5);

    returnRsVo.success("test is return success");

    }

    2, 根据接口收到的 RequestBody 中指定变量名的值判断唯一/**

    * 根据请求参数里的 RequestBody 里获取指定名称的变量 param5 的值进行接口级别防重复点击

    *

    * @param testId 测试 id

    * @param requestVo 请求参数

    * @return

    * @author daleyzou

    */

    @PostMapping("/test/{testId}")

    @NoRepeatSubmit(location="thisIsTestBody",seconds=6,argIndex=1,name="param5")

    publicRsVothisIsTestBody(@PathVariableIntegertestId,@RequestBodyRequestVorequestVo)throwsThrowable{

    // 睡眠 5 秒, 模拟业务逻辑

    Thread.sleep(5);

    returnRsVo.success("test is return success");

    }

    ps: jedis 2.9 和 springboot 有各种兼容问题, 无奈只有降低 springboot 的版本了

    运行结果

    收到响应:{"succeeded":true,"code":500,"msg":"操作过于频繁, 请稍后重试","data":null}

    收到响应:{"succeeded":true,"code":500,"msg":"操作过于频繁, 请稍后重试","data":null}

    收到响应:{"succeeded":true,"code":500,"msg":"操作过于频繁, 请稍后重试","data":null}

    收到响应:{"succeeded":true,"code":200,"msg":"success","data":"test is return success"}

    测试用例packagecom.dalelyzou.preventrepeatsubmit.controller;

    importcom.dalelyzou.preventrepeatsubmit.PreventrepeatsubmitApplicationTests;

    importcom.dalelyzou.preventrepeatsubmit.service.AsyncFeginService;

    importcom.dalelyzou.preventrepeatsubmit.vo.RequestVo;

    importorg.junit.jupiter.API.Test;

    importorg.springframework.beans.factory.annotation.Autowired;

    importjava.io.IOException;

    importjava.util.concurrent.ExecutorService;

    importjava.util.concurrent.Executors;

    /**

    * TestControllerTest

    * @description 防重复点击测试类

    * @author daleyzou

    * @date 2020 年 09 月 28 日 17:13

    * @version 1.3.1

    */

    classTestControllerTestextendsPreventrepeatsubmitApplicationTests{

    @Autowired

    AsyncFeginServiceasyncFeginService;

    @Test

    publicvoidthisIsTestLocation()throwsIOException{

    RequestVorequestVo=newRequestVo();

    requestVo.setParam5("random");

    ExecutorServiceexecutorService=Executors.newFixedThreadPool(4);

    for(inti=0;i<=3;i++){

    executorService.execute(()->{

    Stringkl=asyncFeginService.thisIsTestLocation(requestVo);

    System.err.println("收到响应:"+kl);

    });

    }

    System.in.read();

    }

    @Test

    publicvoidthisIsTestBody()throwsIOException{

    RequestVorequestVo=newRequestVo();

    requestVo.setParam5("special");

    ExecutorServiceexecutorService=Executors.newFixedThreadPool(4);

    for(inti=0;i<=3;i++){

    executorService.execute(()->{

    Stringkl=asyncFeginService.thisIsTestBody(requestVo);

    System.err.println("收到响应:"+kl);

    });

    }

    System.in.read();

    }

    }

    定义一个注解packagecom.dalelyzou.preventrepeatsubmit.aspect;

    importjava.lang.annotation.ElementType;

    importjava.lang.annotation.Retention;

    importjava.lang.annotation.RetentionPolicy;

    importjava.lang.annotation.Target;

    /**

    * NoRepeatSubmit

    * @description 重复点击的切面

    * @author daleyzou

    * @date 2020 年 09 月 23 日 14:35

    * @version 1.4.8

    */

    @Target(ElementType.METHOD)

    @Retention(RetentionPolicy.RUNTIME)

    public@interfaceNoRepeatSubmit{

    /**

    * 锁过期的时间

    * */

    intseconds()default5;

    /**

    * 锁的位置

    * */

    Stringlocation()default"NoRepeatSubmit";

    /**

    * 要扫描的参数位置

    * */

    intargIndex()default0;

    /**

    * 参数名称

    * */

    Stringname()default"";

    }

    根据指定的注解定义一个切面, 根据参数中的指定值来判断请求是否重复packagecom.dalelyzou.preventrepeatsubmit.aspect;

    importcom.dalelyzou.preventrepeatsubmit.constant.RedisKey;

    importcom.dalelyzou.preventrepeatsubmit.service.LockService;

    importcom.dalelyzou.preventrepeatsubmit.vo.RsVo;

    importcom.google.common.collect.Maps;

    importcom.google.gson.Gson;

    importorg.aspectj.lang.ProceedingJoinPoint;

    importorg.aspectj.lang.annotation.Around;

    importorg.aspectj.lang.annotation.Aspect;

    importorg.aspectj.lang.annotation.Pointcut;

    importorg.slf4j.Logger;

    importorg.slf4j.LoggerFactory;

    importorg.springframework.beans.factory.annotation.Autowired;

    importorg.springframework.stereotype.Component;

    importorg.springframework.util.StringUtils;

    importjava.lang.reflect.Field;

    importjava.util.Map;

    @Aspect

    @Component

    publicclassNoRepeatSubmitAspect{

    privatestaticfinalLoggerlogger=LoggerFactory.getLogger(NoRepeatSubmitAspect.class);

    privatestaticGsongson=newGson();

    privatestaticfinalStringSUFFIX="SUFFIX";

    @Autowired

    LockServicelockService;

    /**

    * 横切点

    */

    @Pointcut("@annotation(noRepeatSubmit)")

    publicvoidrepeatPoint(NoRepeatSubmitnoRepeatSubmit){

    }

    /**

    * 接收请求, 并记录数据

    */

    @Around(value="repeatPoint(noRepeatSubmit)")

    publicObjectdoBefore(ProceedingJoinPointjoinPoint,NoRepeatSubmitnoRepeatSubmit){

    Stringkey=RedisKey.NO_REPEAT_LOCK_PREFIX+noRepeatSubmit.location();

    Object[]args=joinPoint.getArgs();

    Stringname=noRepeatSubmit.name();

    intargIndex=noRepeatSubmit.argIndex();

    Stringsuffix;

    if(StringUtils.isEmpty(name)){

    suffix=String.valueOf(args[argIndex]);

    }else{

    MapkeyAndValue=getKeyAndValue(args[argIndex]);

    ObjectvalueObj=keyAndValue.get(name);

    if(valueObj==null){

    suffix=SUFFIX;

    }else{

    suffix=String.valueOf(valueObj);

    }

    }

    key=key+":"+suffix;

    logger.info("==================================================");

    for(Objectarg:args){

    logger.info(gson.toJson(arg));

    }

    logger.info("==================================================");

    intseconds=noRepeatSubmit.seconds();

    logger.info("lock key :"+key);

    if(!lockService.isLock(key,seconds)){

    returnRsVo.fail("操作过于频繁, 请稍后重试");

    }

    try{

    Objectproceed=joinPoint.proceed();

    returnproceed;

    }catch(Throwablethrowable){

    logger.error("运行业务代码出错",throwable);

    thrownewRuntimeException(throwable.getMessage());

    }finally{

    lockService.unLock(key);

    }

    }

    publicstaticMapgetKeyAndValue(Objectobj){

    Mapmap=Maps.newHashMap();

    // 得到类对象

    ClassuserCla=(Class)obj.getClass();

    /* 得到类中的所有属性集合 */

    Field[]fs=userCla.getDeclaredFields();

    for(inti=0;i

    Fieldf=fs[i];

    // 设置些属性是可以访问的

    f.setAccessible(true);

    Objectval=newObject();

    try{

    val=f.get(obj);

    // 得到此属性的值

    // 设置键值

    map.put(f.getName(),val);

    }catch(IllegalArgumentExceptione){

    logger.error("getKeyAndValue IllegalArgumentException",e);

    }catch(IllegalAccessExceptione){

    logger.error("getKeyAndValue IllegalAccessException",e);

    }

    }

    logger.info("扫描结果:"+gson.toJson(map));

    returnmap;

    }

    }

    项目完整代码

    https://github.com/daleyzou/PreventRepeatSubmit

    来源: http://www.bubuko.com/infodetail-3677961.html

    展开全文
  • 背景业务系统中的防重复提交都是由前端控制,后端在某些地方做了相应的业务逻辑相关的判断,但当某些情况下,前后端的判断都会失效,所以这里引入后端的接口防重复提交校验。方案由于需要限制的是部分接口,因此使用...
  • java防重复提交

    2021-03-17 13:41:51
    防重复提交 简介: 客户端访问时,拦截访问数据,进行验证是否是配置的时间内(如下例子:ttl = 10),有相同的参数访问,如果有就相当于数据重复访问。不可以提交。 实例<一>: 后台代码 //防重复提交,表示...
  • 传统方式(不推荐)首先我们介绍下之前传统的防重复提交方式:1:前端处理:思路如下:function dosubmit(){//第一步,我们需要获取表单的提交按钮。var btnSubmit = document.getElementById("submit");//第二步,...
  • AOP 防重复提交

    2020-05-24 10:15:46
    AOP 防重复提交自定义注解定义AOP类Controller添加注解 重复提交定义:同一用户,一定时间内,提交同一参数请求,则认为是重复提交。 自定义注解 import java.lang.annotation.Retention; import java.lang....
  • springMVC自定义防重复提交,通过标签的方式实现
  • 有时候我们的程序执行比较慢,而且我们页面也不怎么友好,没什么提示信息。操作人员以为没有点击提交按钮,就会再一次点击提交。这会导致很多问题出现。下面介绍三种防重复提交的方法
  • axios防重复提交

    2019-09-20 16:17:11
    文章目录场景axios防重复提交思路具体实现补充 场景 用户在进行新增操作(比如新增菜单)。如果快速点击多次新增按钮,可能照成的情况就是新增了多条记录。一般可以通过在点击的时候,先禁用新增按钮,然后等本次...
  • ladda 按钮防重复提交样式 点击按钮后按钮刷新加载 置为不可用状态,当后台处理完毕,结束加载样式 变为可用 个人感觉简单实用
  • Java接口防重复提交

    2021-02-02 18:02:46
    背景 业务系统中的防重复提交都是由前端控制,后端在某些地方做了相应的业务逻辑相关的判断,但当某些情况下,前后端的判断都会失效,所以这里引入后端的接口防重复提交校验。 方案选择 ...
  • 关于防重复提交由于本人从事电商开发工作,项目中面对C端用户或多或少都会接触到提交保存或者修改的请求,例如创建订单,物流包裹签收,团员通知自提消息发送,这些接口因为涉及到数据库的保存或者修改,如果不做...
  • Spring Boot防重复提交

    千次阅读 2018-11-05 07:00:17
    本文介绍一种非分布式服务后台防重复提交的一种实现方式,虽然在实际工作中,单点部署的服务已经很少了,但是我还是决定单独介绍一下,后面的文章再去介绍分布式服务防重复提交的方法,因为无论是单...
  • 如果你不将这三篇博客联系起来看,就不能很透彻的防重复提交这个知识点,也不能学完整这个知识体系。为什么说这三篇文章要关联看,我在第三篇博客 springmvc下的基于token的防重复提交 里会阐述原因.下面要讲的三篇...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,196
精华内容 478
关键字:

防重复提交