「Java 开发实例」SpringBoot+AOP+注解+Redis防重复提交(防抖)
🙏废话不多说系列,直接开整🙏
一、前提准备
(1)Maven 引入
<!-- 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)配置文件
# 应用名称
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
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
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
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
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