springBoot防止重复提交

两种方法,
一种是后端实现,较复杂,要通过自定义注解和AOP以及Redis组合实现
另一种是前端实现,简单,只需通过js,设置过期时间,一定时间内,多次点击按钮只生效一次

后端实现

自定义注解+AOP+Redis

自定义注解

package com.wzw.config.anno;

import java.lang.annotation.*;

/**
 * 自定义注解防止表单重复提交
 */
@Target(ElementType.METHOD) // 注解只能用于方法
@Retention(RetentionPolicy.RUNTIME) // 修饰注解的生命周期
@Documented
public @interface RepeatSubmit {

    /**
     * 防重复操作过期时间,默认1s
     */
    long expireTime() default 1;
}

AOP

package com.wzw.config.aspect;

import com.wzw.config.anno.RepeatSubmit;
import com.wzw.config.exception.CustomException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
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.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 防止重复提交切面
 */
@Slf4j
@Component
@Aspect
public class RepeatSubmitAspect {

    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.wzw.config.anno.RepeatSubmit)")
    public void repeatSubmit() {}

    @Around("repeatSubmit()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        // 获取防重复提交注解
        RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);

        // 获取token当做key
         String token = request.getHeader("token");
        if (StringUtils.isBlank(token)) {
            throw new RuntimeException("token不存在,请登录!");
        }

        String url = request.getRequestURI();
        /**
         *  通过前缀 + url + token 来生成redis上的 key
         *  可以在加上用户id,小编这里没办法获取,大家可以在项目中加上
         */
        String redisKey = "repeat_submit_key:"
                .concat(url)
                .concat(token);
        log.info("==========redisKey ====== {}",redisKey);

        if (!redisTemplate.hasKey(redisKey)) {
            redisTemplate.opsForValue().set(redisKey, redisKey, annotation.expireTime(), TimeUnit.SECONDS);
            try {
                //正常执行方法并返回
                return joinPoint.proceed();
            } catch (Throwable throwable) {
                redisTemplate.delete(redisKey);
                throw new Throwable(throwable);
            }
        } else {
            // 抛出异常
            throw new CustomException("请勿重复提交");
        }
    }
}

自定义异常类和全局异常处理

自定义异常类:CustomException

package com.wzw.config.exception;

/**
 * 自定义异常
 */
public class CustomException extends Exception {

    public CustomException() {
        super();
    }

    public CustomException(String message) {
        super(message);
    }
}

全局异常处理:CustomExceptionHandler

package com.wzw.config.exception;

import com.wzw.base.pojo.Result;
import com.wzw.config.exception.CustomException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class CustomExceptionHandler {

    /**
     * 每当抛出CustomException异常,就会进入这里
     * @param e 自定义异常类
     * @return  返回值实体
     */
    @ExceptionHandler(value = CustomException.class)
    @ResponseBody
    public Result handleCustomException(CustomException e){

        Result result=Result.init();
        result.setMsg(e.getMessage());
        result.setCode(0);
        return result;
    }

}


响应实体:Result

package com.wzw.base.pojo;

import lombok.Data;

/**
 * 返回值响应实体
 */
@Data
public class Result {
    private int code;
    private String msg;
    private Object data;

    public static Result init(){
        return new Result();
    }

    public static Result ok(){
        Result result=Result.init();
        result.code=1;
        return result;
    }

    public static Result ok(String msg){
        Result result=Result.ok();
        result.setMsg(msg);
        return result;
    }

    public static Result err(){
        Result result=Result.init();
        result.code=0;
        return result;
    }

    public static Result err(String msg){
        Result result=Result.err();
        result.setMsg(msg);
        return result;
    }
}

Redis

设置Redis配置数据源,然后创建 Redis配置类

package com.wzw.config.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }
}

使用

    /**
     * 防止重复提交测试
     * @return  响应实体
     * @throws CustomException  自定义异常类
     */
    @RequestMapping("/testCustomerException")
    @ResponseBody
    @RepeatSubmit(expireTime = 10)  //不加expireTime,默认1s内,加参数可以重新设定防止重复提交的时间
    public Result testCustomerException() throws CustomException {

        Result result=Result.ok("请求成功");

        return result;
    }

可以看到第一次是请求成功,之后五秒都是提示请勿重复提交
springBoot防止重复提交_第1张图片

前端实现

来源:https://blog.csdn.net/liangmengbk/article/details/127075604

/* 防止重复点击 */
let clickTimer = 0
 
function clickThrottle() {
  var interval = 3000;//3秒钟之内重复点击只算一次点击
  let now = +new Date(); // 获取当前时间的时间戳
  let timer = clickTimer; // 记录触发事件的事件戳
 
  if (now - timer < interval) {
    // 如果当前时间 - 触发事件时的事件 < interVal,那么不符合条件,直接return false,
    // 不让当前事件继续执行下去
    return false;
  } else {
    // 反之,记录符合条件触发了事件的时间戳,并 return true,使事件继续往下执行
    clickTimer = now;
    return true;
  }
}

在需要的地方进行调用:

if(!clickThrottle()) return;

你可能感兴趣的:(java)