处理重复请求的三种方式

最近做了个业务需求,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 "正常返回";
    }

测试结果

如何重写接口implements 接口如何处理重复请求_java

通过重复请求就会抛出异常

3.数据库唯一索引

数据库处理就是设置唯一索引,可设联合唯一索引用来处理重复数据。缺点:如果业务场景就是应该存储重复的数据,则该种方式不可用。

小结

重复请求常用的处理方式就是幂等性处理,幂等性可以理解为:无论执行了多少次重复请求,数据只会处理一次,在数据库里也只会有一条数据。和数据库的唯一索引是一样的。

参考:

https://codingnote.cc/p/141289/