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