表单页面JSP:
1 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 2 <html> 3 <head> 4 <title>表单页面</title> 5 </head> 6 <body> 7 <form action="commit" method="post"> 8 <input type="text" name="username" /> 9 <input type="submit" id="submit"/> 10 </form> 11 </body> 12 </html>
表单处理Servlet:
1 public class FormServlet extends HttpServlet { 2 @Override 3 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 4 try { 5 Thread.sleep(2000); // 模拟网络延迟 6 } catch (InterruptedException e) { 7 e.printStackTrace(); 8 } 9 req.setCharacterEncoding("utf-8"); 10 System.out.println("对" + req.getParameter("username") + "进行处理"); 11 } 12 }
用户重复提交的场景
只列举了常见的场景
场景一:表单提交后,因为网络延迟,让用户有时间重复点击提交。
场景二:表单提交后,用户刷新页面,导致表单重复提交。
场景三:表单提交后,用户退回上一个页面,再次点击提交。
场景四:在一个浏览器中,打开两个标签页进行提交。
重复提交的解决方案
方案一:在前端,通过设置一个标识变量,标识表单的提交状态。(只能解决场景一)
标识变量默认为false,一旦表单提交触发,会判断标识变量是否为false,如果为false则发送请求并且将标识变量更改为true,如果为true则不发送请求。
1 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 2 <html> 3 <head> 4 <title>表单页面</title> 5 <script type="text/javascript"> 6 var isCommitted = false; // 默认未提交 7 function doSubmit() { 8 if (isCommitted == false) { 9 isCommitted = true; // 改为已提交 10 return true; 11 }else { 12 return false; 13 } 14 } 15 </script> 16 </head> 17 <body> 18 <form action="commit" method="post" onsubmit="return doSubmit()"> <!--通过doSubmit函数,动态地给onsubmit属性赋值--> 19 <input type="text" name="username" /> 20 <input type="submit" id="submit"/> 21 </form> 22 </body> 23 </html>
方案二:在后端,通过session和唯一Token来判断重复提交。(可以解决四个场景)
服务器通过session为用户保存一个唯一Token,并且给到浏览器,浏览器会将其保存在一个隐藏的域中随表单提交。
当第一次提交时,提交的Token与session中的Token相等,进行相应处理,并且删除session中的Token。
1.TokenProcessor的工具类
1 public class TokenProcessor { 2 private static final TokenProcessor instance = new TokenProcessor(); 3 4 public static TokenProcessor getInstance() { 5 return instance; 6 } 7 public String makeToken() { 8 String token = String.valueOf(System.currentTimeMillis() + new Random().nextInt(999999999)); 9 try { 10 MessageDigest md = MessageDigest.getInstance("md5"); 11 byte md5[] = md.digest(token.getBytes()); 12 Base64.Encoder encoder = Base64.getEncoder(); 13 return encoder.encodeToString(md5); 14 } catch (NoSuchAlgorithmException e) { 15 e.printStackTrace(); 16 return null; 17 } 18 } 19 }
2.获取session和Token的Servlet
1 public class GetSessionServlet extends HttpServlet { 2 @Override 3 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 4 String token = TokenProcessor.getInstance().makeToken(); 5 System.out.println(token); // 打印Token的值 6 req.getSession(true).setAttribute("token", token); 7 req.getRequestDispatcher("/index.jsp").forward(req, resp); 8 } 9 }
3.表单页面JSP
1 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 2 <html> 3 <head> 4 <title>表单页面</title> 5 </head> 6 <body> 7 <form action="commit" method="post"> 8 <input type="text" name="username" /> 9 <input type="hidden" name="token" value=${sessionScope.token} /> 10 <input type="submit" id="submit"/> 11 </form> 12 </body> 13 </html>
4.表单处理Servlet
1 public class FormServlet extends HttpServlet { 2 @Override 3 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 4 try { 5 Thread.sleep(2000); // 模拟网络延迟 6 } catch (InterruptedException e) { 7 e.printStackTrace(); 8 } 9 req.setCharacterEncoding("utf-8"); 10 HttpSession session = req.getSession(false); 11 if (session == null) { 12 System.out.println("未拥有session,不处理"); 13 return; 14 } 15 String token = (String)session.getAttribute("token"); 16 if (token == null) { 17 System.out.println("session中未拥有Token,不处理"); 18 return; 19 } 20 if (req.getParameter("token") == null) { 21 System.out.println("浏览器Token为空,不处理"); 22 } 23 if (!token.equals(req.getParameter("token"))) { 24 System.out.println("Token不一致,不处理"); 25 return; 26 } 27 session.removeAttribute("token"); 28 System.out.println("对" + req.getParameter("username") + "进行处理"); 29 } 30 }
测试结果:
场景一
场景二
场景三
场景四(只有后进入的表单页面有正确的Token)