后端解决表单重复提交:
在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
在下列情况下,服务器程序将拒绝处理用户提交的表单请求:
- 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
- 当前用户的Session中不存在Token(令牌)。
- 用户提交的表单数据中没有Token(令牌)。
跳转表单,生成token,存储到session中:
package com.example.demo.learn.servlet.session.duplicatesubmit;
import com.example.demo.learn.Constants;
import com.example.demo.learn.servlet.session.utils.TokenUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@Controller
public class FormController {
/**
* 跳转到页面, 生成一个token
*
* @return 具体页面位置
*/
@RequestMapping("toPage")
public String toPage(HttpServletRequest request, Model model) {
String token = TokenUtils.getToken();
model.addAttribute(Constants.SESSION_TOKEN, token);
request.getSession().setAttribute(Constants.SESSION_TOKEN, token);
return "form";
}
}
表单页面:
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<head>
<title>表单防止重复提交</title>
<script>
var flag = false;
function isSubmit() {
if (!flag) {
flag = true;
return true;
} else {
return false;
}
}
function doSubmit(){
//获取表单提交按钮
var btnSubmit = document.getElementById("submit");
//将表单提交按钮设置为不可用,这样就可以避免用户再次点击提交按钮
btnSubmit.disabled= "disabled";
//返回true让表单可以正常提交
return true;
}
</script>
</head>
<body>
<!-- 前端提交解决
<form action="/song/DuplicateSubmitResolve" method="post" onsubmit="doSubmit()">
用户名:<input type="text" name="userName">
<input type="submit" value="提交" id="submit">
</form>
-->
<form action="/song/DuplicateSubmitResolve" method="post" onsubmit="doSubmit()">
用户名:<input type="text" name="userName">
<input type="text" name="sessionToken" th:value="${sessionToken}" >
<input type="submit" value="提交" id="submit">
</form>
</body>
</html>
表单处理类:
package com.example.demo.learn.servlet.session.duplicatesubmit;
import com.example.demo.learn.Constants;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 解决重复提交问题
* <p>
* <p>
* 表单重复提交的原因?
* 1. 网路延迟
* 2. 表单刷新重复提交
* <p>
* 表单重复提交解决办法:
* 1. 前端解决:
* 提交按钮只能触发一次
* 提交按钮变灰
* 2. 后端解决:
* 使用token + session
*/
@WebServlet("/DuplicateSubmitResolve")
public class DuplicateSubmitResolve extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("userName");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
if (!isSubmit(req)) {
out.write("您已经提交了数据或者无效的token.....");
return;
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("模拟插入数据库.... userName: " + userName);
out.write("保存成功");
// 保存之后删除sessionToken
req.getSession().removeAttribute(Constants.SESSION_TOKEN);
}
/**
* 校验是否提交
*
* @param req request
* @return boolean
*/
private boolean isSubmit(HttpServletRequest req) {
String parameterToken = req.getParameter("sessionToken");
// 获得token
HttpSession session = req.getSession();
String sessionToken = (String) session.getAttribute(Constants.SESSION_TOKEN);
if (sessionToken == null || parameterToken == null) {
return false;
}
// 防止伪造token
if (!parameterToken.equals(sessionToken)) {
return false;
}
return true;
}
}
package com.example.demo.learn;
public class Constants {
public static final String SESSION_TOKEN = "sessionToken";
}