From b3fbe9f9e49faf5321464ff9e9024e7cae5d9664 Mon Sep 17 00:00:00 2001 From: amos wong Date: Sat, 10 May 2025 21:16:46 +0800 Subject: [PATCH] master --- .gitignore | 33 ++++++ pom.xml | 100 ++++++++++++++++++ readme.md | 59 +++++++++++ .../ratelimit/annotation/EnableRateLimit.java | 18 ++++ .../ratelimit/annotation/RateLimit.java | 46 ++++++++ .../ratelimit/aspect/RateLimitAspect.java | 71 +++++++++++++ .../configure/LimiterConfiguration.java | 49 +++++++++ .../exception/RateLimitException.java | 14 +++ .../ratelimit/handler/IRateLimitHandler.java | 20 ++++ .../ratelimit/handler/RateLimitKeyParser.java | 76 +++++++++++++ .../handler/RedissonRateLimitHandler.java | 54 ++++++++++ .../ratelimit/metadata/MethodMetadata.java | 41 +++++++ .../metadata/RateLimitMethodMetaData.java | 63 +++++++++++ .../strategy/DefaultKeyGenerateStrategy.java | 25 +++++ .../strategy/IKeyGenerateStrategy.java | 38 +++++++ .../strategy/IpKeyGenerateStrategy.java | 23 ++++ .../deepinnet/ratelimit/util/MethodUtils.java | 32 ++++++ .../ratelimit/util/RequestUtils.java | 90 ++++++++++++++++ 18 files changed, 852 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 readme.md create mode 100644 src/main/java/com/deepinnet/ratelimit/annotation/EnableRateLimit.java create mode 100644 src/main/java/com/deepinnet/ratelimit/annotation/RateLimit.java create mode 100644 src/main/java/com/deepinnet/ratelimit/aspect/RateLimitAspect.java create mode 100644 src/main/java/com/deepinnet/ratelimit/configure/LimiterConfiguration.java create mode 100644 src/main/java/com/deepinnet/ratelimit/exception/RateLimitException.java create mode 100644 src/main/java/com/deepinnet/ratelimit/handler/IRateLimitHandler.java create mode 100644 src/main/java/com/deepinnet/ratelimit/handler/RateLimitKeyParser.java create mode 100644 src/main/java/com/deepinnet/ratelimit/handler/RedissonRateLimitHandler.java create mode 100644 src/main/java/com/deepinnet/ratelimit/metadata/MethodMetadata.java create mode 100644 src/main/java/com/deepinnet/ratelimit/metadata/RateLimitMethodMetaData.java create mode 100644 src/main/java/com/deepinnet/ratelimit/strategy/DefaultKeyGenerateStrategy.java create mode 100644 src/main/java/com/deepinnet/ratelimit/strategy/IKeyGenerateStrategy.java create mode 100644 src/main/java/com/deepinnet/ratelimit/strategy/IpKeyGenerateStrategy.java create mode 100644 src/main/java/com/deepinnet/ratelimit/util/MethodUtils.java create mode 100644 src/main/java/com/deepinnet/ratelimit/util/RequestUtils.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..457a323 --- /dev/null +++ b/pom.xml @@ -0,0 +1,100 @@ + + + 4.0.0 + + com.deepinnet + rate-limit-starter + 0.0.1-SNAPSHOT + rate-limit + rate-limit + + + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-aop + 2.7.14 + provided + + + + org.springframework + spring-web + 5.3.29 + provided + + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + + org.redisson + redisson-spring-boot-starter + 3.18.0 + provided + + + + cn.hutool + hutool-core + 5.8.11 + provided + + + + org.projectlombok + lombok + true + 1.18.28 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 1.8 + 1.8 + UTF-8 + + + + + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + + + + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..c0f1f3a --- /dev/null +++ b/readme.md @@ -0,0 +1,59 @@ +## 简介 + +rate-limit-starter是基于redisson框架封装的限流框架,提供注解式@RateLimit限流。 + +全部特性: + +- Spring Boot支持 +- @RateLimit注解限流 +- 多限流策略组合 +- 可扩展限流方式,自己扩展IRateLimitHandler进行实现,默认是redisson + +## 使用说明 + +**引入maven依赖** + +注:需要引入redisson依赖 以及 hutool + +```xml + + + com.deepinnet + rate-limit-starter + 0.0.1-SNAPSHOT + +``` + +**开启@EnableRateLimit** + +```java + +@SpringBootApplication +@EnableRateLimit +public class Application { + + public static void main(String[] args) { + SpringApplication.run(PropertyTestApplication.class, args); + } + +} +``` + +**需要限流的方法标记注解@RateLimit** + +```java + @RateLimit( + // 唯一标示,支持SpEL表达式(可无),#name 为获取当前访问参数 name 内容 +// key = "#code", + // 限定阈值,时间间隔 interval 范围内超过该数量会触发锁 + count = 3, + // 限制间隔时长(可无,默认 3 分钟)例如 5s 五秒,6m 六分钟,7h 七小时,8d 八天 + interval = "6m", + // 策略(可无) ip 为获取当前访问IP地址(内置策略) + strategy = {IpKeyGenerateStrategy.TYPE} +) +public void limit(){ + + } +``` + diff --git a/src/main/java/com/deepinnet/ratelimit/annotation/EnableRateLimit.java b/src/main/java/com/deepinnet/ratelimit/annotation/EnableRateLimit.java new file mode 100644 index 0000000..e508e91 --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/annotation/EnableRateLimit.java @@ -0,0 +1,18 @@ +package com.deepinnet.ratelimit.annotation; + +import com.deepinnet.ratelimit.configure.LimiterConfiguration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 速率限制注解类 + */ +@Target(value = {ElementType.TYPE}) +@Retention(value = RetentionPolicy.RUNTIME) +@Import(LimiterConfiguration.class) +public @interface EnableRateLimit { +} diff --git a/src/main/java/com/deepinnet/ratelimit/annotation/RateLimit.java b/src/main/java/com/deepinnet/ratelimit/annotation/RateLimit.java new file mode 100644 index 0000000..0e8b819 --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/annotation/RateLimit.java @@ -0,0 +1,46 @@ +package com.deepinnet.ratelimit.annotation; + + +import com.deepinnet.ratelimit.strategy.DefaultKeyGenerateStrategy; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 速率限制注解类 + */ +@Target(value = {ElementType.METHOD}) +@Retention(value = RetentionPolicy.RUNTIME) +public @interface RateLimit { + + /** + * 唯一标示,支持SpEL表达式 + */ + String key() default ""; + + /** + * 限定阈值 + *

