重复提交的问题在web开发中是很常碰到的一个问题,主要分为前端和后端两种途径解决,前端处理一般采用提交事件后,禁止用户再次点击提交按钮,等待服务端结果再重置提交按钮状态。
本文着重介绍,通过java后端处理重复提交问题。开发环境是:spring boot 2.0+react+ant+dva,下图是主要流程思路:
以下是详细步骤代码:
1:客户端登陆,服务端登陆成功后返回初始的表单令牌
packagecom.df.web.manager.security;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.util.UUID;/*** @类名称:
* @类描述:
* @创建人 刘丹
* @创建时间 2018/6/23
* @最后修改人 刘丹.
* @最后修改时间 2018/6/23.
* @版本:1.0*/
public classFormTokenUtil {public staticString refreshFormToken(HttpServletRequest request, HttpServletResponse response) {
String newFormToken=UUID.randomUUID().toString();
response.setHeader("formToken", newFormToken);
request.getSession(true).setAttribute("formToken", newFormToken);returnnewFormToken;
}
}
2:前端获取服务端返回的formToken
sessionStorage.setItem("formToken", resData.result.formToken);
3:在前端统一的request(fetch)的headers中增加表单token项
returnrequest(serviceUrl,
{
method:"POST",
headers: {‘Accept‘: ‘application/json‘,‘Content-Type‘: ‘application/json‘,‘formToken‘: sessionStorage.getItem("formToken")
},
body: data,
credentials:‘include‘});
4:服务端使用aop技术拦截指定注解的Controller请求
packagecom.df.web.manager.aop;importcom.df.web.manager.security.FormTokenUtil;importcom.empiresoft.annotation.FormToken;importcom.empiresoft.pojo.common.ActionResultGenerator;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.stereotype.Component;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;/*** @类名称: 表单重复提交拦截处理
* @类描述:
* @创建人 刘丹
* @创建时间 2018/6/23
* @最后修改人 刘丹.
* @最后修改时间 2018/6/23.
* @版本:1.0*/@Aspect
@Componentpublic classFormTokenAspect {private final Logger logger = LoggerFactory.getLogger(this.getClass());/*** 对formToken注解的Action执行重复提交验证
*
*@paramproceedingJoinPoint
*@paramformToken
*@return
*/@Around("@annotation(formToken)")publicObject 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);returno;
}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"))
}returnresponse.json();
}
function checkStatus(response) {if (response.status >= 200 && response.status < 300) {returnresponse;
}
}/*** 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"*/exportdefaultfunction request(url, options) {returnfetch(url, options)
.then(checkStatus)
.then(parseJSON)
.then(data=>({ data }))
.catch((err) =>{
});
}
注解定义:
packagecom.empiresoft.annotation;import java.lang.annotation.*;/*** @类名称:FormToken注解类
* @类描述:使用此注解 则表示需要验证FormToken, 用于处理表单重复提交
* @创建人 刘丹
* @创建时间 2018/6/23
* @最后修改人 刘丹.
* @最后修改时间 2018/6/23.
* @版本:1.0*/@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)public @interfaceFormToken {
}
标记需要重复提交验证
@FormToken
@RequestMapping(value= "/call_service", method =RequestMethod.POST)public ActionResult callServiceByPost(@RequestBody CallService callService) throwsException {returnOauthClientUtil.callUnifiedPlatformService(callService, SecurityUtil.getLoginUser(request), request);
}
注:如需允许用户不同的表单使用不同的表单token,只对同性质表单做重复提交验证,可在前后端对token名称"formToken"的命名做扩展处理。
原文:https://www.cnblogs.com/liudan996/p/9218034.html