防止重复提交
背景
平时开发的项目中可能会出现下面这些情况:
- 由于用户误操作,多次点击表单提交按钮。
- 由于网速等原因造成页面卡顿,用户重复刷新提交页面。
- 黑客或恶意用户使用postman等工具重复恶意提交表单(攻击网站)。 这些情况都会导致表单重复提交,造成数据重复,增加服务器负载,严重甚至会造成服务器宕机。因此有效防止表单重复提交有一定的必要性。
解决方案
前端画面控制
通过前端代码,当用户点击提交按钮后,屏蔽提交按钮使用户无法点击提交按钮或点击无效,从而实现防止表单重复提交。 由于代码很容易被绕过。比如用户通过刷新页面方式,或使用postman等工具绕过前段页面仍能重复提交表单。因此此方法只能作为后端控制的补充,减少重复提交的发生机率。
<a-button :loading="loading">
提交
</a-button>
后端访问控制
单线程模式
- 自定义注解 @NoRepeatSubmit 标记所有Controller中的提交请求
- 通过AOP 对所有标记了 @NoRepeatSubmit 的方法拦截
- 在业务方法执行前,获取当前用户的 token + 当前请求地址,存进内存中,给个有效时间X秒,X秒内的请求如果在缓存中就报错“重复请求,请稍后再试”。
多线程模式
- 自定义注解 @NoRepeatSubmit 标记所有Controller中的提交请求
- 通过AOP 对所有标记了 @NoRepeatSubmit 的方法拦截
- 在业务方法执行前,获取当前用户的 token + 当前请求地址,作为一个唯一 KEY,去获取 Redis 分布式锁(如果此时并发获取,只有一个线程会成功获取锁)
- 业务方法执行后,释放锁
代码实现
定义一个注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {
/**
* 过期的时间
* */
int seconds() default 5;
}
根据指定的注解定义一个切面
@Aspect
@Component
public class NoRepeatSubmitAspect {
/**
* 横切点
*/
@Pointcut("@annotation(noRepeatSubmit)")
public void repeatPoint(NoRepeatSubmit noRepeatSubmit) {
}
/**
* 接收请求,并记录数据
*/
@Around(value = "repeatPoint(noRepeatSubmit)")
public Object doBefore(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) {
//获取token与url
//如果单线程使用Cache<String,Object>,需要seconds的值用于失效时间
//如果多线程使用redis分布式锁,需要seconds的值用于失效时间
//判断请求是否重复
}