防重复提交的重要性?
在业务开发中,为什么我们要去想办法解决重复提交这一问题发生?网上的概念很多:导致表单重复提交,造成数据重复,增加服务器负载,严重甚至会造成服务器宕机,那么为什么会造成这种现象?前台操作的抖动,快速操作,网络通信或者后端响应慢,都会增加后端重复处理的概率,就拿我亲身经历来说,因为业务逻辑,需要进行一个"关注"操作,但是写好业务之后在测试时连续点击几下,重复地进行关注和取消关注操作,因为操作过于频繁,而服务器走过来的响应速度没有那么快地进行处理,导致重复数据插入地情况,最后导致在查询关注的时候服务器报错,这个时候,放重复提交就显得很重要了
如何防重复提交?
其实实现地方法有很多,但是原理大概都是相通的,我选择的是通过使用AOP+redis来进行处理,前端发起请求的时候需要在请求头里将token给我,然后我这边通过token+ip再加上请求的路径作为一个key存到redis里面去,设置一个合适的过期时间,下一次再从redis中取出和当前时间进行一个判断,如果大于我们设定的一个超时时间,那么就进行拦截,不让它进行下面的业务代码,并给出提示“操作频繁,请稍后重试”
代码实现
前提环境准备:SpringAOP的支持,Redis的支持
其实用文字叙述出来感觉有一点点绕,但是代码实现起来其实不难
首先我们需要自定义一个注解:
package com.ifueen.anntion;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
/**
* @author fueen
* @date 2020/7/4
* 自定义防重复提交注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface NoRepeatSubmit {
/**
* 默认时间 默认1秒钟
* @return
*/
int lockTime() default 1000;
}
然后写一个AOP进行拦截处理
package com.ifueen.aspect;
import com.ifueen.anntion.NoRepeatSubmit;
import com.ifueen.utils.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @author fueen
* @date 2020/7/4 15:07
*/
@Aspect
@Component
@Slf4j
@SuppressWarnings("all")
public class RepeatSubmitAspect {
public static final String KEYPREX="noRpeat:user:";
@Autowired
private RedisTemplate redisTemplate;
/**
* 进行接口防重复操作处理
* @param pjp
* @param noRepeatSubmit
* @return
*/
@Around("execution(* com.ifueen.controller.*.*(..)) && @annotation(noRepeatSubmit)")
public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
try {
//获取request
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
//拿到token和请求路径
StringBuilder sb = new StringBuilder();
sb.append(KEYPREX).append(request.getHeader("token").toString()).append(request.getRequestURI().toString());
//获取现在时间
long now = System.currentTimeMillis();
if (redisTemplate.hasKey(sb.toString())){
//上次请求时间
long lastTime= Long.valueOf(redisTemplate.opsForValue().get(sb.toString()).toString()) ;
// 如果现在距离上次提交时间小于设置的默认时间 则 判断为重复提交 否则 正常提交 -> 进入业务处理
if ((now - lastTime)>noRepeatSubmit.lockTime()){
//重新记录时间 10分钟过期时间
redisTemplate.opsForValue().set(sb.toString(),String.valueOf(now),10, TimeUnit.MINUTES);
//处理业务
Object result = pjp.proceed();
return result;
}else {
return CommonResult.getFaiInstance("-1","点击的太快了,请慢一点!");
}
}else {
//第一次操作
redisTemplate.opsForValue().set(sb.toString(),String.valueOf(now),10, TimeUnit.MINUTES);
Object result = pjp.proceed();
return result;
}
}catch (Throwable e){
log.error("校验表单重复提交时异常: {}", e.getMessage());
return CommonResult.getFaiInstance("-1","校验重复提交时异常");
}
}
}
AOP和注解都写好了,现在只需要在要用到的请求上面打上这个注解就可以了
其他请求需要进行防重复提交,也只需要打上这个注解即可