java学习笔记之: 重复提交解决方案
来源:互联网 发布:电路分析软件 编辑:程序博客网 时间:2024/06/07 18:39
一.场景:
a.一个按钮在未响应前多次点击 常见为 网络慢造成(如双11 购买按钮)
b.响应完成后再次提交相同数据,属于正常的请求,是后台防止数据库数据重复的问题,那是另外一个故事了
c.提交后页面上的url是要提交的目标链接 显示是servlet 然后刷新/后退 会造成重复提交
(现在基本用Ajax,就不会有这个问题,这种场景涉及技术很老 基本不会出现了 所以忽略吧)
所以针对第一个场景:
二.方案
1.前台方案:标记flag
提交一次提交后就不再提交/ 外观可见为按钮不允许再点 比较简单
2.后台方案:利用session
a.在这些关键词中,我们用什么来避免场景a呢?
session + request,每次点击会生成新的request,其传递数据是相同的,但其是共享session的,不同的request共享一个session,目的是只让其中一个request有效,遵循这个思路,可以再request中放一个token,session里也放一个,然后多个request中只有一个token和session对比有效 ,似乎就可以防止到了
b.在此场景下衍生出一个场景问题:同一个浏览器打开两个窗口, 窗口间也是共享session的,那session改了窗口的正常提交场景都有可能是报错的
那就多存几个session里token值,由String存改为list存,可以限制list大小以及token存在时间,同时可以搞定表单超时问题
c.but request中的token怎么来,表单生成前就需要先经过后台交互生成token值,好麻烦
1.jsp可以直接搞定 jsp页面直接生成request/session中的token2.html就不行了,那是否不生成session而直接交互,然后第一次请求时session里null,接着添加session的值,那第二次请求session里就不为null了 好像这样的逻辑可行-->but 经验证,什么时候添加session里的token都有问题,不行
d.最后一个问题 session是在处理业务前还是后清除呢
很明显 ,若在业务后清除,等第一个请求处理完业务然后去清除session的时间,可能其他请求已经验证完成了,就验证不到了,所以 得在验证完后就 立即清除,确保不会验证有效性
请注意 以上方案针对的场景是弱网络状况的下的多次点击 即场景a
代码呢 也只是本地跑着玩,实际开发没用上 有问题我不负责的 谢谢
遗留问题就是谁能搞定方案c中提到的request中的token怎么样可以不交互后台而直接得到
核心代码如下:
切面切自定义注解
生成代码的url那里@FormRepeat(getForm=true)
其他需要校验重复性的加上注解@FormRepeat
package com.pafa.testDemo.from;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.List;import java.util.Random;import javax.servlet.http.HttpServletRequest;import jdk.nashorn.internal.parser.TokenStream;import org.apache.commons.lang3.StringUtils;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestAttributes;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import com.google.common.cache.Cache;import com.pafa.testDemo.baseParam.BaseJsonObjectResult;import com.palic.elis.ius.web.param.base.ReturnResult;/** * AOP实现 /拦截器实现 * 名词解释: * 连接点joinpoint 当前连接的点 正在被切的点 具体的方法之类的 * 切入点pointcut 何地(某些方法/某一类注解) * 通知advice 何时(在方法前还是方法后还是异常后) * * @author EX-ZHOUXIAOWEI004 * */@Component@Aspectpublic class FormAspect { private Logger log = LoggerFactory.getLogger(FormAspect.class); private static List<String> tokens = new ArrayList<String>(); // 切入点 这个注解 @Pointcut("@annotation(com.pafa.testDemo.from.FormRepeat)") public void token() { } // 环绕通知 对应切入点是token @Around("token()") public Object aroundToken(ProceedingJoinPoint joinpoint) { Object object = null; String tokenName = "token"; try { System.out.println("AOP环绕通知start"); //获取request RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); // 获取formRepeat注解的值 MethodSignature methodSignature = (MethodSignature) joinpoint.getSignature(); Method method = methodSignature.getMethod(); FormRepeat formRepeat = method.getAnnotation(FormRepeat.class); boolean getForm = formRepeat.getForm(); if (getForm) { generate(request, tokenName); object = joinpoint.proceed(); } else { // 开始校验 if (verify(request, tokenName)) { object = joinpoint.proceed(); }else{ //正式使用不做响应即可 否则返回值必须和对应接口的返回值相同 return BaseJsonObjectResult.getJsonObject("00004", "无效或者重复提交表单"); } } } catch (Throwable e) { // TODO Auto-generated catch block e.printStackTrace(); } // 不干涉返回值 直接返回 return object; } /** * 重置session里先有的值 * * @param request */ public static String generate(HttpServletRequest request, String tokenName) { String token = System.currentTimeMillis() + "" + new Random().nextInt(555); tokens.add(token); //只允许有两个 if(tokens.size() > 2){ tokens.remove(0); } System.out.println("生成的Token:" + token); System.out.println("生成的Tokens:" + tokens); request.getSession().setAttribute(tokenName, tokens); return token; } /** * 校验token * * @param request */ public static Boolean verify(HttpServletRequest request, String tokenName) { System.out.println("校验Token:" + tokenName); Boolean result = false; List sessionToken = (List) request.getSession().getAttribute(tokenName); String requestToken = (String) request.getParameter(tokenName); // 对比token&& session // 如果request没有token 检验不过 if (StringUtils.isEmpty(requestToken)) { result = false; return result; } // 对比session OK if (sessionToken.contains(requestToken)) { result = true; } // 检验完后 不论结果 立即再次生成新的 不等处理业务 generate(request, tokenName); return result; }}
控制器代码
@Controllerpublic class FormSolution { //提交表单的接口 @RequestMapping("/submitForm") @ResponseBody @FormRepeat public JSONObject submitForm(HttpServletRequest request,String form){ System.out.println("业务begin"); try { Thread.currentThread().sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("业务end"); return BaseJsonObjectResult.getSuccessJsonObject(form); } //生成表单token的接口 @RequestMapping("/getForm") @ResponseBody @FormRepeat(getForm=true) public JSONObject getForm(HttpServletRequest request){ return BaseJsonObjectResult.getSuccessJsonObject(); }}
- java学习笔记之: 重复提交解决方案
- Struts2学习笔记之<s:token/>防止表单重复提交
- Struts2学习笔记之<s:token/>防止表单重复提交
- java web 防止表单重复提交解决方案
- 重复提交解决方案
- 表单重复提交解决方案
- Servlet学习笔记---防止表单重复提交
- 页面内容重复提交解决方案
- JSP学习之------>客户端防表单重复提交和服务器端session防表单重复提交
- Struts2学习笔记(十八) 防止表单重复提交
- struts2学习笔记(十四)防重复提交
- java处理重复提交
- java 防止重复提交
- java防止重复提交
- Java笔记--Session:避免表单的重复提交
- js学习之道:js防止表单重复提交
- Struts2框架学习之七:避免表单重复提交
- JavaWeb初级学习 之 会话控制&表单重复提交问题
- HDOJ_1720_A+B Coming
- DSP28035时钟设置讲解
- 数据结构-树
- linux/windows下查看目标文件.a/.lib的函数符号名称
- 验证码自动生成
- java学习笔记之: 重复提交解决方案
- html2
- ApplicationContextAware接口
- 【nginx】nginx反向代理请求响应报文大数据量时的json报文返回不完整
- <c语言经典100例>c13 条件运算符
- Linux 下 NetworkManager 自定义 DNS
- 《数学之美》吴军
- hadoop maven pom.xml文件的配置
- git错误解决 -- 小结