记得前几次面试的时候,3家公司问了表单重复提交的问题,我回答的都不是特别好,虽然知道是个什么流程,但是因为只知道理论,所以面试官问的细一点就懵了
这次就写了一个demo测试一下之前的想法是否准确.
1 表单重复提交大部分情况是用户点击提交时,因为服务端处理数据太慢,没能及时返回结果给用户,用户重复的点击提交按钮,这个时候就会出现重复提交,这里我用Thread.sleep增加响应延迟
2 用户提交表单成功,服务端处理完成并将结果响应给用户了.但是可能用户手滑一直f5请求刷新,这时候也会造成表单重复提交
3 用户提交表单成功,服务端处理完成并将结果响应给用户了.但是可能用户手滑一直返回提交返回提交,这时候也会造成表单重复提交
鉴于以上3种经常出现情况,都有相应的解决方案
1 js方案
提交按钮设置为不可用状态. 但是无法防止2,3种情况发生
2 当用户请求form页面时,在服务端生成token,并存放在当前用户session中和form页面表单的隐藏域中
当用户点击请求时必须将表单隐藏的token传入到服务端,然后进行两端token的验证,验证不同过打回去
@RequestMapping("/doForm")
public String doForm(HttpServletRequest request) {
//创建token
String token = UUID.randomUUID().toString();
//在服务端session中保存token
request.getSession().setAttribute("token", token);
System.out.println("本次生成的token:" + token);
//跳转到form页面
return "/fromResubmit/form";
}
@RequestMapping(value = "/submit", produces = "text/html;charset=UTF-8")
@ResponseBody
public String submit(HttpServletRequest request) {
//取出客户端表单中的token,如果表单中没有token则认为用户是重复提交
String clientToken = request.getParameter("token");
if (StringUtils.isEmpty(clientToken)) {
return "请不要重复提交";
}
//取出服务端当前用户session中的token,如果为空则认为用户重复提交
String serviceToken = (String) request.getSession().getAttribute("token");
if (StringUtils.isEmpty(serviceToken)) {
return "请不要重复提交";
}
//判断两个客户端和服务端我token是否相同,不相同认为用户重复提交
if (!Objects.equals(clientToken, serviceToken)) {
return "请不要重复提交";
}
//确定本次请求不是重复提交,移除session中的token
request.getSession().removeAttribute("token");
return "提交成功";
}
有些基础的东西真的很值得深究啊,就拿这个表单重新提交 我发现我们公司的项目组没有一个有重复验证的
之前我做的那个重复回款验证的 也是根据现有的数据用redis做的锁来验证重复提交的,之后再根据状态来验证是否打回去,所以完全不适用上面的方案
如果项目是集群部署的话 可以考虑用一个中间件来替换session,这样就不会出现重复提交的尴尬了
源码在此:https://github.com/zhouminsen/web