2019独角兽企业重金招聘Python工程师标准>>>
1. 由于服务器缓慢或网络延迟等原因,导致用户重复点击提交按钮。
2. 使用forward方式已经提交成功,再次刷新成功页面导致重复提交。
3. 已经提交成功,通过回退,再次点击提交按钮。
注意:
在firefox,重复提交到同一地址无效。
回退后,刷新表单页面,再次提交这时不是重复提交,而是发送新的请求。
使用redirect方式重定向到成功页面不会出现重复提交的问题。
要避免重复刷新、重复提交、以及防止后退的问题的,需要区分如何处理以及在哪里处理。处理的方式无非是客户端或者服务器端两种。对于不同的位置处理的方式也是不同的,但是任何客户端(尤其是B/S端)的处理都是不可信任的,最好的也是最应该的是服务器端的处理方法。
1. 客户端处理
javascript只能处理服务器繁忙时的用户多次提交。
1.1 重复刷新、重复提交
方法一:设置一个变量,只允许提交一次。
方法二:将提交按钮或者image置为disable
1.2 防止用户后退
这里的方法是千姿百态,有的是更改浏览器的历史纪录的,比如使用window.history.forward()方法;有的是“用新页面的URL替换当前的历史纪录,这样浏览历史记录中就只有一个页面,后退按钮永远不会变为可用。”比如使用javascript:location.replace(this.href); event.returnValue=false;
2. 服务器端处理
实现思路:
使用UUID生成随机数,借助session,使用令牌机制来实现。
服务器在每次产生的页面FORM表单中分配一个唯一标识号(这个唯一标识号可以使用UUID产生),将这个唯一标识号保存在该FORM表单中的一个隐藏域中。同时在当前用户的session域中保存该唯一标识符。当用户提交表单时,服务器接收程序比较FORM表单隐藏字段中的标识号和存储在当前用户session域中的标识号是否一致。如果一致,则处理表单数据,同时清除当前用户session域中存储的标识号。如果不一致,服务器接收程序不处理提交的表单请求。
使用以上方式会导致编辑页面只能有一个。解决方案是:为每一个提交的form表单增加一个属性提交区别来源于不同表单。
示例主要代码:
UUIDToken.java
package cn.heimar.common.util;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class UUIDToken {
public static final String UUIDTOKEN_IN_REQUEST = "UUIDTOKEN_IN_REQUEST";
public static final String UUIDTOKEN_IN_SESSION = "UUIDTOKEN_IN_SESSION";
public static final String UUIDTOKEN_FORMID_IN_REQUEST = "UUIDTOKEN_FORMID_IN_REQUEST";
private static UUIDToken instance = new UUIDToken();
private UUIDToken(){
}
public static UUIDToken getInstance(){
return instance;
}
/**
* 生成UUIDToken
* @return
*/
public void generateUUIDToken(HttpServletRequest req){
HttpSession session = req.getSession(true);
UUID uuid = UUID.randomUUID();
UUID uuid_form_id = UUID.randomUUID();
req.setAttribute(UUIDTOKEN_IN_REQUEST, uuid.toString());
req.setAttribute(UUIDTOKEN_FORMID_IN_REQUEST, uuid_form_id.toString());
session.setAttribute(uuid_form_id.toString(), uuid.toString());
}
/*
* 是否重复提交
* 思路:
* 1,如果没有session,验证失败
* 2,如果session中没有UUIDTOKEN_IN_SESSION,验证失败
* 3,如果session中的UUIDTOKEN_IN_SESSION和表单中的UUIDTOKEN_IN_REQUEST不相等,验证失败
*/
public boolean isRepeatSubmit(HttpServletRequest req){
//是否重复提交(默认true重复提交)
boolean isRepeatSubmit = true;
HttpSession session = req.getSession(false);
if(session != null){
String uuidToken_formId = req.getParameter("UUIDTOKEN_FORMID_IN_REQUEST");
if(StringUtil.isNotBlank(uuidToken_formId)){
String uuidToken_in_session = (String)session.getAttribute(uuidToken_formId);
if(StringUtil.isNotBlank(uuidToken_in_session)){
String uuidToken_in_request = req.getParameter(UUIDTOKEN_IN_REQUEST);
if(uuidToken_in_session.equals(uuidToken_in_request)){
isRepeatSubmit = false;
//清除session中的uuid防重复提交令牌
session.removeAttribute(uuidToken_formId);
}
}
}
}
return isRepeatSubmit;
}
}
ProductServlet.java
package cn.heimar.demo.servlet;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import cn.heimar.common.core.dao.DaoFactory;
import cn.heimar.common.util.PageResult;
import cn.heimar.common.util.StringUtil;
import cn.heimar.common.util.UUIDToken;
import cn.heimar.demo.dao.IProductDao;
import cn.heimar.demo.dao.ISupplierDao;
import cn.heimar.demo.dao.query.ProductQueryObject;
import cn.heimar.demo.domain.Product;
import cn.heimar.demo.domain.Supplier;
public class ProductServlet extends HttpServlet {
private static final long serialVersionUID = -3393643900564582082L;
private IProductDao productDao;
private ISupplierDao supplierDao;
private boolean hasLength(String s) {
return s != null && !"".equals(s.trim());
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String cmd = req.getParameter("cmd");
if ("edit".equals(cmd)) {
// 转向添加页面
String id = req.getParameter("id");
if (hasLength(id)) {
Product p = productDao.get(Long.parseLong(id));
if (p != null)
req.setAttribute("p", p);
}
List suppliers = supplierDao.getSimpleSupplier();
req.setAttribute("suppliers", suppliers);
//防止重复提交:
//在导向编辑页面时,向request和session域中添加uuid随机数
UUIDToken.getInstance().generateUUIDToken(req);
// 导向添加页面
req.getRequestDispatcher("/WEB-INF/views/demo/product/edit.jsp").forward(req, resp);
}
// 执行保存过程
else if ("save".equals(cmd)) {
String id = req.getParameter("id");
Product p = new Product();
if (hasLength(id)) {
p = productDao.get(Long.parseLong(id));
}
System.out.println(req.getParameter("supplier"));
// 设置值
setProperties(req, p);
//判断是否重复提交
if(!UUIDToken.getInstance().isRepeatSubmit(req)){
if (p.getId() != null) {
productDao.update(p);
} else {
productDao.save(p);
}
}else{
System.out.println("重复提交!");
}
//重定向
resp.sendRedirect(req.getContextPath() + "/product");
} else {
if ("del".equals(cmd)) {
// 执行删除过程
String id = req.getParameter("id");
if (hasLength(id)) {
productDao.delete(Long.parseLong(id));
}
}
// 列出所有货品(除了转向添加/编辑页面,其他都要执行这句)
// List ps = dao.getAll();
// req.setAttribute("ps", ps);
// req.getRequestDispatcher("/WEB-INF/jsp/list.jsp")
// .forward(req, resp);
ProductQueryObject pqo = createQueryObject(req);
// 高级查询
PageResult ps = productDao.getByQuery(pqo);
req.setAttribute("page", ps);
List suppliers = supplierDao.getSimpleSupplier();
req.setAttribute("suppliers", suppliers);
req.setAttribute("qo",pqo);
req.getRequestDispatcher("/WEB-INF/views/demo/product/list.jsp")
.forward(req, resp);
}
}
/**
* 创建高级查询对象
*
* @param req
* @return
*/
private ProductQueryObject createQueryObject(HttpServletRequest req) {
ProductQueryObject pqo = new ProductQueryObject();
pqo.setName(req.getParameter("name"));
pqo.setSn(req.getParameter("sn"));
pqo.setBrand(req.getParameter("brand"));
String supplier = req.getParameter("supplier");
if(StringUtil.isNotBlank(supplier)){
pqo.setSupplier(Long.parseLong(supplier));
}
String salePrice1 = req.getParameter("salePrice1");
if (hasLength(salePrice1))
pqo.setSalePrice1(new BigDecimal(salePrice1));
String salePrice2 = req.getParameter("salePrice2");
if (hasLength(salePrice2))
pqo.setSalePrice2(new BigDecimal(salePrice2));
String costPrice1 = req.getParameter("costPrice1");
if (hasLength(costPrice1))
pqo.setCostPrice1(new BigDecimal(costPrice1));
String costPrice2 = req.getParameter("costPrice2");
if (hasLength(costPrice2))
pqo.setCostPrice2(new BigDecimal(costPrice2));
String currentPage = req.getParameter("currentPage");
if (hasLength(currentPage)) {
pqo.setCurrentPage(Integer.parseInt(currentPage));
}
return pqo;
}
/**
* 设置值
*
* @param req
* @param p
*/
private void setProperties(HttpServletRequest req, Product p) {
p.setName(req.getParameter("name"));
p.setSn(req.getParameter("sn"));
String supplier_id = req.getParameter("supplier");
if(StringUtil.isNotBlank(supplier_id)){
Supplier s = new Supplier();
s.setId(Long.parseLong(supplier_id));
p.setSupplier(s);
}
p.setBrand(req.getParameter("brand"));
p.setTypes(req.getParameter("types"));
p.setUnit(req.getParameter("unit"));
p.setSalePrice(new BigDecimal(req.getParameter("salePrice")));
p.setCostPrice(new BigDecimal(req.getParameter("costPrice")));
p.setCutoffPrice(new BigDecimal(req.getParameter("cutoffPrice")));
}
@Override
public void init() throws ServletException {
super.init();
productDao = (IProductDao) DaoFactory.getDao("productDao");
supplierDao = (ISupplierDao) DaoFactory.getDao("supplierDao");
}
}
edit.jsp
<%@ page language="java" pageEncoding="utf-8" contentType="text/html; charset=utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
编辑产品
3. struts的同步令牌
利用同步令牌(Token)机制来解决Web应用中重复提交的问题,Struts也给出了一个参考实现。
基本原理:
服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,
看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给
客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次
提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。