表單重複提交的常見應用場景
一、在網絡延遲的狀況下讓用戶又是加你點擊屢次submit按鈕致使
二、表單提交後用戶點擊刷新按鈕致使表單重複提交
三、用戶表單提交後,點擊瀏覽器後退按鈕退回表單頁面後進行再次提交前端
不少狀況下,重複提交的數據,都不是咱們想要的,如訂單的提交,申請退款的提交等,那麼如何作到防重提交呢?java
下面介紹有4種方法,重點最後一種:web
1.給數據庫所需的字段添加上惟一性約束spring
此方法最有效的防止了數據重複提交,可是前臺仍是會出現重複提交的狀況,後臺回報錯.數據庫
2.前端使用js在點擊按鈕提交後設置disable,後或者js設置一個屬性,提交前爲true,提交後爲false
客戶端禁用js,這種方法將無效apache
3.使用Post/Redirect/Get 設計模式
Post/Redirect/Get簡稱PRG,是一種能夠防止表單數據重複提交的一種Web設計模式,像用戶刷新提交響應頁面等比較典型的重複提交表單數據的問題可使用PRG模式來避免。例如:當用戶提交成功以後,執行客戶端重定向,跳轉到提交成功頁面。瀏覽器
注意:PRG設計模式並不適用全部的重複提交狀況,好比:服務器
1)因爲服務器響應緩慢,用戶刷新提交POST請求形成的重複提交。網絡
2)用戶點擊後退按鈕,返回到數據提交界面,致使的數據重複提交。
3)用戶屢次點擊提交按鈕,致使的數據重複提交。
4)用戶惡意避開客戶端預防屢次提交手段,進行重複數據提交。
4.使用session和註解設置令牌
所謂令牌其實就是一種標識,標識當前提交狀態,好比之前古代打戰時,皇帝發佈命令時會給個虎符給手下的人攜帶到將軍那邊傳達,而將軍手上也有皇帝給的另外一半虎符,將軍一對比,果真能湊成一對,就根據傳達的命令去打戰,若是來人沒有攜帶虎符,將軍是確定把來人砍頭的.
那麼這個令牌怎麼設置,下圖就是寫這個令牌token的一個思路:
1.先寫一個簡單的註解類
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
*/
@Target(value=ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TokenForm {
//用於標記須要防重提方法的 ,建立Token的屬性 boolean create() default false; //用於標記須要防重提方法的,刪除Token的屬性 boolean remove() default false;
}
2.在跳轉到增長頁面前,建立token
/** * 跳轉到增長頁面 * 請求路徑:${pageContext.request.contextPath}/admin/toAdminAdd * @return */ @RequestMapping(value="/toAdminAdd") @TokenForm(create=true) public String toAdminAdd(HttpServletRequest request) { return "manager/adminAdd"; }
3.添加數據後刪除token
/**
* 增長管理員 * 請求路徑:${pageContext.request.contextPath }/admin/addAdmin * @param admin * @param request * @return */ @RequestMapping(value="/addAdmin") @TokenForm(remove=true) public String addAdmin(@RequestParam Map<String, Object> admin,HttpServletRequest request) { try { //將密碼Md5編碼後在插入 admin.put("admin_pwd",Md5Utils.md5((String)admin.get("admin_pwd")) ); LOGGER.debug("-增長管理員-"+admin); adminService.addAdmin(admin); request.setAttribute("admin_add_msg", "增長管理員成功"); } catch (Exception e) { e.printStackTrace(); request.setAttribute("admin_add_msg", "增長管理員失敗"); } return "manager/adminAdd"; }
4.建立一個攔截器,攔截髮送路徑前的token信息
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import cn.gzsxt.annotation.TokenForm;
public class TokenInterceptor implements HandlerInterceptor {
private static final Logger LOGGER = LogManager.getLogger(TokenInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //第一步:得到調用處理方法的註解 HandlerMethod hm=(HandlerMethod) handler; TokenForm tokenForm = hm.getMethodAnnotation(TokenForm.class); //第二步:判斷是否有Token註解 if (tokenForm!=null) { HttpSession session = request.getSession(); if (tokenForm.create()==true) { session.setAttribute("token", UUID.randomUUID().toString()); LOGGER.debug("打印出來的token:"+session.getAttribute("token")); } if (tokenForm.remove()==true) { //判斷表單的Token與服務端的Token是否相同 String formToken = request.getParameter("token"); Object sessionToken = session.getAttribute("token"); //傳遞過來的Token與服務端的Token相同,容許操做,而且刪除session的Token if (formToken.equals(sessionToken)){ session.removeAttribute("token"); }else{ //跳轉到指定的路徑 String invoke = request.getParameter("token.invoke"); response.sendRedirect(request.getContextPath()+invoke); return false; } } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // TODO Auto-generated method stub } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // TODO Auto-generated method stub }
5.前端指定提交的token和重複提交後可跳轉的地址token.invoke
<input type="hidden" name="token" value="${sessionScope.token }"<input type="hidden" name="token.invoke" value="/admin/toAdminAdd">