目 录CONTENT

文章目录

使用注解和AOP实现接口限流、防抖、防重提交

在等晚風吹
2024-10-22 / 0 评论 / 0 点赞 / 15 阅读 / 0 字 / 正在检测是否收录...

使用注解和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";
    }
}

通过以上代码,我们实现了接口的限流、防重复提交和防抖,能够有效提高系统的稳定性和安全性。

0

评论区