首页 > 其他分享 >简单接口限流能力实现

简单接口限流能力实现

时间:2023-05-24 16:01:01浏览次数:49  
标签:OperationRateLimitAspect String 接口 annotation 限流 private 简单 import public

使用AOP注解方式在controller接口上实现的请求接口限流

一:核心逻辑


package com.simple.common.aop;

import com.simple.common.model.ErrorCode;
import com.simple.common.model.OperationRateLimit;
import com.simple.common.model.ServiceException;
import com.simple.common.model.OperationRateLimit.VoidOperatorKeyGetter;
import com.simple.common.util.JsonUtil;
import com.google.common.base.Joiner;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.springframework.aop.support.AopUtils;

public class OperationRateLimitAspect { private static final int CONCURRENCY_LEVEL = 8; private static final Joiner joiner = Joiner.on("|").useForNull(""); private final Logger errorLogger; private final OperationRateLimitAspect.OperatorKeyGetterRouter router; private final OperationRateLimitAspect.KeyValueStore keyValueStore; private final OperationRateLimitAspect.CacheManager cacheManager = new OperationRateLimitAspect.CacheManager(); private final AtomicBoolean enableLocalCache = new AtomicBoolean(true); public OperationRateLimitAspect(Logger errorLogger, OperationRateLimitAspect.OperatorKeyGetterRouter router, OperationRateLimitAspect.KeyValueStore keyValueStore) { Objects.requireNonNull(errorLogger, "error logger can not be null"); Objects.requireNonNull(router, "operator key getter router can not be null"); Objects.requireNonNull(keyValueStore, "key-value store can not be null"); this.errorLogger = errorLogger; this.router = router; this.keyValueStore = keyValueStore; } public Object operationRateLimit(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature)joinPoint.getSignature(); Class<?> targetClass = AopUtils.getTargetClass(joinPoint.getTarget()); Method method = AopUtils.getMostSpecificMethod(signature.getMethod(), joinPoint.getTarget().getClass()); String className = targetClass.getName(); String methodName = method.getName(); OperationRateLimit annotation = (OperationRateLimit)method.getAnnotation(OperationRateLimit.class); long coolDownMillis; if (annotation != null && (coolDownMillis = annotation.coolDownMillis()) > 0L) { String operatorKey = this.getOperatorKey(joinPoint, annotation, className, methodName); String operationKey = joiner.join(operatorKey, className, new Object[]{methodName}); boolean enableLocalCache = this.enableLocalCache.get(); Cache<String, Long> localCache = null; if (enableLocalCache) { localCache = this.cacheManager.getCache(coolDownMillis); Long lastOPTime = (Long)localCache.getIfPresent(operationKey); if (lastOPTime != null) { return this.reject(className, methodName, operatorKey); } } if (this.keyValueStore.exists(operationKey)) { return this.reject(className, methodName, operatorKey); } else { long now = System.currentTimeMillis(); if (enableLocalCache) { localCache.put(operationKey, now); } boolean writeSucceed = this.keyValueStore.setIfAbsent(operationKey, String.valueOf(now), coolDownMillis); if (!writeSucceed) { return this.reject(className, methodName, operatorKey); } else { Object result = this.invoke(joinPoint); if (annotation.resetCoolDownAfterInvoked()) { if (enableLocalCache) { localCache.invalidate(operationKey); } try { this.keyValueStore.delete(operationKey); } catch (Exception var19) { this.errorLogger.error(joiner.join("deleteOperationKeyFailedButBizSucceed", "bizResult=" + (result == null ? "null" : JsonUtil.serialize(result)), new Object[0])); throw var19; } } return result; } } } else { return this.invoke(joinPoint); } } private String getOperatorKey(ProceedingJoinPoint joinPoint, OperationRateLimit annotation, String className, String methodName) { Class<? extends OperationRateLimitAspect.OperatorKeyGetter> getterClass = annotation.operatorKeyGetter(); OperationRateLimitAspect.OperatorKeyGetter getter = this.router.route(joinPoint, getterClass == VoidOperatorKeyGetter.class ? null : getterClass); String operatorKey = getter.getOperatorKey(joinPoint); if (StringUtils.isBlank(operatorKey)) { this.printErrorLog("blankOperatorKey", className, methodName, operatorKey); throw new ServiceException(ErrorCode.COMMON_ERROR, "get operator key failed, blank operator key"); } else { return operatorKey; } } private Object invoke(ProceedingJoinPoint joinPoint) throws Throwable { return joinPoint.proceed(); } public void enableLocalCache() { this.enableLocalCache.set(true); } public void disableLocalCache() { this.enableLocalCache.set(false); } private void printErrorLog(String desc, String className, String methodName, String operatorKey) { this.errorLogger.error(joiner.join(desc, "method=" + className + "#" + methodName, new Object[]{"operatorKey=" + operatorKey})); } private Object reject(String className, String methodName, String operatorKey) { this.printErrorLog("operationLimit", className, methodName, operatorKey); throw new ServiceException(ErrorCode.COMMON_OPERATION_LIMIT, "操作过于频繁,请稍后重试"); } public interface KeyValueStore { boolean exists(String var1); boolean setIfAbsent(String var1, String var2, long var3); boolean delete(String var1); } public interface OperatorKeyGetter { String getOperatorKey(ProceedingJoinPoint var1); } public interface OperatorKeyGetterRouter { OperationRateLimitAspect.OperatorKeyGetter route(ProceedingJoinPoint var1, Class<? extends OperationRateLimitAspect.OperatorKeyGetter> var2); } private class CacheManager { private final Map<Long, Cache<String, Long>> cacheMap; private CacheManager() { this.cacheMap = new ConcurrentHashMap(64, 0.75F, 8); } public Cache<String, Long> getCache(long coolDownMillis) { return (Cache)this.cacheMap.computeIfAbsent(coolDownMillis, (cd) -> { int size; if ((size = this.cacheMap.size()) > 128) { OperationRateLimitAspect.this.errorLogger.warn(OperationRateLimitAspect.joiner.join("tooManyOperationRateLimitCache", "currentCacheCount=" + size, new Object[0])); } return CacheBuilder.newBuilder().concurrencyLevel(8).initialCapacity(64).expireAfterWrite(cd, TimeUnit.MILLISECONDS).build(); }); } } }

