精华内容
下载资源
问答
  • 2017-10-02 18:10:15

    一、为什么会出现重复提交?

    主要是由于网络的延迟问题以及页面刷新的操作。

    二、表单的重复提交会导致的问题?

    主要能够造成很多脏数据。

    三、解决的办法:

    3.1 前端解决办法:通过前端的方法将提交按钮变灰。对于前端的办法这里就不做演示了,因为前端的控制虽然能够防止数据的重复提交但是治标不治本。这里主要介绍第二种方法。

    3.2 后端解决: 思路:主要是利用唯一Token值与提交参数相匹配验证。

    后端解决的代码示例:

    1.前端页面

    <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
    <%
    	String path = request.getContextPath();
    	String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
    			+ path + "/";
    %>
    
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
    <head>
    <base href="<%=basePath%>">
    
    <title>My JSP 'index.jsp' starting page</title>
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="This is my page">
    <!--
    	<link rel="stylesheet" type="text/css" href="styles.css">
    	-->
    </head>
    
    <body>
    	<form action="${pageContext.request.contextPath}/DoFormServlet" method="post">
    		<input type="hidden" name="token" value="${sessionToken}">
    		用户名:<input type="text" name="userName"> <input type="submit"
    			value="提交" id="submit">
    	</form>
    </body>
    </html>
    
    2.发送Token值去前端页面代码:
    package session;
    
    import java.io.IOException;
    import java.util.UUID;
    
    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("/SendTokenToForm")
    public class SendTokenToForm extends HttpServlet {
    	
    	/**
    	 * 
    	 */
    	private static final long serialVersionUID = 5841829906440324978L;
    
    	@Override
    	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    		req.getSession().setAttribute("sessionToken", UUID.randomUUID().toString());
    		req.getRequestDispatcher("/index.jsp").forward(req, resp);
    	}
    
    }
    3.具体解决重复提交核心代码:

    package session;
    
    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("/DoFormServlet")
    public class DoFormServlet extends HttpServlet {
    	
    	/**
    	 * 
    	 */
    	private static final long serialVersionUID = 82128771669092572L;
    
    	@Override
    	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    		if (!isSubmit(req)) {
    			resp.getWriter().write("数据已提交");
    			System.out.println("数据已提交");
    		}
    		//让线程休眠0.9秒,方便测试
    		try {
    			Thread.sleep(900);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		
    		String userName = req.getParameter("userName");
    		System.out.println("正在往数据库插入数据"+userName);
    		resp.getWriter().write("success");
    		
    	}
    	/**
    	 * @Title: isSubmit
    	 * @Description: 判断token值是否相同以及是否有伪token值得传入
    	 * @author 西安工业大学-查文彬
    	 * @time 2017年10月2日 下午5:53:41 
    	 * @param request
    	 * @return
    	 * boolean
    	 */
    	
    	public boolean isSubmit(HttpServletRequest request){
    		String sessionToken = (String) request.getSession().getAttribute("sessionToken");
    		String parameter = request.getParameter("token");
    		if (!(sessionToken.equals(parameter))) {
    			return false;
    		}
    		request.getSession().removeAttribute("sessionToken");
    		return true;
    	}
    
    }
    
    感想:解决数据重复提交虽然技术没有多么高大上,但是由于平时的不怎么注意,很多人都忽略这个细节,解决起来也花不了多少的时间。所以平时对于代码的细节方面应该多多斟酌,追求代码完美。避免不必要的事件发生,即使发生的概率很小。

    更多相关内容
  • springboot如何解决重复提交,接口短时间重复调用 在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,...

    springboot如何解决重复提交,接口短时间重复调用

    在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交….

    重复提交

    字面意思就是提交了很多次,这种情况一般都是前端给你挖的坑….

    前段时间在开发中遇到一个这样的问题;前端小哥哥调用接口的时候存在 循环调用 的问题,正常情况下发送一个请求添加一条数据,结果变成了同一时刻并发的发送了 N 个请求,服务端瞬间懵逼的插入了 N 条一模一样的数据,前端小哥哥也不知道问题在哪里(恩…坑就这样挖好了,反正不填坑,气死你) 这时候咋办呢;后端干呗,反正脏活累活,背锅的事情也没少干了,多一件也不多….

    本章目标

    利用 自定义注解Spring AopGuava Cache 实现表单防重复提交(不适用于分布式哦,后面会讲分布式方式…)

    1.导入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>21.0</version>
        </dependency>
    </dependencies>
    

    2.Lock 注解

    创建一个 LocalLock 注解,简单点就一个 key 可以了,由于暂时未用到 redis 所以 expire 是摆设….

    package com.battcn.annotation;
    
    import java.lang.annotation.*;
    
    /**
     * 锁的注解
     *
     * @author Levin
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface LocalLock {
    
        /**
         * @author fly
         */
        String key() default "";
    
        /**
         * 过期时间 TODO 由于用的 guava 暂时就忽略这属性吧 集成 redis 需要用到
         *
         * @author fly
         */
        int expire() default 5;
    }
    

    3.Lock 拦截器(AOP)

    首先通过 CacheBuilder.newBuilder()构建出缓存对象,设置好过期时间;其目的就是为了防止因程序崩溃锁得不到释放(当然如果单机这种方式程序都炸了,锁早没了;但这不妨碍我们写好点)

    在具体的interceptor()方法上采用的是 Around(环绕增强) ,所有带 LocalLock注解的都将被切面处理;

    如果想更为灵活,key 的生成规则可以定义成接口形式(可以参考:org.springframework.cache.interceptor.KeyGenerator),这里就偷个懒了;

    package com.battcn.interceptor;
    
    import com.battcn.annotation.LocalLock;
    import com.google.common.cache.Cache;
    import com.google.common.cache.CacheBuilder;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.util.StringUtils;
    
    import java.lang.reflect.Method;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 本章先基于 本地缓存来做,后续讲解 redis 方案
     *
     * @author Levin
     * @since 2018/6/12 0012
     */
    @Aspect
    @Configuration
    public class LockMethodInterceptor {
    
        private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
                // 最大缓存 100 个
                .maximumSize(1000)
                // 设置写缓存后 5 秒钟过期
                .expireAfterWrite(5, TimeUnit.SECONDS)
                .build();
    
        @Around("execution(public * *(..)) && @annotation(com.battcn.annotation.LocalLock)")
        public Object interceptor(ProceedingJoinPoint pjp) {
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            Method method = signature.getMethod();
            LocalLock localLock = method.getAnnotation(LocalLock.class);
            String key = getKey(localLock.key(), pjp.getArgs());
            if (!StringUtils.isEmpty(key)) {
                if (CACHES.getIfPresent(key) != null) {
                    throw new RuntimeException("请勿重复请求");
                }
                // 如果是第一次请求,就将 key 当前对象压入缓存中
                CACHES.put(key, key);
            }
            try {
                return pjp.proceed();
            } catch (Throwable throwable) {
                throw new RuntimeException("服务器异常");
            } finally {
                // TODO 为了演示效果,这里就不调用 CACHES.invalidate(key); 代码了
            }
        }
    
        /**
         * key 的生成策略,如果想灵活可以写成接口与实现类的方式(TODO 后续讲解)
         *
         * @param keyExpress 表达式
         * @param args       参数
         * @return 生成的key
         */
        private String getKey(String keyExpress, Object[] args) {
            for (int i = 0; i < args.length; i++) {
                keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString());
            }
            return keyExpress;
        }
    }
    

    4.控制层

    在接口上添加 @LocalLock(key = "book:arg[0]");意味着会将arg[0]替换成第一个参数的值,生成后的新 key 将被缓存起来;

    package com.battcn.controller;
    
    import com.battcn.annotation.LocalLock;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * BookController
     *
     * @author Levin
     * @since 2018/6/06 0031
     */
    @RestController
    @RequestMapping("/books")
    public class BookController {
    
        @LocalLock(key = "book:arg[0]")
        @GetMapping
        public String query(@RequestParam String token) {
            return "success - " + token;
        }
    }
    

    测试

    完成准备事项后,启动 Chapter21Application 自行测试即可,测试手段相信大伙都不陌生了,如 浏览器postmanjunitswagger,此处基于 postman,如果你觉得自带的异常信息不够友好,那么配上一起来学SpringBoot | 第十八篇:轻松搞定全局异常 可以轻松搞定…

    第一次请求

    在这里插入图片描述

    第二次请求(会捕获异常并提示请不要重复请求)

    在这里插入图片描述

    转载自: https://battcn.blog.csdn.net/article/details/80837988

    展开全文
  • 本文实例总结分析了ThinkPHP防止重复提交表单的方法。分享给大家供大家参考,具体如下: 为什么会有表单重复的坑 在开发中,如果一个新增或修改的表单,在后台完成数据库操作后我们设定的不是跳转到其他页面,还是...
  • 1. 为什么会出现表单重复提交问题? 网络延迟的情况下用户多次点击submit按钮导致表单重复提交 用户提交表单后,点击【刷新】按钮导致表单重复提交(点击浏览器的刷新按钮,就是把浏览器上次做的事情再做一次,因为...

    1. 为什么会出现表单重复提交问题?

    1. 网络延迟的情况下用户多次点击submit按钮导致表单重复提交
    2. 用户提交表单后,点击【刷新】按钮导致表单重复提交(点击浏览器的刷新按钮,就是把浏览器上次做的事情再做一次,因为这样也会导致表单重复提交
    3. 用户提交表单后,点击浏览器的【后退】按钮回退到表单页面后进行再次提交

    2.解决方案

    2.1 前端解决方案(治标不治本)

    2.1.1用JavaScript控制Form表单只能提交一次

    主要代码:

    <form action="${pageContext.request.contextPath}/servlet/DoFormServlet" οnsubmit="return dosubmit()" method="post">
        用户名:<input type="text" name="username">
        <input type="submit" value="提交" id="submit">
    </form>
    
    <head>
        <title>Form表单</title>
        <script type="text/javascript">
            var isCommitted = false;//表单是否已经提交标识,默认为false
            function dosubmit(){
                if(isCommitted==false){
                    isCommitted = true;//提交表单后,将表单是否已经提交标识设置为true
                    return true;//返回true让表单正常提交
                } else {
                    return false;//返回false那么表单将不提交
                }
            }
        </script>
    </head>
    
    

    2.1.2提交以后将提交按钮设置为不可用(体验可能不太好)

    主要代码:

    function dosubmit(){
        //获取表单提交按钮
        var btnSubmit = document.getElementById("submit");
        //将表单提交按钮设置为不可用,这样就可以避免用户再次点击提交按钮
        btnSubmit.disabled= "disabled";
        //返回true让表单可以正常提交
        return true;
    }
    
    

    2.1.3提交以后将页面关闭(可能用户体验不好)

    2.1.4提交以后将页面数据刷新 这个时候将本条记录的主键id拿到,后端进行插入的时候看看主键id是否存在决定进行插入或者修改

    2.2 后端解决方案

    2.2.1利用Session防止表单重复提交

    主要步骤:

    1. 在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),并在当前用户的Session域中保存这个Token

    token生成类

    public class TokenProccessor {
     
        /*
         *单例设计模式(保证类的对象在内存中只有一个)
         *1、把类的构造函数私有
         *2、自己创建一个类的对象
         *3、对外提供一个公共的方法,返回类的对象
         */
        private TokenProccessor(){
        }
        
        private static final TokenProccessor instance = new TokenProccessor();
        
        /**
         * 返回类的对象
         * @return
         */
        public static TokenProccessor getInstance(){
            return instance;
        }
        
        /**
         * 生成Token
         * Token:Nv6RRuGEVvmGjB+jimI/gw==
         * @return
         */
        public String makeToken(){
            String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";
            //数据指纹   128位长   16个字节  md5
            try {
                MessageDigest md = MessageDigest.getInstance("md5");
                byte md5[] =  md.digest(token.getBytes());
                //base64编码--任意二进制编码明文字符   adfsdfsdfsf
                BASE64Encoder encoder = new BASE64Encoder();
                return encoder.encode(md5);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    
    
    String token = TokenProccessor.getInstance().makeToken();//创建令牌
    System.out.println("在FormServlet中生成的token:"+token);
    request.getSession().setAttribute("token", token);  //在服务器使用session保存token(令牌)
    request.getRequestDispatcher("/form.jsp").forward(request, response);//跳转到form.jsp页面    
    
    1. 将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端
    <form action="${pageContext.request.contextPath}/servlet/DoFormServlet" method="post">
        <%--使用隐藏域存储生成的token--%>
        <%--
            <input type="hidden" name="token" value="<%=session.getAttribute("token") %>">
        --%>
        <%--使用EL表达式取出存储在session中的token--%>
        <input type="hidden" name="token" value="${token}"/> 
        用户名:<input type="text" name="username"> 
        <input type="submit" value="提交">
    </form>
    
    1. 在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
    /**
     * 判断客户端提交上来的令牌和服务器端生成的令牌是否一致
     * @param request
     * @return 
     *         true 用户重复提交了表单 
     *         false 用户没有重复提交表单
     */
    private boolean isRepeatSubmit(HttpServletRequest request) {
        String client_token = request.getParameter("token");
        //1、如果用户提交的表单数据中没有token,则用户是重复提交了表单
        if(client_token==null){
            return true;
        }
        //取出存储在Session中的token
        String server_token = (String) request.getSession().getAttribute("token");
        //2、如果当前用户的Session中不存在Token(令牌),则用户是重复提交了表单
        if(server_token==null){
            return true;
        }
        //3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则用户是重复提交了表单
        if(!client_token.equals(server_token)){
            return true;
        }
        
        return false;
    }
    

    doGet方法中:

    
    boolean b = isRepeatSubmit(request);//判断用户是否是重复提交
    if(b==true){
        System.out.println("请不要重复提交");
        return;
    }
    request.getSession().removeAttribute("token");//移除session中的token
    System.out.println("处理用户提交请求!!");
    

    对于【场景二】和【场景三】导致表单重复提交的问题,既然客户端无法解决,那么就在服务器端解决,在服务器端解决就需要用到session了。使用token的方法能解决【场景二】和【场景三】的情况。

    2.3 前端加后端的解决方案

    思路: 前端提交以后,页面弄一个加载动画,后端处理完成以后将本条数据的主键id返回前端,前端第二次请求的时候把主键id附上值即可。

    总结:

    常见的方法有四种:

    • 方法一:禁用掉“提交(注册)”按钮,当用户第一次点击提交注册按钮时,事务已经提交了,但是网络存在不稳定性,可能没有及时作出响应,这时用户可能会认为没有点击提交按钮,进而会再次点击提交按钮,
      这是不符合我们的业务逻辑的,因此,我们可以在用户第一次点击提交按钮之后,立刻将提交按钮设置为不可用状态(例如:变灰),用户就不会再次提交了。

    • 方法二:采用页面重定向,在用户点击提交按钮之后,转向一个新的页面,提示用户提交成功。

    • 方法三:采用标志,在一个会话中,当用户请求一个表单时,服务器端在发送页面的时候可以生成一串密文(也可以是随机数),跟随页面同时发给客户端,当客户端填写好表单后,点击提交按钮,再次将这个密文发回到服务器端,并和服务器端的密文进行比对,如果相同,则开始处理业务,如果不相同,则不处理此次业务(像互联网金融行业大多采用这种方式)。

    • 方法四:利用服务器端的数据库,可以在数据库的表里添加相关的约束来防止重复提交,但是这样会增加数据库的负载。

    上面的四种方法各有利弊,我们可以根据项目的实际情况来选择,也可以将这些方法组合起来使用。

    参考链接;https://www.jianshu.com/p/5fd07359ad22
    https://www.cnblogs.com/guozhenqiang/p/5439455.html

    展开全文
  • 前端在向后端进行数据提交的时候,通常会需要在第一次提交返回前,阻止用户在快速点击发送二次请求,即防止重复提交,最简单的方法是使用标志参数或者 class 元素控制,但缺点是,每个控制重复提交的地方都需要加上...
  • 下面小编就为大家带来一篇详谈表单重复提交的三种情况及解决方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  •  首先我们在讨论如何解决表单重复提交问题之前先来解决三个问题:1.什么叫表单重复提交?2.什么情况下会出现表单重复提交?3.什么情况需要避免表单重复提交?  什么叫表单提交问题,说白了,是同一份信息,重复的...
  • 重复提交解决方案

    千次阅读 2021-03-01 17:11:53
    重复提交解决方案

    重复提交解决方案

    重复提交是一个常见的问题,涉及到接口的幂等性。

    幂等 f(f(x)) = f(x)

    编程中常见的幂等和非幂等(What)

    幂等

    select、delete、update某个字段

    非幂等

    update更新累加操作 、 insert非幂等操作

    产生原因(Why)

    由于重复提交或网络重发

    1. 按钮提交点击两次
    2. 点击了刷新
    3. 使用浏览器后退按钮重复之前的操作,导致重复提交表单
    4. 浏览器重复的http请求
    5. nginx重发
    6. 分布式RPC的重试,如Mq幂等性问题

    解决方案How

    这里主要分为前端和后端

    前端

    1. 对于快速点击,后端直接插入多条数据

    前端判断后端的返回,如果上次提交结果没有返回响应,拒绝短时间内再次提交

    2. 对于浏览器刷新、前进、后退重复提交

    在提交后执行页面重定向,这就是所谓的Post-Redirect-Get (PRG)模式。

    简言之,当用户提交了表单后,你去执行一个客户端的重定向,转到提交成功信息页面。

    后端

    1. 对于重复提交

    如果第二次新增,判断是否已经存在数据,如果有就进行更新

    2. 用户快速重复点击—加锁

    上面这个问题,但是如果用户快速点击,第一条记录还没有写库,那么第二条数据也会插入,还是产生重复提交

    这时候要进行加锁,使得同一时间只能一个请求进行

    2.1 Redis分布式锁

    setnx key value
    

    使用Redis的 setNx(), 第一次设置会返回 true,第二次设置如果存在会返回false,标识覆盖失败。

    重复提交,锁定一个唯一的参数作为key

    Boolean flag = opsForValue().setIfAbsent(key,value);
    if(falg) {
      //进行更新
    }else {
      return ;
    }
    

    具体可以使用 jedis或者 redisson来操作redis

    注意:

    1. 这里必须设置过期时间:否则如果运行中的线程如果挂了,那么提交就一直失败

    2. 如果遇到超长时间的流程,运行时间大于过期时间,即第二个线程已经获取到锁了,这时候判断锁的名字和之前的是否一致,不一致就放弃释放。


    2.2 ConcurrentHashMap方案

    原理:使用了 ConcurrentHashMap 并发容器 putIfAbsent 方法,和 ScheduledThreadPoolExecutor 定时任务

    Content-MD5:是指 Body 的 MD5 值,只有当 Body 非Form表单时才计算MD5,计算方式直接将参数和参数名称统一加密MD5

    代码学习下,并发下的线程池和 ConcurrentHashMap使用

    这个只适合单机部署的应用,生产不实用。 究其原理应该是 ConcurrentHashMap私有,不像redis是共有的

    定义注解
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Resubmit {
    
        /**
         * 延时时间 在延时多久后可以再次提交
         *
         * @return Time unit is one second
         */
        int delaySeconds() default 20;
    }
    
    实例化锁

    这里注意线程池的使用,5个线程, 抛弃策略

    /**
     * @author lijing
     * 重复提交锁
     */
    @Slf4j
    public final class ResubmitLock {
    
    
        private static final ConcurrentHashMap LOCK_CACHE = new ConcurrentHashMap<>(200);
        private static final ScheduledThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor(5, new ThreadPoolExecutor.DiscardPolicy());
    
    
       // private static final Cache CACHES = CacheBuilder.newBuilder()
                // 最大缓存 100 个
       //          .maximumSize(1000)
                // 设置写缓存后 5 秒钟过期
       //         .expireAfterWrite(5, TimeUnit.SECONDS)
       //         .build();
    
    
        private ResubmitLock() {
        }
    
        /**
         * 静态内部类 单例模式
         *
         * @return
         */
        private static class SingletonInstance {
            private static final ResubmitLock INSTANCE = new ResubmitLock();
        }
    
        public static ResubmitLock getInstance() {
            return SingletonInstance.INSTANCE;
        }
    
    
        public static String handleKey(String param) {
            return DigestUtils.md5Hex(param == null ? "" : param);
        }
    
        /**
         * 加锁 putIfAbsent 是原子操作保证线程安全
         *
         * @param key   对应的key
         * @param value
         * @return
         */
        public boolean lock(final String key, Object value) {
            return Objects.isNull(LOCK_CACHE.putIfAbsent(key, value));
        }
    
        /**
         * 延时释放锁 用以控制短时间内的重复提交
         *
         * @param lock         是否需要解锁
         * @param key          对应的key
         * @param delaySeconds 延时时间
         */
        public void unLock(final boolean lock, final String key, final int delaySeconds) {
            if (lock) {
                EXECUTOR.schedule(() -> {
                    LOCK_CACHE.remove(key);
                }, delaySeconds, TimeUnit.SECONDS);
            }
        }
    }
    
    配置切面

    设置切面,拿到注解,先对传参计算Md5值,然后用锁锁住

    @Log4j
    @Aspect
    @Component
    public class ResubmitDataAspect {
    
        private final static String DATA = "data";
        private final static Object PRESENT = new Object();
    
        @Around("@annotation(com.cn.xxx.common.annotation.Resubmit)")
        public Object handleResubmit(ProceedingJoinPoint joinPoint) throws Throwable {
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
            //获取注解信息
            Resubmit annotation = method.getAnnotation(Resubmit.class);
            int delaySeconds = annotation.delaySeconds();
            Object[] pointArgs = joinPoint.getArgs();
            String key = "";
            //获取第一个参数
            Object firstParam = pointArgs[0];
            if (firstParam instanceof RequestDTO) {
                //解析参数
                JSONObject requestDTO = JSONObject.parseObject(firstParam.toString());
                JSONObject data = JSONObject.parseObject(requestDTO.getString(DATA));
                if (data != null) {
                    StringBuffer sb = new StringBuffer();
                    data.forEach((k, v) -> {
                        sb.append(v);
                    });
                    //生成加密参数 使用了content_MD5的加密方式
                    key = ResubmitLock.handleKey(sb.toString());
                }
            }
            //执行锁
            boolean lock = false;
            try {
                //设置解锁key
                lock = ResubmitLock.getInstance().lock(key, PRESENT);
                if (lock) {  //如果设置成功
                    //放行
                    return joinPoint.proceed();
                } else {
                    //响应重复提交异常
                    return new ResponseDTO<>(ResponseCode.REPEAT_SUBMIT_OPERATION_EXCEPTION);
                }
            } finally {
                //设置解锁key和解锁时间
                ResubmitLock.getInstance().unLock(lock, key, delaySeconds);
            }
        }
    }
    
    使用案例
    
    @ApiOperation(value = "保存我的帖子接口", notes = "保存我的帖子接口")
    @PostMapping("/posts/save")
    @Resubmit(delaySeconds = 10)
    public ResponseDTO saveBbsPosts(@RequestBody @Validated RequestDTO requestDto) {
        return bbsPostsBizService.saveBbsPosts(requestDto);
    }
    

    以上就是本地锁的方式进行的幂等提交 使用了Content-MD5 进行加密 只要参数不变,参数加密 密值不变,key存在就阻止提交

    当然也可以使用 一些其他签名校验 在某一次提交时先 生成固定签名 提交到后端 根据后端解析统一的签名作为 每次提交的验证token 去缓存中处理即可.

    小节

    当然Redis也可以做成切面的形式,但是做成Api的形式效率更高一些

    展开全文
  • 主要介绍了vue 按钮多次点击重复提交数据的问题,本文通过实例结合的形式给大家介绍的非常详细,需要的朋友可以参考下
  • 数据重复提交解决方案

    千次阅读 2022-03-29 16:11:30
    java防止数据重复提交
  • 主要介绍了PHP+Session防止表单重复提交解决方法,需要的朋友可以参考下
  • 主要介绍了解决php表单重复提交实现方法,需要的朋友可以参考下
  • 主要介绍了浅谈利用Session防止表单重复提交,简单介绍表单重复提交的情况,分析,以及解决方法代码示例,具有一定借鉴价值,需要的朋友可以了解下。
  • Struts解决重复提交、上传组件+视频
  • 简化的模拟代码如下(基于 Spring Boot): import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;... * 被重复请求的方法 ...
  • 表单重复提交是在多用户Web应用中最常见、带来很多麻烦的一个问题。有很多的应用场景都会遇到重复提交问题,比如: 点击提交按钮两次。 点击刷新按钮。 使用浏览器后退按钮重复之前的操作,导致重复提交表单。 使用...
  • Java怎样防止重复提交

    2020-12-22 17:08:54
    防止重复提交java解决  B/S结构的软件开发中,特别是在越大型的分布式应用中体现的越明显,后端的处理往往会因为出现较多的时间消耗而引起延迟,这种延迟有可能过长而终使用户认为是自己的操作错误,导致他们重新...
  • /** * * @authors Benjamin * @date 2013-11-13 10:16:59 */ 一、常见的重复提交问题 a>点击提交按钮两次。 b>点击刷新按钮。 c>使用浏览器后退按钮重复之前的操作,导致重复提交表单。 d>使用浏览器历史记录重复...
  • 解决重复提交、上传组件.rar
  • 先看一下有哪些情况下回导致表单重复提交呢,知道哪些情况下可能会出现表单重复提交就可以从根源处理表单重复提交的情况了。 下面的情况就会导致表单重复提交:  点击提交按钮两次。  点击刷新按钮。  使用浏览器...
  • 主要介绍了IE 下Enter提交表单存在重复提交问题的解决方法,需要的朋友可以参考下
  • Struts解决重复提交步骤也可以说是struts的令牌机制很有用的啊
  • 自定义封装注解类,(生成token存放到redis中)通过注解的方式解决API接口幂等设计防止表单重复提交
  • 按照这个含义,最终的含义就是 对数据库的影响只能是一次性的,不能重复处理。如何保证其幂等性,通常有以下手段: 1、数据库建立唯一性索引,可以保证最终插入数据库的只有一条数据。 2、token机制,每次接口请求前...
  • Java中如何避免重复提交请求

    千次阅读 2021-03-06 02:23:33
    查看后台日志后发现一个同样的请求提交了多次,后果就是轻则导致产生多条...二、产生原因导致重复请求的原因很多,大体为以下几种:多次点击提交按钮反复刷新页面点击浏览器后退按钮,导致重复提交表单浏览器重复的H...
  • NULL 博文链接:https://feng5588feng.iteye.com/blog/1494002
  • 我的解决办法如下(只针对客户端): 用户点击提交按钮后给按钮添加disabled属性 代码如下:$(“input:submit”).each(function() { var srcclick = $(this).attr(“onclick”); if(typeof(srcclick)==”function”){...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 283,766
精华内容 113,506
关键字:

解决重复提交