防止表单重复提交

自定义注解实现

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
    /**
     * 指定时间内不可重复提交
     */
    long intervalTime() default 3000;//毫秒

}

方法一:

拦截器实现

首先在 addInterceptors 中添加自定义的拦截器,addPathPatterns:拦截路径,代码如下


    @Override
    public void addInterceptors(InterceptorRegistry registry)
    {
        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
    }
    然后在自定义拦截器中重写preHandle(该方法会在视图之前执行),代码如下:
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            log.info("防止重复提交");
            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
            if (annotation != null) {
                if (this.isRepeatSubmit(request)) {
                    throw new SSyException("不允许重复提交,请稍后再试");
                }
            }
            return true;
        } else {
            return super.preHandle(request, response, handler);
        }
    }
 public abstract boolean isRepeatSubmit(HttpServletRequest request);
 
//最后就是isRepeatSubmit的实现方法了

/**
 * 判断请求url和数据是否和上一次相同,
 * 如果和上次相同,则是重复提交表单。 有效时间为intervalTime,yml中配置。
 */
    @Component
    public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
    {
    public final String REPEAT_PARAMS = "repeatParams";

    public final String REPEAT_TIME = "repeatTime";

    @Value("${token.header}")
    private String header;
    @Value("${token.intervalTime}")
    private int intervalTime;

    @Autowired
    private RedisCache redisCache;
    
    //防止重复提交实现,思想,同一个token,同样的参数限制时间
    @SuppressWarnings("unchecked")
    @Override
    public boolean isRepeatSubmit(HttpServletRequest request) {
           String nowParams = "";
        if (request instanceof RepeatedlyRequestWrapper) {
            RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) 	request;
            nowParams = HttpHelper.getBodyString(repeatedlyRequest);
        }

        // body参数为空,获取Parameter的数据
        if (StringUtils.isEmpty(nowParams)) {
        	//如果方法带参数,那ParameterMap会把参数带上
            nowParams = JSONObject.toJSONString(request.getParameterMap());
            
        }
        Map<String, Object> nowDataMap = new HashMap<>(16);
        nowDataMap.put(REPEAT_PARAMS,{});
        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());

        // 请求地址(作为存放map的key值)
        String url = request.getRequestURI();

        // 唯一值(作为存放redis的key值,没有token则使用请求地址)
        String submitKey = request.getHeader(header);
        if (StringUtils.isEmpty(submitKey)) {
            submitKey = url;
        }
        // 唯一标识(指定key + 消息头)
        String CACHE_REPEAT_KEY = Constants.REPEAT_SUBMIT_KEY + submitKey;

        Object sessionObj = redisCache.getCacheObject(CACHE_REPEAT_KEY);
        if (sessionObj != null)
        {
            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
            //比较该访问地址该token是否访问过并且存在
            if (sessionMap.containsKey(url))
            {
                Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);

                //比较存取的参数和当前参数,时间差是否小于yml定义的时间
                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap))
                {
                    return true;
                }
            }
        }
        //新的map用于存储redis
        Map<String, Object> cacheMap = new HashMap<String, Object>();
        cacheMap.put(url, nowDataMap);
        redisCache.setCacheObject(CACHE_REPEAT_KEY, cacheMap, intervalTime, TimeUnit.SECONDS);
        return false;
    }

    /**
     * 判断参数是否相同
     */
    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)
    {
        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
        String preParams = (String) preMap.get(REPEAT_PARAMS);
        return nowParams.equals(preParams);
    }

    /**
     * 判断两次间隔时间
     */
    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap)
    {
        long time1 = (Long) nowMap.get(REPEAT_TIME);
        long time2 = (Long) preMap.get(REPEAT_TIME);
        if ((time1 - time2) < (this.intervalTime * 1000))
        {
            return true;
        }
        return false;
    }
}

方法二:

aop实现

//思想,同样的ip类名,方法名,计算hash值放到缓存中,限制时间
    @Aspect
    @Component
    public class RepeatSubmitAspect {

    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * @param point
     */
    @Around(value = "@annotation(com.meeting.common.annotation.RepeatSubmit)")
    public Object around(ProceedingJoinPoint point) throws Throwable {

        HttpServletRequest request = ((ServletRequestAttributes) 	                 
        RequestContextHolder.currentRequestAttributes()).getRequest();
        //获取请求的ip地址
        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //获取请求的类名
        String className = method.getDeclaringClass().getName();
        //获取请求的方法
        String name = method.getName();
        String ipKey = String.format("%s#%s", className, name);
        //计算出hash值
        int hashCode = Math.abs(ipKey.hashCode());
        String key = Constants.REDIS_ROOT + String.format("%s_%d", ip, hashCode);
        log.info("ipKey={},hashCode={},key={}", ipKey, hashCode, key);
        
        RepeatSubmit repeatSubmit = method.getAnnotation(RepeatSubmit.class);
        //intervalTime是定义注解是的参数,可以给默认值,自由填充
        long timeout = repeatSubmit.intervalTime();
        if (timeout < 0) {
            timeout = 3 * Constants.SECOND;
        }
        String value = (String) redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(value)) {
            throw new CustomException("请勿重复提交");
        }
        redisTemplate.opsForValue().set(key, UUID.randomUUID().toString(), timeout, TimeUnit.SECONDS);
        //执行方法
        Object object = point.proceed();
        return object;
    }

}

效果查看:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值