二、注解对象

package com.simple.common.model;

import com.simple.common.aop.OperationRateLimitAspect.OperatorKeyGetter;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.aspectj.lang.ProceedingJoinPoint;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface OperationRateLimit {
    long coolDownMillis() default 1000L;

    boolean resetCoolDownAfterInvoked() default false;

    Class<? extends OperatorKeyGetter> operatorKeyGetter() default OperationRateLimit.VoidOperatorKeyGetter.class;

    public static final class VoidOperatorKeyGetter implements OperatorKeyGetter {
        public VoidOperatorKeyGetter() {
        }

        public String getOperatorKey(ProceedingJoinPoint joinPoint) {
            throw new UnsupportedOperationException();
        }
    }
}

三、拦截器

package com.simple.work.web.aop.aspect;

import com.simple.common.aop.OperationRateLimitAspect;
import com.simple.work.biz.middleware.redis.RedisService;
import com.simple.work.common.constant.Loggers;
import com.simple.work.web.context.WebContextService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.validation.constraints.NotBlank;
import java.util.concurrent.TimeUnit;

/**
 * zz
 * 2021/4/13
 */
@Aspect
@Order(9)
@Component
public class OperationRateAspect {

    private static final Logger errorLogger = Loggers.ERROR_LOGGER;
    private OperationRateLimitAspect aspect;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private WebContextService webContextService;

    @Autowired
    private RedisService redisService;

    @PostConstruct
    public void init() {
        final OperationRateLimitAspect.OperatorKeyGetter defaultOperatorKeyGetter =
                joinPoint -> webContextService.getAuthUserKey();

        final OperationRateLimitAspect.OperatorKeyGetterRouter router =
                (joinPoint, getterClass) -> getterClass == null ? defaultOperatorKeyGetter : applicationContext.getBean(getterClass);

        final OperationRateLimitAspect.KeyValueStore keyValueStore = new OperationRateLimitAspect.KeyValueStore() {
            @Override
            public boolean exists(@NotBlank String key) {
                return redisService.exists(key);
            }

            @Override
            public boolean setIfAbsent(@NotBlank String key, @NotBlank String value, long expireMillis) {
                Boolean result = redisService.setIfAbsent(key, value, expireMillis, TimeUnit.MILLISECONDS);
                return Boolean.TRUE.equals(result);
            }

            @Override
            public boolean delete(@NotBlank String key) {
                Boolean result = redisService.delete(key);
                return Boolean.TRUE.equals(result);
            }
        };

        aspect = new OperationRateLimitAspect(errorLogger, router, keyValueStore);
        aspect.enableLocalCache();
    }

