处理重复请求的三种方式
最近做了个业务需求,web交互页面,上线没多久后出现了重复请求的问题。描述如下:用户需要先在学习材料页停留一定时长后可作答单元测试,测试答题结果以表单形式提交给后端。用户最多答3次,今天查看后台数据发现有个用户作答了4次。通过一系列日志查询,确认是用户自己快速点击了4次造成的。
网络延迟是造成用户多次点击的原因,用户在国外,需要访问国内服务器,延迟大了自然会出现这种case。
今天我搜查了网络上的各种方法,自己做了一下总结.
1.前端ajax处理
优点:前端可在用户点击之后将按钮锁定,不可点击,直到后端返回数据才释放锁。前端处理是最为简单有效,且对用户比较友好的方式。可用提示语提醒用户等待。
缺点:无法应对直接刷后端接口的情况,如果用户直接调后端接口,无法处理(属于特殊情况)。
方式:ajax防止重复提交
这里我用登录页面功能作为例子讲解,按下面的方法,用户在点击登录后,按钮会不可点击,直到后端返回结果,关键js代码如下:
function login(){
//1.去用户名密码
var user=$("#inputName").val();
var pwd=$("#inputPassword").val();
//2.让提交按钮失效,以实现防止按钮重复点击
$("#loginBtn").attr('disabled', 'disabled');
//3.给用户提供友好状态提示
$("#loginBtn").text('登录中...');
//4.异步提交
$.ajax({
type:"POST",
url:"/verifylogin",
dataType:"json",
data:{
"username":user,
"password":pwd
},
success : function(data) {
//登录成功跳转
},
error:function(){
//5.失败后,登录按钮重新有效
$("#loginBtn").removeAttr('disabled');
alert("登录 发生错误");
}
});
}
2.后端AOP处理
后端处理逻辑是需要使用额外的存储数据,记录用户访问接口次数。在重复提交时拦截请求,对于重复的请求直接返回就行,不做任何处理。
方式:自定义注解+AOP
这里我用springboot2+redis+maven 构造示例,请求的思路是将用户访问记录缓存,5s内,请求的记录在redis中有记录时直接抛出异常。
自定义注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface AccessLimit {
//有效时间
int seconds() default 5;
//有效时间内最大计数
int maxCount() default 1;
}
拦截器
@Service
public class AccessInterceptor extends HandlerInterceptorAdapter {
@Autowired
private RedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
//获取方法中的注解,是否有限制注解
AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
if (Objects.isNull(accessLimit)) {
return true;
}
int seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
//获取入参
String userId = request.getParameter("userId");
if (Objects.isNull(userId)) {
return false;
}
ValueOperations operations = redisTemplate.opsForValue();
Integer count = (Integer) operations.get(userId);
//第一次访问,userId作为key,value为1
if (count == null) {
operations.set(userId, 1);
redisTemplate.expire(userId, seconds, TimeUnit.SECONDS);
} else if (count < maxCount) {
operations.increment(userId);
} else {
//超出访问次数
throw new Exception("请求过快");
}
}
return true;
}
}
测试接口
@AccessLimit
@GetMapping("/test/accessLimit")
public String testAccessLimit(@RequestParam("userId") String userId) {
return "正常返回";
}
测试结果
通过重复请求就会抛出异常
3.数据库唯一索引
数据库处理就是设置唯一索引,可设联合唯一索引用来处理重复数据。缺点:如果业务场景就是应该存储重复的数据,则该种方式不可用。
小结
重复请求常用的处理方式就是幂等性处理,幂等性可以理解为:无论执行了多少次重复请求,数据只会处理一次,在数据库里也只会有一条数据。和数据库的唯一索引是一样的。
参考:
https://codingnote.cc/p/141289/