🙏废话不多说系列,直接开整🙏
前期知识储备:
- 什么是防抖?
- ① 控制用户频繁快速点击同一个操作;
- ② 防止网络抖动;
- ③ 避免接口请求频繁过载;
- 哪一类接口需要防抖?
- 用户输入类接口;
- 按钮点击类接口;
- 滚动加载类接口;
- 防抖的大致思路是?
- 防抖即 重复提交,接口参数再短时间一致,如果多台机器客户端发送过来的请求被拦截了也是不合理的,需要再加上 IP地址进行区分。
一、前提准备
(1)Maven 引入
1 2 3 4 5 6 7 8 9 10 11
| <!-- spring boot aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- Spring boot redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
|
(2)配置文件
1 2 3 4 5 6 7 8 9 10
| # 应用名称 server.port=39002 spring.application.name=transfer # redis config spring.redis.host=localhost spring.redis.database=0 spring.redis.port=6379 spring.redis.password= spring.redis.timeout=2
|
二、完整源码
核心功能:① 通过注解的方式指定接口的重复时间;② 可以指定接口锁定日期和时间粒度;
(1)定义注解 @NoRepeatSubmitAopByRedis
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.TimeUnit;
/** * 定义一个注解(目前可以指定缓存KEY的时间) * * @author drew * @apiNote @Target(ElementType.METHOD) 作用到方法上 * @apiNote @Retention(RetentionPolicy.RUNTIME) 只有运行时有效 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoRepeatSubmitByRedis { /** * 锁定时间 * @return 锁定时间 */ int lockedTime() default 2;
/** * 时间单位(时分秒等) * @return 单位 */ TimeUnit timeUnit() default TimeUnit.SECONDS; }
|
(2)定义切面 NoRepeatSubmitByRedisAop.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| import edu.study.module.aop.utils.ApiResult; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.Objects;
/** * @author zl * @create 2021-04-03 9:28 */ @Aspect @Configuration public class NoRepeatSubmitByRedisAop { private final Log logger = LogFactory.getLog(getClass());
@Resource private RedisTemplate redisTemplate;
@Pointcut("@annotation(noRepeatSubmitByRedis)") public void pointCut(NoRepeatSubmitByRedis noRepeatSubmitByRedis) { }
@Before("@annotation(noRepeatSubmitByRedis)") public void before(NoRepeatSubmitByRedis noRepeatSubmitByRedis) { }
@Around("pointCut(noRepeatSubmitByRedis)") public Object around(ProceedingJoinPoint pjp, NoRepeatSubmitByRedis noRepeatSubmitByRedis) { try { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); String sessionId = Objects.requireNonNull(RequestContextHolder.getRequestAttributes()).getSessionId(); assert attributes != null; HttpServletRequest request = attributes.getRequest(); String key = sessionId + "-" + request.getServletPath(); // 如果缓存中有这个url视为重复提交 if (redisTemplate.opsForValue().get(key) == null) { Object o = pjp.proceed(); redisTemplate.opsForValue().set(key, request.getRequestURI()); logger.info("请求的KEY:" + key + ",请求URI:" + request.getRequestURI()); redisTemplate.expire(key, noRepeatSubmitByRedis.lockedTime(), noRepeatSubmitByRedis.timeUnit()); return o; } else { logger.error("重复提交"); return new ApiResult(888, "请勿短时间内重复操作", null); } } catch (Throwable e) { e.printStackTrace(); logger.error("验证重复提交时出现未知异常!"); return new ApiResult(889, "验证重复提交时出现未知异常!", null); } }
}
|
三、测试演示
(1)控制层 TestNoRepeatSubmit.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import edu.study.module.aop.NoRepeatSubmit; import edu.study.module.aop.NoRepeatSubmitByRedis; import edu.study.module.aop.utils.ApiResult; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController @RequestMapping(path = "/repeatSubmit") public class TestRepeatSubmitController {
/** * redis: 添加防重复提交注解(提供指定时间和时间粒度) */ @NoRepeatSubmitByRedis(lockedTime = 8, timeUnit = TimeUnit.SECONDS) @RequestMapping("/submitByRedis") public ApiResult testByRedis() { return new ApiResult(0, "测试通过", null); }
}
|
(2)浏览器请求地址
http://localhost:39002/repeatSubmit/submitByRedis
(3)控制台输出
四、总结
(1)此切面没有考虑到 自身如果出现异常,那么如何处理已经保存的缓存KEY呢?需要添加注解 @AfterThrowing 以及 @AfterReturning 注解等相关的方法;
附录
相关具体源码见:【github.com/GitSuperDre…】
(1)统一返回类 ApiResult.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class ApiResult {
private Integer code; private String message; private Object data;
public ApiResult(Integer code, String message, Object data) { this.code = code; this.message = message; this.data = data; }
public Integer getCode() {return code;} public void setCode(Integer code) {this.code = code;} public String getMessage() {return message;} public void setMessage(String message) {this.message = message == null ? null : message.trim();} public Object getData() {return data;} public void setData(Object data) {this.data = data;}
@Override public String toString() { return "ApiResult{" + "code=" + code + ", message='" + message + '\'' + ", data=" + data + '}'; }
}
|
🙏至此,非常感谢阅读🙏
原文链接: https://juejin.cn/post/7365831823928229938