共计 3329 个字符,预计需要花费 9 分钟才能阅读完成。
有两种防止重复提交:
禁用提交按钮
发出请求令牌 / ID:
禁用提交按钮
我们可以在函数调用 HTTP 请求之前禁用提交按钮,并在完成 HTTP 响应后再次启用它。该技术对于需要很长时间才能完成的过程(超过 5 秒)是有效的。由于不耐烦而无法获得结果,用户无法再次单击 n ’。此外,我们可能会显示一个正在 Loading 装载进度,以获得良好的体验。
<!DOCTYPE html>
<html lang=”en”>
<head>
<script type=”text/javascript” src=”https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js”></script>
</head>
<body>
<form name=”form-payment” id=”form-payment”>
…
</form>
<script type=”text/javascript”>
$(‘#form-payment’).submit(function (e) {
e.preventDefault();
$.ajax({
type: ‘POST’,
dataType : “json”,
contentType: “application/json; charset=utf-8”,
url: “#”,
data: “{}”,
beforeSend: function(){
$(‘#button-submit’).attr(‘disabled’, ‘disable’);
},
complete: function(){
$(‘#button-submit’).removeAttr(‘disabled’);
},
success: function (data) {
// do your success action
},
error: function () {
// do your error handler
}
});
});
</script>
</body>
在 beforeSend 和 complete 段,我添加“disable”属性作为开关,(jquery 中有专门语句防止二次提交)
重点来了:
Spring Boot 中如何发出请求令牌 / ID
这种技术实际上更复杂,更难实现,但是由于一个好的框架(如 Spring Boot)使这更容易。在我们开始代码实现之前,让我们先讨论一下这个机制;
加载表单页面时,发出新的 requestId
在调用后端服务之前将已发出的 requestId 发送到 HTTP 头
后端服务标识 requestId 是否已注册
如果 requestId 已经注册,那么我们可以将其标记为违规请求
我们来开始代码。这里是我的 JavaScript 中的示例代码,用于发出新的 requestId。
$(document).ready(function () {
var requestId = new Date().getTime(); // <— issue new requestId every time page laoded
$(‘#form-payment’).submit(function (e) {
e.preventDefault();
$.ajax({
type: ‘POST’,
dataType : “json”,
contentType: “application/json; charset=utf-8”,
headers: {“requestId” : requestId}, // <— add requestId in header
url: “#”,
data: “{}”,
beforeSend: function(){
$(‘#button-submit’).attr(‘disabled’, ‘disable’);
},
complete: function(){
$(‘#button-submit’).removeAttr(‘disabled’);
},
success: function (data) {
},
error: function () {
}
});
});
});
这里是我的 Spring Boot 项目中的示例代码,我创建了一个 Interceptor 来处理 requestId:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class Interceptor implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ViolationInterceptor()).addPathPatterns(“/**”);
}
public class ViolationInterceptor extends HandlerInterceptorAdapter {
private List<String> requestIds = new ArrayList<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestId = request.getHeader(“requestId”);
if (requestIds.contains(requestId))
throw new IllegalArgumentException(“Violation Request; Reason requestId already registered”);
requestIds.add(requestId);
return super.preHandle(request, response, handler);
}
}
}
Exception 处理:
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class ExceptionAdvisor {
@ExceptionHandler(IllegalArgumentException.class)
ResponseEntity illegalArgumentExceptionHandler(IllegalArgumentException e){
return ResponseEntity.ok(e.getMessage());
}
}
在此示例中,我使用应用程序内存来存储 requestId。对于认真的开发,我建议使用内存数据库,例如 Redis。
实际上,我们可以在识别 requestId 时修改如何发布新令牌和逻辑。因为这个过程非常简单,我们需要一些东西(requestId)来识别已经请求过的东西。
写在最后:
既然看到这里了,觉得笔者写的还不错的就点个赞,加个关注呗!点关注,不迷路,持续更新!!!