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也給出了一個參考實現。