java+react前後端分離專案處理重複提交問題
阿新 • • 發佈:2019-01-22
重複提交的問題在web開發中是很常碰到的一個問題,主要分為前端和後端兩種途徑解決,前端處理一般採用提交事件後,禁止使用者再次點選提交按鈕,等待服務端結果再重置提交按鈕狀態。
本文著重介紹,通過java後端處理重複提交問題。開發環境是:spring boot 2.0+react+ant+dva,下圖是主要流程思路:
以下是詳細步驟程式碼:
1:客戶端登陸,服務端登陸成功後返回初始的表單令牌
package com.df.web.manager.security;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
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:前端獲取服務端返回的formToken
sessionStorage.setItem("formToken", resData.result.formToken);
3:在前端統一的request(fetch)的headers中增加表單token項
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;
/**
* @類名稱: 表單重複提交攔截處理
@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。
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) => {
});
}
註解定義:
package com.empiresoft.annotation;
import java.lang.annotation.*;
/**
* @類名稱:FormToken註解類
* @類描述:使用此註解 則表示需要驗證FormToken, 用於處理表單重複提交
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FormToken {
}
標記需要重複提交驗證
@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"的命名做擴充套件處理