javaweb開發之防止重複提交

1、產生表單重複提交可能的狀況

1. 因爲服務器緩慢或網絡延遲等緣由,致使用戶重複點擊提交按鈕。 javascript

2. 使用forward方式已經提交成功,再次刷新成功頁面致使重複提交。 html

3. 已經提交成功,經過回退,再次點擊提交按鈕。 java

注意: 數組

在firefox,重複提交到同一地址無效。 瀏覽器

回退後,刷新表單頁面,再次提交這時不是重複提交,而是發送新的請求。 服務器

使用redirect方式重定向到成功頁面不會出現重複提交的問題。 網絡

2、解決方式

要避免重複刷新、重複提交、以及防止後退的問題的,須要區分如何處理以及在哪裏處理。處理的方式無非是客戶端或者服務器端兩種。對於不一樣的位置處理的方式也是不一樣的,可是任何客戶端(尤爲是B/S端)的處理都是不可信任的,最好的也是最應該的是服務器端的處理方法。 session

1. 客戶端處理

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;

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<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>

3. struts的同步令牌

利用同步令牌(Token)機制來解決Web應用中重複提交的問題,Struts也給出了一個參考實現。

基本原理: 服務器端在處理到達的請求以前,會將請求中包含的令牌值與保存在當前用戶會話中的令牌值進行比較, 看是否匹配。在處理完該請求後,且在答覆發送給客戶端以前,將會產生一個新的令牌,該令牌除傳給 客戶端之外,也會將用戶會話中保存的舊的令牌進行替換。這樣若是用戶回退到剛纔的提交頁面並再次 提交的話,客戶端傳過來的令牌就和服務器端的令牌不一致,從而有效地防止了重複提交的發生。
相關文章
相關標籤/搜索