1. 因为服务器缓慢或网络延迟等缘由,致使用户重复点击提交按钮。 javascript
2. 使用forward方式已经提交成功,再次刷新成功页面致使重复提交。 html
3. 已经提交成功,经过回退,再次点击提交按钮。 java
注意: 数组
在firefox,重复提交到同一地址无效。 浏览器
回退后,刷新表单页面,再次提交这时不是重复提交,而是发送新的请求。 服务器
使用redirect方式重定向到成功页面不会出现重复提交的问题。 网络
要避免重复刷新、重复提交、以及防止后退的问题的,须要区分如何处理以及在哪里处理。处理的方式无非是客户端或者服务器端两种。对于不一样的位置处理的方式也是不一样的,可是任何客户端(尤为是B/S端)的处理都是不可信任的,最好的也是最应该的是服务器端的处理方法。 session
javascript只能处理服务器繁忙时的用户屡次提交。 dom
1.1 重复刷新、重复提交 jsp
方法一:设置一个变量,只容许提交一次。
<script language="javascript"> var checkSubmitFlag = false; function checkSubmit() { if (checkSubmitFlag == true) { return false; } checkSubmitFlag = true; return true; } document.ondblclick = function docondblclick() { window.event.returnValue = false; } document.onclick = function doc { if (checkSubmitFlag) { window.event.returnValue = false; } } </script> <html:form action="myAction.do" method="post" onsubmit="return checkSubmit();">
方法二:将提交按钮或者image置为disable
<html:form action="myAction.do" method="post" onsubmit="getElById('submitInput').disabled = true; return true;"> <html:image styleId="submitInput" src=\'#\'" /ok_b.gif" border="0" /> </html:form>
1.2 防止用户后退
这里的方法是千姿百态,有的是更改浏览器的历史纪录的,好比使用window.history.forward()方法;有的是“用新页面的URL替换当前的历史纪录,这样浏览历史记录中就只有一个页面,后退按钮永远不会变为可用。”好比使用javascript:location.replace(this.href); event.returnValue=false;实现思路:
使用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<Supplier> 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<Product> ps = dao.getAll(); // req.setAttribute("ps", ps); // req.getRequestDispatcher("/WEB-INF/jsp/list.jsp") // .forward(req, resp); ProductQueryObject pqo = createQueryObject(req); // 高级查询 PageResult<Product> ps = productDao.getByQuery(pqo); req.setAttribute("page", ps); List<Supplier> 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" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>编辑产品</title> </head> <body> <form name="form1" action="<%=request.getContextPath() %>/product?cmd=save" method="post" > <input type="hidden" name="id" value="${p.id }"> <input type="hidden" name="UUIDTOKEN_IN_REQUEST" value="${UUIDTOKEN_IN_REQUEST }"> <input type="hidden" name="UUIDTOKEN_FORMID_IN_REQUEST" value="${UUIDTOKEN_FORMID_IN_REQUEST }"> <table width="90%" border="0" align="center" cellpadding="4" cellspacing="2" class="gray-bd3-2"> <tr> <td width="100" align="center" class="brightcyan-tb">产品名称:</td> <td><input name="name" type="text" class="big-input" maxLength="20" size="20" value="${p.name }"/></td> <tr> <tr> <td width="100" align="center" class="brightcyan-tb">产品编码:</td> <td><input name="sn" type="text" class="big-input" maxLength="20" size="20" value="${p.sn }"/></td> <tr> <tr> <td width="100" align="center" class="brightcyan-tb">供应商:</td> <td> <select name="supplier"> <option value="0">--请选择--</option> <c:forEach var="o" items="${suppliers }"> <option value="${o.id }" <c:if test="${not empty id && p.supplier.id == o.id }">selected="selected"</c:if>> ${o.name } </option> </c:forEach> </select> <!-- <input name="supplier" type="text" class="big-input" maxLength="20" size="20" value="${p.supplier }"/> --> </td> <tr> <tr> <td width="100" align="center" class="brightcyan-tb">品牌:</td> <td><input name="brand" type="text" class="big-input" maxLength="20" size="20" value="${p.brand }"/></td> <tr> <tr> <td width="100" align="center" class="brightcyan-tb">分类:</td> <td><input name="types" type="text" class="big-input" maxLength="20" size="20" value="${p.types }"/></td> <tr> <tr> <td width="100" align="center" class="brightcyan-tb">单位:</td> <td><input name="unit" type="text" class="big-input" maxLength="20" size="20" value="${p.unit }"/></td> <tr> <tr> <td width="100" align="center" class="brightcyan-tb">零售价:</td> <td><input name="salePrice" type="text" class="big-input" maxLength="20" size="20" value="${p.salePrice }"/></td> <tr> <tr> <td width="100" align="center" class="brightcyan-tb">成本价:</td> <td><input name="costPrice" type="text" class="big-input" maxLength="20" size="20" value="${p.costPrice }"/></td> <tr> <tr> <td width="100" align="center" class="brightcyan-tb">折扣价:</td> <td><input name="cutoffPrice" type="text" class="big-input" maxLength="20" size="20" value="${p.cutoffPrice }"/></td> <tr> </table> <p align="center"> <input name="ok" type="button" class="small-btn" value="提交" onClick="save()"/> <input type="reset" class="small-btn" value="重置" /> </p> </form> <script type="text/javascript"> function save(){ //forms javaScrip的内置对象 表示form的集合 是一个数组 //提交表单 document.forms[0].submit(); } </script> </body> </html>
利用同步令牌(Token)机制来解决Web应用中重复提交的问题,Struts也给出了一个参考实现。