所谓重复提交,也就是是我们经常体提及的接口幂等性。这个问题在很多场景下会出现,因为我个人版的内推蛙小系统用的服务器不算太好的,所以我自己在使用的时候会有一些问题,那就是重复提交内推帖子的问题,目前我已经采取了下面我提及的三种方法的第三种AOP加自定义注解的方式解决了这一问题。

重复提交可能出现的场景

  1. 前端按钮
  2. 卡顿刷新
  3. 恶意操作

带来的问题

  1. 数据重复或者错乱
  2. 增加服务器压力

解决方案

  1. 按钮置灰

这种方式实现起来比较简单,就是在前端保证,在第一次提交后,把按钮给置灰,不让用户多次点击

  1. 建立唯一索引

如果网络不好,用户在网页端自行刷新,这样按钮置灰就没什么用了,所以我们可以在数据库层面添加一个唯一索引,这样就不会出现重复的数据了

  1. AOP+自定义注解

这就是我在项目中采用的方案了。下面看看是怎么实现的。

自定义注解类

@Target(ELementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit{
enum Type {PARAM,TOKEN}
Type limitType() default Type.PARAM;
long LockTime() default 5;
}

注解中

  • Type 提供枚举值,可以根据参数或者token两种方式来进行防重复提交
  • limitType 指定使用那种方式
  • LockTime 代表了在固定的时间范围内提交了同样的参数过来就会被认为是重复提交

切面类

切面类中就重点关注两点,一点就是切入点,我们给它指定为加了注解的方法进行监控,指定之后他就会拦截加了注解的方法。

@Pointcut(value "@annotation(repeatSubmit)")
public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit){}

第二个关注通知方式

我采用的是环绕通知

@Around(value "pointCutNoRepeatSubmit(repeatSubmit)",argNames "joinPoint,repeatSubmit")

在这个方法中,我们就可以写防抖的具体逻辑了。为保护版权,这里就不给出我写的具体代码了,只给出实现方案。

方式一就是IP+参数+类名+方法名防重复提交

if (type.equalsIgnoreCase(RepeatSubmit.Type.PARAM.name())){
/方式一,参数防重提交
/基于IP、类名、方法名和URL生成唯一key
MethodSignature methodSignature =(MethodSignature)joinPoint.getSignature();
Method method= methodSignature.getMethod();
String className =method.getDeclaringClass().getName();
key=key +String.format("%s-%s-%s-%s",ipAddress,url,className,method);
}

接着采用md5加密生成key,然后判断redis中是否存在这个key,存在就是重复请求,不存在就会存进redis,有效时间就有注解中的locktime决定。我项目中就是用的这种方式。

方式二是token+url的方式来检测是否重复提交,逻辑和第一种一样。

else{
/方式二,令牌形式防重提交
/从请求头中获取token,如果不存在,则抛出异常
String requestToken= request.getHeader(s:"token");
if (StringUtils.isBlank(requestToken)){
Log.error("token不存在,非法请求");
return"token不存在,非法请求"
key =key +String.format("%s-%s",requestToken,url);
}

注解的使用

在要防抖的接口上加上注解

@RepeatSubmit(limitType RepeatSubmit.Type.TOKEN,lockTime 10)