    @Around("@annotation(com.flyfish.common.model.OperationRateLimit)")
    public Object operationRateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
        return aspect.operationRateLimit(joinPoint);
    }
}

四、使用

    @ApiOperation("列表查询接口")
    @OperationRateLimit(coolDownMillis = 30000, resetCoolDownAfterInvoked = true)
    @PostMapping("/list")
    public WebResult<Void> list(@RequestBody @Validated @ApiParam("查询参数")
                                                                  BookingExportSearchRequest request) {

return null;
    }

 

标签:OperationRateLimitAspect,String,接口,annotation,限流,private,简单,import,public
From: https://www.cnblogs.com/zhuzhen/p/17428571.html

相关文章

  • 通过商品API接口获取到数据后的分析和应用
    一、如果你想要分析商品API接口获取到的数据,可以按照如下的步骤进行:了解API接口返回值的格式,如JSON格式、XML格式、CSV格式等,选择适合你的数据分析方式。使用API请求工具(如Postman、curl)调用API接口,并接收HTTP响应。一般来说,API接口的访问需要使用APIKey等认证机制,需要提前......
  • 「API 接口获取方法」
    在创建一个应用程序的过程中,获取数据是非常关键的一步。而通过API接口获取数据通常是最好的方式之一。那么,如何通过关键字获取API接口呢?以下是一些步骤:1.确定你需要获取的数据类型首先,你需要确定你需要获取的数据类型。是文本?图片?还是视频?不同的数据类型可能需要不同的API接口来进......
  • 为什么要使用API接口,他能带来哪些便利
    API接口是程序员进行应用程序开发时不可或缺的工具之一。以下是使用API接口的一些优点:数据交换:使用API接口可以使不同的应用程序、网站或服务之间交换数据更为便捷,减少人工输入数据的时间和风险。自动化处理:使用API接口可以实现自动化的数据交换和处理,减少人工干预,提高数......
  • 剑指 Offer II 018(Java). 有效的回文(简单)
    题目:给定一个字符串s,验证s 是否是 回文串 ,只考虑字母和数字字符,可以忽略字母的大小写。本题中,将空字符串定义为有效的 回文串 。 示例1:输入:s="Aman,aplan,acanal:Panama"输出:true解释:"amanaplanacanalpanama"是回文串示例2:输入:s="raceacar"......
  • 上个接口日志公共的类
     CLASSzcl_afl_utilitiesDEFINITIONPUBLICFINALCREATEPUBLIC.PUBLICSECTION.CLASS-METHODSre_processIMPORTING!guidTYPEguid.CLASS-METHODSis_prdRETURNINGVALUE(result)TYPEabap_bool.CLASS-M......
  • 简单/复杂整数划分
    这个题我做过类似的题目,没错,又是记忆化搜索,但也不完全是,还是用搜索就可以过,本质也是动态规划基本上只要会简单的,就会做复杂的,只不过是步骤麻烦点#include<bits/stdc++.h>usingnamespacestd;intn,a[1000010]={1},res=1;voiddfs(intt,ints)//现在的数总和是t,用......
  • SpringBoot中使用枚举类、switch、常量类(声明并初始化map)实现类策略者模式,接口返回
    场景SpringBoot中策略模式+工厂模式业务实例(接口传参-枚举类查询策略映射关系-执行不同策略)规避大量if-else:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/130503707SpringBoot+@Validate+全局异常拦截实现自定义规则参数校验(校验get请求参数不能为空且在指定......
  • matlab中通过ode函数求解常微分方程附加简单的钟摆模型
    ✅作者简介:热爱科研的算法开发者,Python、Matlab项目可交流、沟通、学习。......
  • 抽象类和接口
    抽象类在类之前加一个abstract抽象类是单继承,是一种约束,不能实现方法。不能new这个对象抽象类中可以有抽象方法(加abstract关键词)也可由普通方法接口实现类可实现多个接口接口种方法必须要在实现类中实现......
  • 六、流水线简单使用
    一、简单例子Jenkins流水线是通过Jenkinsfile配置文件配置的。Jenkinsfile文遵循Groovy风格的规范。项目从开发到部署一般都经历,构建,测试,部署三个阶段。现在用流水线模拟这个过程(使用上个例子的my_pipeline项目):   将Jenkinsfile改成:pipeline{agentanystag......