防重复提交实现方案

在WEB系统操作中,往往会出现用户连续重复点击一个按钮导致重复提交,后台程序的同一个接口代码往往上一个请求还没执行完,下一个请求就到达了,而这两个请求又是请求和操作的同一条数据,就会出现业务上的逻辑错误,往往结果不可预料;

要解决重复提交带来的问题的解决方案有多种,不如网上有很多介绍怎么通过前端页面控制来解决重复提交,当然还有其他方式,这里我采用了通过后台程序代码利用redis做分布式锁的方式来防止重复提交,其思路就是在进入一个后端接口执行前先获取一个分布式锁,如果获取成功则上锁,然后执行业务代码,执行完成后再释放分布式锁;如果获取锁失败则可以认为是重复提交的请求,可以将此请求丢弃掉,其流程图如下:

这里有几个关键点:

1)分布式锁:实现分布式锁的方式也有很多种,这里采用redis来实现;

2)注解方式实现:如果在每个接口的前后都加上一堆防止重复提交的代码无疑是非常糟糕的,代码冗余繁琐不说,而且非常不利于代码的扩展和维护,所以如果能通过一个自定义注解实现防重复提交的控制,只在需要控制重复提交的接口上加上这个注解,这样的代码无疑是非常清爽和好维护的;

另外在实现注解时一般可以使用AOP技术和拦截器技术来实现,但是我个人更喜欢用拦截器来实现,这里我就以拦截器的方式来实现;下面给出关键代码:

1、注解的定义:

1 @Target(ElementType.METHOD)
2 @Retention(RetentionPolicy.RUNTIME)
3 public @interface NoRepeatSubmit {
4 }

2、拦截器的实现:

 1 private static final RedisCacheUtils redis = RedisCacheUtils.getInstance(RedisConfigEnum.USER_REPEATSUBMIT_LOCK);
 2
 3 @Override
 4 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 5     HandlerMethod handlerMethod = (HandlerMethod) handler;
 6     Class<?> clazz = handlerMethod.getBeanType();
 7     Method method = handlerMethod.getMethod();
 8     if (method.isAnnotationPresent(NoRepeatSubmit.class)) {
 9         /** 获取token */
10         String token = getToken(request, clazz.getName(), method.getName());
11         if (StringUtils.isBlank(token)) {
12             //token未能获取到,则按照默认处理,并记录日志
13             LOGGER.error("{}.{} 未获取到token", clazz.getName(), method.getName());
14             return true;
15         }
16
17         LOGGER.info("请求URL:{}", request.getRequestURL());
18         if (!lockToken(token)) {
19             //重复提交,丢弃处理并记录日志
20             LOGGER.error("{}.{} 重复提交", clazz.getName(), method.getName());
21             return false;
22         }
23     }
24
25     return true;
26 }
27
28 @Override
29 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
30     HandlerMethod handlerMethod = (HandlerMethod) handler;
31     Class<?> clazz = handlerMethod.getBeanType();
32     Method method = handlerMethod.getMethod();
33     if (method.isAnnotationPresent(NoRepeatSubmit.class)) {
34         String token = getToken(request, clazz.getName(), method.getName());
35         if (StringUtils.isNotBlank(token)) {
36             LOGGER.info("请求URL处理完成:{}", request.getRequestURL());
37             unLockToken(token);
38         }
39     }
40 }
41
42 /**
43  * 从request中读取出token
44  *
45  * @param request
46  * @return
47  */
48 private String getToken(HttpServletRequest request, String className, String methodName) {
49     if (request.getCookies() != null) {
50         for (Cookie cookie: request.getCookies()) {
51             if (StringUtils.equalsIgnoreCase(cookie.getName(), TOKEN_KEY)) {
52                 String cookieToken = cookie.getValue();
53
54                 return String.format("%s.%s.%s", className, methodName, cookieToken);
55             }
56         }
57     }
58
59     return null;
60 }
61
62 /**
63  * 给token上锁
64  *
65  * @param token
66  */
67 private boolean lockToken(String token) {
68     return redis.setIfAbsent(token, System.currentTimeMillis() + token);
69 }
70
71 /**
72  * 释放token上的锁
73  *
74  * @param token
75  */
76 private void unLockToken(String token) {
77     redis.delete(token);
78 }