+ * 时间间隔 interval 范围内超过该数量会触发锁 + */ + long count(); + + /** + * 时间间隔,默认 3 分钟 + *

+ * 例如 5s 五秒,6m 六分钟,7h 七小时,8d 八天 + */ + String interval() default "3m"; + + /** + * 限制策略 + */ + String[] strategy() default {DefaultKeyGenerateStrategy.TYPE}; + + /** + * 提示消息,非必须 + */ + String message() default "请勿频繁操作"; +} diff --git a/src/main/java/com/deepinnet/ratelimit/aspect/RateLimitAspect.java b/src/main/java/com/deepinnet/ratelimit/aspect/RateLimitAspect.java new file mode 100644 index 0000000..0b514c3 --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/aspect/RateLimitAspect.java @@ -0,0 +1,71 @@ +package com.deepinnet.ratelimit.aspect; + +import com.deepinnet.ratelimit.annotation.RateLimit; +import com.deepinnet.ratelimit.exception.RateLimitException; +import com.deepinnet.ratelimit.handler.IRateLimitHandler; +import com.deepinnet.ratelimit.metadata.MethodMetadata; +import com.deepinnet.ratelimit.metadata.RateLimitMethodMetaData; +import com.deepinnet.ratelimit.util.MethodUtils; +import lombok.AllArgsConstructor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 速率限制拦截切面处理类 + */ +@Aspect +@AllArgsConstructor +public class RateLimitAspect { + /** + * 缓存方法上的源注解信息。减少反射的开销 + */ + private static final Map RATE_LIMIT_MAP = new ConcurrentHashMap<>(); + private final IRateLimitHandler rateLimitHandler; + + /** + * 限流注解切面 + * + * @param pjp {@link ProceedingJoinPoint} + * @return {@link Object} + * @throws Throwable 限流异常 + */ + @Around("@annotation(com.deepinnet.ratelimit.annotation.RateLimit)") + public Object interceptor(ProceedingJoinPoint pjp) throws Throwable { + MethodSignature signature = (MethodSignature) pjp.getSignature(); + Method method = signature.getMethod(); + final String classMethodName = MethodUtils.getClassMethodName(method); + final RateLimit rateLimit = this.getRateLimit(method, classMethodName); + MethodMetadata methodMetadata = this.buildMethodMetadata(pjp); + if (rateLimitHandler.proceed(methodMetadata)) { + return pjp.proceed(); + } else { + throw new RateLimitException(StringUtils.hasLength(rateLimit.message()) ? rateLimit.message() : "current limiting rule triggered"); + } + } + + /** + * 获取执行速率限制注解,缓存反射信息 + * + * @param method 执行方法 + * @param classMethodName 执行类方法名 + * @return 方法对应的注解源信息,如果有,直接返回,如果无,获取放入缓存。 + */ + public RateLimit getRateLimit(Method method, String classMethodName) { + return RATE_LIMIT_MAP.computeIfAbsent(classMethodName, k -> method.getAnnotation(RateLimit.class)); + } + + private MethodMetadata buildMethodMetadata(ProceedingJoinPoint joinPoint) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + String classMethodName = MethodUtils.getClassMethodName(method); + RateLimit rateLimit = this.getRateLimit(method, classMethodName); + return new RateLimitMethodMetaData(method, joinPoint::getArgs, rateLimit); + } +} diff --git a/src/main/java/com/deepinnet/ratelimit/configure/LimiterConfiguration.java b/src/main/java/com/deepinnet/ratelimit/configure/LimiterConfiguration.java new file mode 100644 index 0000000..fc599f6 --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/configure/LimiterConfiguration.java @@ -0,0 +1,49 @@ +package com.deepinnet.ratelimit.configure; + +import com.deepinnet.ratelimit.aspect.RateLimitAspect; +import com.deepinnet.ratelimit.exception.RateLimitException; +import com.deepinnet.ratelimit.handler.IRateLimitHandler; +import com.deepinnet.ratelimit.handler.RateLimitKeyParser; +import com.deepinnet.ratelimit.handler.RedissonRateLimitHandler; +import com.deepinnet.ratelimit.strategy.IKeyGenerateStrategy; +import com.deepinnet.ratelimit.strategy.IpKeyGenerateStrategy; +import org.redisson.api.RedissonClient; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; + +import java.util.List; + +/** + * 速率限制配置 + */ +@Order(Ordered.LOWEST_PRECEDENCE) +public class LimiterConfiguration { + + @Bean + public IpKeyGenerateStrategy ipRateLimitStrategy() { + return new IpKeyGenerateStrategy(); + } + + @Bean + public RateLimitKeyParser rateLimitKeyParser(List rateLimitStrategyList) { + return new RateLimitKeyParser(rateLimitStrategyList); + } + + @Bean + @ConditionalOnMissingBean + public IRateLimitHandler rateLimitHandler(RedissonClient redissonClient, + RateLimitKeyParser rateLimitKeyParser) { + if (redissonClient == null) { + throw new RateLimitException("RedissonClient is a must!!!"); + } + return new RedissonRateLimitHandler(redissonClient, rateLimitKeyParser); + } + + @Bean + public RateLimitAspect rateLimitAspect(IRateLimitHandler rateLimitHandler) { + return new RateLimitAspect(rateLimitHandler); + } + +} diff --git a/src/main/java/com/deepinnet/ratelimit/exception/RateLimitException.java b/src/main/java/com/deepinnet/ratelimit/exception/RateLimitException.java new file mode 100644 index 0000000..ee4cbb6 --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/exception/RateLimitException.java @@ -0,0 +1,14 @@ + +package com.deepinnet.ratelimit.exception; + +/** + * 速率限制异常 + */ +public class RateLimitException extends RuntimeException { + + private static final long serialVersionUID = 4856751949960004774L; + + public RateLimitException(String message) { + super(message); + } +} diff --git a/src/main/java/com/deepinnet/ratelimit/handler/IRateLimitHandler.java b/src/main/java/com/deepinnet/ratelimit/handler/IRateLimitHandler.java new file mode 100644 index 0000000..e6ce3e3 --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/handler/IRateLimitHandler.java @@ -0,0 +1,20 @@ + +package com.deepinnet.ratelimit.handler; + + +import com.deepinnet.ratelimit.metadata.MethodMetadata; +import com.deepinnet.ratelimit.metadata.RateLimitMethodMetaData; + +/** + * 速率限制处理器接口 + */ +public interface IRateLimitHandler { + + /** + * 继续执行 + * + * @param methodMetadata {@link RateLimitMethodMetaData} + * @return true 继续执行 false 限流不执行 + */ + boolean proceed(MethodMetadata methodMetadata); +} diff --git a/src/main/java/com/deepinnet/ratelimit/handler/RateLimitKeyParser.java b/src/main/java/com/deepinnet/ratelimit/handler/RateLimitKeyParser.java new file mode 100644 index 0000000..a9f0cff --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/handler/RateLimitKeyParser.java @@ -0,0 +1,76 @@ +package com.deepinnet.ratelimit.handler; + +import cn.hutool.core.util.ArrayUtil; +import com.deepinnet.ratelimit.metadata.MethodMetadata; +import com.deepinnet.ratelimit.strategy.DefaultKeyGenerateStrategy; +import com.deepinnet.ratelimit.strategy.IKeyGenerateStrategy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.expression.MethodBasedEvaluationContext; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; + +/** + * 速率限制唯一标示 key 解析器 + */ +@Slf4j +public class RateLimitKeyParser { + private final ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer(); + private final ExpressionParser parser = new SpelExpressionParser(); + private final List keyGenerateStrategyList; + private final IKeyGenerateStrategy defaultKeyGenerateStrategy; + + public RateLimitKeyParser(List keyGenerateStrategyList) { + this.keyGenerateStrategyList = keyGenerateStrategyList; + // 默认key生成策略 + defaultKeyGenerateStrategy = new DefaultKeyGenerateStrategy(); + } + + /** + * 构建唯一标示 KEY + * + * @param useDefaultStrategy 是否使用默认的key策略 + */ + public String buildKey(MethodMetadata methodMetadata, String spELKey, String[] strategy, boolean useDefaultStrategy) { + StringBuilder key = new StringBuilder(); + + // SpEL Key 解析 + String parseKey = Optional.ofNullable(spELKey) + .filter(StringUtils::hasLength) + .map(str -> { + Method method = methodMetadata.getMethod(); + Object[] args = methodMetadata.getArgs(); + return this.parser(spELKey, method, args); + }).orElse(""); + + if (useDefaultStrategy) { + key.append(defaultKeyGenerateStrategy.getKey(methodMetadata, parseKey)); + } + + // 组装自定义策略 + keyGenerateStrategyList.stream() + .filter(rateLimitStrategy -> ArrayUtil.contains(strategy, rateLimitStrategy.getType())) + .forEach(rateLimitStrategy -> { + String strategyKey = rateLimitStrategy.getKey(methodMetadata, parseKey); + if (!key.toString().contains(strategyKey)) { + key.append(":").append(strategyKey); + } + }); + return key.toString(); + } + + + public String parser(String key, Method method, Object[] args) { + EvaluationContext context = new MethodBasedEvaluationContext(null, method, args, nameDiscoverer); + Object objKey = parser.parseExpression(key).getValue(context); + return ObjectUtils.nullSafeToString(objKey); + } +} diff --git a/src/main/java/com/deepinnet/ratelimit/handler/RedissonRateLimitHandler.java b/src/main/java/com/deepinnet/ratelimit/handler/RedissonRateLimitHandler.java new file mode 100644 index 0000000..89191d5 --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/handler/RedissonRateLimitHandler.java @@ -0,0 +1,54 @@ +package com.deepinnet.ratelimit.handler; + +import com.deepinnet.ratelimit.annotation.RateLimit; +import com.deepinnet.ratelimit.metadata.MethodMetadata; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.*; +import org.springframework.boot.convert.DurationStyle; + +import java.util.Objects; + +/** + * Redisson 速率限制处理器 + */ +@Slf4j +@AllArgsConstructor +public class RedissonRateLimitHandler implements IRateLimitHandler { + private final RedissonClient redissonClient; + private final RateLimitKeyParser rateLimitKeyParser; + + @Override + public boolean proceed(MethodMetadata methodMetadata) { + RateLimit rateLimit = methodMetadata.getAnnotation(); + // 间隔时间解析为毫秒 + long interval = DurationStyle.detectAndParse(rateLimit.interval()).toMillis(); + final String key = rateLimitKeyParser.buildKey(methodMetadata, rateLimit.key(), rateLimit.strategy(), true); + if (log.isDebugEnabled()) { + log.debug("rate.limit.key = {}", key); + } + RRateLimiter rateLimiter = redissonClient.getRateLimiter(key); + if (rateLimiter.isExists()) { + //判断限流器配置是否跟注解上一致 不一致需要重新生成配置 + if (change(rateLimit, interval, rateLimiter.getConfig())) { + rateLimiter.delete(); + rateLimiter.trySetRate(RateType.OVERALL, rateLimit.count(), interval, RateIntervalUnit.MILLISECONDS); + } + } else { + // 如果限流器不存在,就创建一个RRateLimiter限流器 + rateLimiter.trySetRate(RateType.OVERALL, rateLimit.count(), interval, RateIntervalUnit.MILLISECONDS); + } + // 是否触发限流 + return rateLimiter.tryAcquire(); + } + + public boolean change(RateLimit rateLimit, Long interval, RateLimiterConfig config) { + if (!Objects.equals(config.getRate(), rateLimit.count())) { + return true; + } + if (!Objects.equals(config.getRateInterval(), interval)) { + return true; + } + return false; + } +} diff --git a/src/main/java/com/deepinnet/ratelimit/metadata/MethodMetadata.java b/src/main/java/com/deepinnet/ratelimit/metadata/MethodMetadata.java new file mode 100644 index 0000000..8006e0c --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/metadata/MethodMetadata.java @@ -0,0 +1,41 @@ +package com.deepinnet.ratelimit.metadata; + + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +/** + * 运行时的信息 + */ +public interface MethodMetadata { + + /** + * 获得当前运行时方法名 + * {@link Method} + * + * @return 运行时方法名 + */ + String getClassMethodName(); + + /** + * 获得当前运行时方法名 + * + * @return 运行时方法 + */ + Method getMethod(); + + /** + * 获得运行时方法的参数 + * + * @return 参数数组 + */ + Object[] getArgs(); + + /** + * 获得当前运行的参数 + * + * @param 当前运行的注解泛型 + * @return 获取到的泛型的值 + */ + T getAnnotation(); +} diff --git a/src/main/java/com/deepinnet/ratelimit/metadata/RateLimitMethodMetaData.java b/src/main/java/com/deepinnet/ratelimit/metadata/RateLimitMethodMetaData.java new file mode 100644 index 0000000..c6867f4 --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/metadata/RateLimitMethodMetaData.java @@ -0,0 +1,63 @@ +package com.deepinnet.ratelimit.metadata; + +import com.deepinnet.ratelimit.annotation.RateLimit; +import com.deepinnet.ratelimit.util.MethodUtils; +import org.springframework.util.Assert; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.function.Supplier; + +/** + * 当前速率限制的运行时的信息 + */ +public class RateLimitMethodMetaData implements MethodMetadata { + /** + * 方法 + */ + private final Method method; + /** + * 当前运行时方法中的参数 + */ + private final Supplier argsSupplier; + /** + * 当前方法的全称 + * 使用{@link MethodUtils#getClassMethodName(Method)}方法获得 + */ + private final String classMethodName; + /** + * 速率限制所用到的注解 + */ + private final RateLimit rateLimit; + + public RateLimitMethodMetaData(Method method, Supplier argsSupplier, RateLimit rateLimit) { + Assert.notNull(method, "'method' cannot be null"); + Assert.notNull(argsSupplier, "'argsSupplier' cannot be null"); + Assert.notNull(rateLimit, "'rateLimit' cannot be null"); + this.method = method; + this.argsSupplier = argsSupplier; + this.rateLimit = rateLimit; + this.classMethodName = MethodUtils.getClassMethodName(method); + } + + @Override + public String getClassMethodName() { + return classMethodName; + } + + @Override + public Method getMethod() { + return method; + } + + @Override + public Object[] getArgs() { + return argsSupplier.get(); + } + + @SuppressWarnings("unchecked") + @Override + public T getAnnotation() { + return (T) rateLimit; + } +} diff --git a/src/main/java/com/deepinnet/ratelimit/strategy/DefaultKeyGenerateStrategy.java b/src/main/java/com/deepinnet/ratelimit/strategy/DefaultKeyGenerateStrategy.java new file mode 100644 index 0000000..c1eb7fb --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/strategy/DefaultKeyGenerateStrategy.java @@ -0,0 +1,25 @@ +package com.deepinnet.ratelimit.strategy; + +import com.deepinnet.ratelimit.metadata.MethodMetadata; +import org.springframework.util.StringUtils; + +/** + * 默认key策略 + */ +public class DefaultKeyGenerateStrategy implements IKeyGenerateStrategy { + public final static String TYPE = "default"; + + @Override + public String getType() { + return TYPE; + } + + @Override + public String getKey(MethodMetadata methodMetadata, String parseKey) { + String result = methodMetadata.getClassMethodName(); + if (StringUtils.hasLength(parseKey)) { + result += ":" + parseKey; + } + return result; + } +} diff --git a/src/main/java/com/deepinnet/ratelimit/strategy/IKeyGenerateStrategy.java b/src/main/java/com/deepinnet/ratelimit/strategy/IKeyGenerateStrategy.java new file mode 100644 index 0000000..a527d1c --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/strategy/IKeyGenerateStrategy.java @@ -0,0 +1,38 @@ +package com.deepinnet.ratelimit.strategy; + + +import com.deepinnet.ratelimit.metadata.MethodMetadata; +import com.deepinnet.ratelimit.util.RequestUtils; + +import javax.servlet.http.HttpServletRequest; + +/** + * key生成策略接口 + */ +public interface IKeyGenerateStrategy { + + /** + * 策略类型 + * + * @return 限流策略类型 + */ + String getType(); + + /** + * 唯一标示 key + * + * @param methodMetadata {@link MethodMetadata} + * @param parseKey 解析spEL得到的Key + * @return 包装的key + */ + String getKey(MethodMetadata methodMetadata, String parseKey); + + /** + * 当前请求 + * + * @return 当前请求 + */ + default HttpServletRequest getRequest() { + return RequestUtils.getRequest(); + } +} diff --git a/src/main/java/com/deepinnet/ratelimit/strategy/IpKeyGenerateStrategy.java b/src/main/java/com/deepinnet/ratelimit/strategy/IpKeyGenerateStrategy.java new file mode 100644 index 0000000..6f2b876 --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/strategy/IpKeyGenerateStrategy.java @@ -0,0 +1,23 @@ +package com.deepinnet.ratelimit.strategy; + + +import com.deepinnet.ratelimit.metadata.MethodMetadata; +import com.deepinnet.ratelimit.util.RequestUtils; + +/** + * IP 速率限制策略 + */ +public class IpKeyGenerateStrategy implements IKeyGenerateStrategy { + public final static String TYPE = "ip"; + + @Override + public String getType() { + return TYPE; + } + + @Override + public String getKey(MethodMetadata methodMetadata, String parseKey) { + return RequestUtils.getIp(this.getRequest()); + } + +} diff --git a/src/main/java/com/deepinnet/ratelimit/util/MethodUtils.java b/src/main/java/com/deepinnet/ratelimit/util/MethodUtils.java new file mode 100644 index 0000000..a4c0068 --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/util/MethodUtils.java @@ -0,0 +1,32 @@ +package com.deepinnet.ratelimit.util; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +/** + * 反射方法相关工具类 + */ +public class MethodUtils { + + /** + * 获取执行类方法名 + * + * @param method 执行方法 + * @return 类名+方法名 + */ + public static String getClassMethodName(Method method) { + return String.format("%s.%s", method.getDeclaringClass().getName(), method.getName()); + } + + /** + * 获取方法指定注解信息 + * + * @param method {@link Method} + * @param annotationClass 注解类 + * @param 注解类 + * @return 指定注解信息 + */ + public static T getAnnotation(Method method, Class annotationClass) { + return method.getAnnotation(annotationClass); + } +} diff --git a/src/main/java/com/deepinnet/ratelimit/util/RequestUtils.java b/src/main/java/com/deepinnet/ratelimit/util/RequestUtils.java new file mode 100644 index 0000000..c79d88c --- /dev/null +++ b/src/main/java/com/deepinnet/ratelimit/util/RequestUtils.java @@ -0,0 +1,90 @@ +package com.deepinnet.ratelimit.util; + +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + + +public class RequestUtils { + + public final static String LOCAL_IP = "127.0.0.1"; + + public final static String LOCAL_ADDRESS = "0:0:0:0:0:0:0:1"; + + /** + * 当前 HttpServletRequest 请求 + * + * @return HttpServletRequest + */ + public static HttpServletRequest getRequest() { + return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + } + + private static final String[] HEADERS = { + "x-forwarded-for", + "Proxy-Client-IP", + "WL-Proxy-Client-IP", + "HTTP_X_FORWARDED_FOR", + "HTTP_X_FORWARDED", + "HTTP_X_CLUSTER_CLIENT_IP", + "HTTP_CLIENT_IP", + "HTTP_FORWARDED_FOR", + "HTTP_FORWARDED", + "HTTP_VIA", + "REMOTE_ADDR", + "X-Real-IP" + }; + + /** + * 获取 request 请求 IP 地址 + * + * @return IP 地址 + */ + public static String getIp(HttpServletRequest request) { + String ip = null; + for (String header : HEADERS) { + String currentIp = request.getHeader(header); + if (isNotUnknown(currentIp)) { + ip = currentIp; + break; + } + } + if (null == ip) { + ip = request.getRemoteAddr(); + } + if (null == ip) { + return ""; + } + if (LOCAL_ADDRESS.equals(ip)) { + return LOCAL_IP; + } + return getMultistageReverseProxyIp(ip); + } + + /** + * 从多级反向代理中获得第一个非unknown IP地址 + * + * @param ip 获得的IP地址 + * @return 第一个非unknown IP地址 + */ + private static String getMultistageReverseProxyIp(String ip) { + // 多级反向代理检测 + String delimiter = ","; + if (ip != null && ip.indexOf(delimiter) > 0) { + String[] ips = ip.trim().split(delimiter); + for (String subIp : ips) { + if (isNotUnknown(subIp)) { + ip = subIp; + break; + } + } + } + return ip; + } + + private static boolean isNotUnknown(String checkIp) { + return StringUtils.hasLength(checkIp) && !"unknown".equalsIgnoreCase(checkIp); + } +}