赞
踩
两种方法,
一种是后端实现,较复杂,要通过自定义注解和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;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。