使用注解和AOP实现接口限流、防抖、防重提交
接口限流、防重复提交、接口防抖是保障接口安全、稳定提供服务,避免错误或脏数据产生的重要手段。本文将通过实际代码示例,演示如何使用自定义注解和AOP(面向切面编程)来实现这些功能。
1. 接口限流
1.1 接口限流的概念
接口限流是一种控制应用程序或服务访问速率的技术,常用于防止因请求过多导致系统过载、响应延迟或崩溃。在高并发场景下,合理的限流措施能保障系统的稳定性和可用性。
1.2 自定义注解 @AccessLimit
我们可以通过自定义注解 @AccessLimit
来定义接口的限流规则。
/**
* 接口限流注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AccessLimit {
int seconds() default 10;
int maxCount() default 5;
String msg() default "您操作频率太过频繁,稍后再试";
}
seconds
: 限流时间窗口,默认10秒。maxCount
: 在时间窗口内允许的最大请求次数,默认5次。msg
: 当超过请求限制时返回的提示信息。
1.3 利用AOP实现限流
我们可以通过AOP来捕获并处理带有 @AccessLimit
注解的方法调用,实现接口限流。
@Aspect
@Slf4j
public class AccessLimitAspect {
@Resource
private RedisRepository redisRepository;
@Around("@annotation(accessLimit)")
public Object around(ProceedingJoinPoint point, AccessLimit accessLimit) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = attributes.getResponse();
if (attributes == null) return null;
// 获取方法和类名
MethodSignature signature = (MethodSignature) point.getSignature();
String methodName = signature.getMethod().getName();
String className = point.getTarget().getClass().getName();
// 生成Redis中的限流key
String key = className + ":" + methodName;
int seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
// 检查请求次数
if (!redisRepository.hasKey(key)) {
redisRepository.expire(key, seconds, TimeUnit.SECONDS);
}
long count = redisRepository.opsForValue().increment(key);
if (count > maxCount) {
response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
response.getWriter().write(accessLimit.msg());
return null;
}
return point.proceed();
}
}
1.4 注册限流切面
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class AccessLimitConfig {
@Bean
public AccessLimitAspect accessLimitAspect() {
return new AccessLimitAspect();
}
}
1.5 示例
@GetMapping("/limit")
@AccessLimit(seconds = 5, maxCount = 3, msg = "您操作频率太过频繁,稍后再试")
public ResultVO AccessLimit() {
return new ResultVO();
}
2. 防重复提交
2.1 防重复提交的概念
防止用户在短时间内多次提交相同请求,避免多次执行同一操作。常见的应用场景包括防止按钮重复点击导致的数据重复提交。
2.2 自定义注解 @RepeatSubmit
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
enum Type { PARAM, TOKEN }
Type limitType() default Type.PARAM;
long lockTime() default 5;
String serviceId() default "";
}
limitType
: 防重提交的方式(基于参数或令牌)。lockTime
: 防重提交的时间窗口,单位秒。serviceId
: 用于标识不同的业务逻辑。
2.3 利用AOP实现防重
@Aspect
@Slf4j
public class RepeatSubmitAspect {
@Resource
private RedissonClient redissonClient;
@Resource
private RedisRepository redisRepository;
@Around("@annotation(repeatSubmit)")
public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String serviceId = repeatSubmit.serviceId();
boolean res = false;
// 基于方法参数的防重提交
if (repeatSubmit.limitType() == RepeatSubmit.Type.PARAM) {
String ipAddr = request.getRemoteAddr();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
String key = serviceId + ":repeat_submit:" + AddrUtil.MD5(ipAddr + method.getName());
RLock lock = redissonClient.getLock(key);
res = lock.tryLock(0, repeatSubmit.lockTime(), TimeUnit.SECONDS);
} else {
// 基于Token的防重提交
String requestToken = request.getHeader("request-token");
String key = serviceId + ":submit_token:" + requestToken;
res = redisRepository.del(key);
}
if (!res) {
log.error("请求重复提交");
return null;
}
return joinPoint.proceed();
}
}
2.4 注册防重切面
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RepeatAutoConfiguration {
@Bean
public RepeatSubmitAspect repeatSubmitAspect() {
return new RepeatSubmitAspect();
}
}
2.5 示例
@PostMapping("/users/save")
@RepeatSubmit(serviceId = "saveUser", limitType = RepeatSubmit.Type.TOKEN, lockTime = 5)
public ResponseEntity<String> saveUser(@RequestBody User user) {
userService.save(user);
return ResponseEntity.ok("用户保存成功");
}
3. 接口防抖
3.1 防抖的概念
防抖是一种减少高频触发的机制,在一定时间内,连续的多次操作只会触发一次。后端防抖主要用于避免短时间内的重复请求。
3.2 自定义注解 @AntiShake
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AntiShake {
long value() default 1000L; // 默认1秒内不允许重复请求
}
3.3 利用AOP实现防抖
@Aspect
@Component
public class AntiShakeAspect {
private ThreadLocal<Long> lastInvokeTime = new ThreadLocal<>();
@Around("@annotation(antiShake)")
public Object aroundAdvice(ProceedingJoinPoint joinPoint, AntiShake antiShake) throws Throwable {
long currentTime = System.currentTimeMillis();
long lastTime = lastInvokeTime.get() != null ? lastInvokeTime.get() : 0;
if (currentTime - lastTime < antiShake.value()) {
return null; // 请求过于频繁,拒绝处理
}
lastInvokeTime.set(currentTime);
return joinPoint.proceed();
}
}
3.4 注册防抖切面
@Configuration(proxyBeanMethods = false)
public class AntiShakeAutoConfiguration {
@Bean
public AntiShakeAspect antiShakeAspect() {
return new AntiShakeAspect();
}
}
3.5 示例
@Service
public class SomeService {
@AntiShake(value = 2000)
public String someMethodThatNeedsToBeDebounced(String param) {
return "Result";
}
}
通过以上代码,我们实现了接口的限流、防重复提交和防抖,能够有效提高系统的稳定性和安全性。
评论区