重複提交的問題在web開發中是很常碰到的一個問題,主要分爲前端和後端兩種途徑解決,前端處理通常採用提交事件後,禁止用戶再次點擊提交按鈕,等待服務端結果再重置提交按鈕狀態。前端
本文着重介紹,經過java後端處理重複提交問題。開發環境是:spring boot 2.0+react+ant+dva,下圖是主要流程思路:java
如下是詳細步驟代碼:react
1:客戶端登錄,服務端登錄成功後返回初始的表單令牌web
package com.df.web.manager.security; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.UUID; /** * @類名稱: * @類描述: * @建立人 劉丹 * @建立時間 2018/6/23 * @最後修改人 劉丹. * @最後修改時間 2018/6/23. * @版本:1.0 */ public class FormTokenUtil { public static String refreshFormToken(HttpServletRequest request, HttpServletResponse response) { String newFormToken = UUID.randomUUID().toString(); response.setHeader("formToken", newFormToken); request.getSession(true).setAttribute("formToken", newFormToken); return newFormToken; } }
2:前端獲取服務端返回的formTokenspring
sessionStorage.setItem("formToken", resData.result.formToken);
3:在前端統一的request(fetch)的headers中增長表單token項json
return request(serviceUrl, { method: "POST", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'formToken': sessionStorage.getItem("formToken") }, body: data, credentials: 'include' });
4:服務端使用aop技術攔截指定註解的Controller請求後端
package com.df.web.manager.aop; import com.df.web.manager.security.FormTokenUtil; import com.empiresoft.annotation.FormToken; import com.empiresoft.pojo.common.ActionResultGenerator; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @類名稱: 表單重複提交攔截處理 * @類描述: * @建立人 劉丹 * @建立時間 2018/6/23 * @最後修改人 劉丹. * @最後修改時間 2018/6/23. * @版本:1.0 */ @Aspect @Component public class FormTokenAspect { private final Logger logger = LoggerFactory.getLogger(this.getClass()); /** * 對formToken註解的Action執行重複提交驗證 * * @param proceedingJoinPoint * @param formToken * @return */ @Around("@annotation(formToken)") public Object execute(ProceedingJoinPoint proceedingJoinPoint, FormToken formToken) { try { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); HttpServletResponse response = attributes.getResponse(); String strFormToken = request.getHeader("formToken"); if (strFormToken == null) { return ActionResultGenerator.errorResult("表單Token不能爲空!"); } Object sessionFormToken = request.getSession(true).getAttribute("formToken"); if (sessionFormToken == null || !sessionFormToken.toString().equals(strFormToken)) { return ActionResultGenerator.errorResult("請勿重複提交數據!"); } //放行 Object o = proceedingJoinPoint.proceed(); //重置表單令牌 且寫入response 重置前端 表單令牌 FormTokenUtil.refreshFormToken(request, response); return o; } catch (Throwable e) { logger.error(e.getMessage()); return ActionResultGenerator.errorResult("發生異常!"); } } }
5:前端監控Response返回的數據中是否包含表單token項,若是包含則重置前端sessionStorage的表單token。promise
import fetch from 'dva/fetch'; import { message } from 'antd'; function parseJSON(response) { if (response.headers.get("formToken")) { sessionStorage.setItem("formToken", response.headers.get("formToken")) } return response.json(); } function checkStatus(response) { if (response.status >= 200 && response.status < 300) { return response; } } /** * Requests a URL, returning a promise. * * @param {string} url The URL we want to request * @param {object} [options] The options we want to pass to "fetch" * @return {object} An object containing either "data" or "err" */ export default function request(url, options) { return fetch(url, options) .then(checkStatus) .then(parseJSON) .then(data => ({ data })) .catch((err) => { }); }
註解定義:session
package com.empiresoft.annotation; import java.lang.annotation.*; /** * @類名稱:FormToken註解類 * @類描述:使用此註解 則表示須要驗證FormToken, 用於處理表單重複提交 * @建立人 劉丹 * @建立時間 2018/6/23 * @最後修改人 劉丹. * @最後修改時間 2018/6/23. * @版本:1.0 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface FormToken { }
標記須要重複提交驗證antd
@FormToken @RequestMapping(value = "/call_service", method = RequestMethod.POST) public ActionResult callServiceByPost(@RequestBody CallService callService) throws Exception { return OauthClientUtil.callUnifiedPlatformService(callService, SecurityUtil.getLoginUser(request), request); }
注:如需容許用戶不一樣的表單使用不一樣的表單token,只對同性質表單作重複提交驗證,可在先後端對token名稱"formToken"的命名作擴展處理。