前端使用防连点+lock,后端使用Spring Aop实现防连点
前言
在前后端开发中,我们常常需要做出一些功能来防止数据异常,例如客户端对按钮的重复点击,可能会添加重复的数据,这时候就需要做出防连点的功能,而简单的防抖动等又不能完全解决这种问题,因此就要做前端和后端的双重防连点。
一、前端实现
1.1 防抖动
我们前端采用防抖动(定义一个计时器,当点击按钮后开始计时,而不会立即放松请求,如果在这个时间段再次点击,计时器将重新计时,只有当计时器计时完成后才会发送请求),如果项目中添加按钮比较多,我们可以将防抖动方法封装成一个组件,在使用的组件中引进即可
let timeout = null;
function debounce(func, wait) {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(function () {
func();
}, wait);
}
export default debounce
引入组件
防抖动遇到的问题
当只使用防抖动实现方法,进行测试时我发现了一个问题:当点击按钮发送第一个请求到达后端,被服务器接受处理的过程中,再次点击按钮发送请求,后端就会收到两个携带相同数据的请求,还是不能够完全的解决防连点的问题。
1.2 采用lock锁
为了解决这个问题,我想到了点击按钮后加上一个lock锁
二、后端Spring Aop实现
2.1 定义一个注解
使用Spring Aop可以定义一个注解
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RepeatSubmit {
}
2.2 使用@Around在方法前检验请求是否重复
获取请求的路径信息,将其存储在服务器session中,当请求到达时,先查看session中是否存在方法请求,如果存在则不让其通行,最后一定要在添加方法执行结束后清除掉session中的请求信息
import com.jz.system.common.ResultVO;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
@Pointcut("@annotation(com.jz.system.aspect.RepeatSubmit)")
public void repeatSubmit() {
}
@Around("repeatSubmit()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
String path = request.getServletPath()+"_"+ request.getMethod() + "_REPEAT_REQUEST";
Object attribute = request.getSession().getAttribute(path);
if (attribute != null) {
return ResultVO.error("请求重复");
} else {
request.getSession().setAttribute(path,path);
try {
return joinPoint.proceed();
} finally {
request.getSession().removeAttribute(path);
}
}
}
return joinPoint.proceed();
}
}