赞
踩
欢迎访问我的个人博客:www.ifueen.com
在业务开发中,为什么我们要去想办法解决重复提交这一问题发生?网上的概念很多:导致表单重复提交,造成数据重复,增加服务器负载,严重甚至会造成服务器宕机,那么为什么会造成这种现象?前台操作的抖动,快速操作,网络通信或者后端响应慢,都会增加后端重复处理的概率,就拿我亲身经历来说,因为业务逻辑,需要进行一个"关注"操作,但是写好业务之后在测试时连续点击几下,重复地进行关注和取消关注操作,因为操作过于频繁,而服务器走过来的响应速度没有那么快地进行处理,导致重复数据插入地情况,最后导致在查询关注的时候服务器报错,这个时候,放重复提交就显得很重要了
其实实现地方法有很多,但是原理大概都是相通的,我选择的是通过使用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和注解都写好了,现在只需要在要用到的请求上面打上这个注解就可以了
其他请求需要进行防重复提交,也只需要打上这个注解即可
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。