3、在web.xml中定义拦截器:

1 <mvc:interceptors>
2     <bean class="*.*.*.common.Interceptor.RepeatSubmitInterceptor" />
3 </mvc:interceptors>

4、在接口方法上添加注解:

1 @RequestMapping(value = "/test.do_",method = {RequestMethod.POST})
2 @ResponseBody
3 @NoRepeatSubmit
4 public Result<JSONObject> testFunc(){
5     //......业务代码实现.......
6 }

原文地址:https://www.cnblogs.com/laoxia/p/11664043.html

时间: 2024-10-25 22:21:39

防重复提交实现方案的相关文章

(九)Struts2 防重复提交

所有的学习我们必须先搭建好Struts2的环境(1.导入对应的jar包,2.web.xml,3.struts.xml) 第一节:重复提交示例演示 第二节:使用<s:token/>标签防重复提交 <s:token></s:token> :加在form 里: 使用token 拦截器: <interceptor-ref name="token"></interceptor-ref> <interceptor-ref name=

struts2学习(15)struts2防重复提交

一.重复提交的例子: 模拟一种情况,存在延时啊,系统比较繁忙啊啥的. 模拟延迟5s钟,用户点了一次提交,又点了一次提交,例子中模拟这种情况: 这样会造成重复提交: com.cy.action.StudentAction.java: package com.cy.action; import java.io.File; import org.apache.commons.io.FileUtils; import com.cy.model.Student; import com.opensympho

防重复提交

// 防重复提交 定义全局变量 var checkingCorrespondflg = false; =============== 点击提交判断 if (!checkingCorrespondflg) { checkingCorrespondflg = true; } else { return false; } =============== 出错之后设置 checkingCorrespondflg = false;

springmvc的token防重复提交

一:首要创立一个号码大全token处置类  ,这儿的类名叫关键词挖掘工具  TokenHandler private static Logger logger = Logger.getLogger(TokenHandler.class); static Map springmvc_token http://www.3h5.cn = null; //生成一个仅有值的token @SuppressWarnings("unchecked") public synchronized stati

spring MVC 后台token防重复提交解决方案

看到公司有个部门提出了这个问题,补个粗略的解决方案... 1.编写拦截器 /** * Description: 防止重复提交 * * @Author liam * @Create Date: 2018/3/9 9:22 */ public class AvoidReSubmitIntercepter extends HandlerInterceptorAdapter { private static final String SPLIT_FLAG = "_"; private stat

防止跨站请求伪造(CSRF)攻击 和 防重复提交 的方法的实现

CSRF的概率可以参考:http://netsecurity.51cto.com/art/200812/102951.htm 本文介绍的是基于spring拦截器的Spring MVC实现 首先配置拦截器: <mvc:interceptors> <mvc:interceptor> <!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller --> <mvc:mapping path="/xxx/**" /> <

AJAX防重复提交的办法总结

如果提交对象为按钮的话,可以对按钮设置disabled,此办法适应于按钮提交,此种方法简单粗暴,也是很多人用的办法,代如下: //在按钮提交之后和AJAX提交之前将按钮设置为禁用 $("input[type=submit]").attr('disabled',true) $.ajax({ url:'/post.php' data:{a:1,b,1} success:function(){ //在提交成功之后重新启用该按钮 $("input[type=submit]"

表单防重复提交

防止表单重复提交 介绍了使用 redirect 技术防止表单提交,但是 redirect 解决不了后退到表单页面时重复提交表单,为了解决这个问题,加入了 token 的机制.如果每个 form 相关的处理方法中都写一遍 token 的生成和校验代码,在实际项目中是不太能接受的,接下来介绍了使用拦截器的方式生成和校验 token. 1. 常规防止表单重复提交流程: GET 访问表单页面 填写表单 POST 提交表单 Server 端处理表单数据,例如把数据写入数据库 重定向到另一个页面,防止用户刷

SpringMVC 防重复提交拦截

/**  * Created with IntelliJ IDEA.  * User: lijian  * Date: 14-7-11  * Time: 上午10:38  * description :防止重复提交数据     http://hi.baidu.com/uvmfawxtxzbknqe/item/3c87d4220acba32442634acf  * To change this template use File | Settings | File Templates.  */ p