两种方法,
一种是后端实现,较复杂,要通过自定义注解和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;
}
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配置类
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;
}
来源: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;