Skip to main content

防止重复提交

背景

平时开发的项目中可能会出现下面这些情况:

  • 由于用户误操作,多次点击表单提交按钮。
  • 由于网速等原因造成页面卡顿,用户重复刷新提交页面。
  • 黑客或恶意用户使用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的值用于失效时间
//判断请求是否重复
}