master
This commit is contained in:
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@@ -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/
|
||||
100
pom.xml
Normal file
100
pom.xml
Normal file
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.deepinnet</groupId>
|
||||
<artifactId>rate-limit-starter</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>rate-limit</name>
|
||||
<description>rate-limit</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<version>2.7.14</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>5.3.29</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
<version>3.18.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>5.8.11</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
<version>1.18.28</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.5.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- 要将源码放上去,需要加入这个插件 -->
|
||||
<plugin>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<configuration>
|
||||
<attach>true</attach>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- 发布 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>2.8.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
59
readme.md
Normal file
59
readme.md
Normal file
@@ -0,0 +1,59 @@
|
||||
## 简介
|
||||
|
||||
rate-limit-starter是基于redisson框架封装的限流框架,提供注解式@RateLimit限流。
|
||||
|
||||
全部特性:
|
||||
|
||||
- Spring Boot支持
|
||||
- @RateLimit注解限流
|
||||
- 多限流策略组合
|
||||
- 可扩展限流方式,自己扩展IRateLimitHandler进行实现,默认是redisson
|
||||
|
||||
## 使用说明
|
||||
|
||||
**引入maven依赖**
|
||||
|
||||
注:需要引入redisson依赖 以及 hutool
|
||||
|
||||
```xml
|
||||
|
||||
<dependency>
|
||||
<groupId>com.deepinnet</groupId>
|
||||
<artifactId>rate-limit-starter</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version><!--最新版本-->
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**开启@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(){
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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 "";
|
||||
|
||||
/**
|
||||
* 限定阈值
|
||||
* <p>
|
||||
* 时间间隔 interval 范围内超过该数量会触发锁
|
||||
*/
|
||||
long count();
|
||||
|
||||
/**
|
||||
* 时间间隔,默认 3 分钟
|
||||
* <p>
|
||||
* 例如 5s 五秒,6m 六分钟,7h 七小时,8d 八天
|
||||
*/
|
||||
String interval() default "3m";
|
||||
|
||||
/**
|
||||
* 限制策略
|
||||
*/
|
||||
String[] strategy() default {DefaultKeyGenerateStrategy.TYPE};
|
||||
|
||||
/**
|
||||
* 提示消息,非必须
|
||||
*/
|
||||
String message() default "请勿频繁操作";
|
||||
}
|
||||
@@ -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<String, RateLimit> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<IKeyGenerateStrategy> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<IKeyGenerateStrategy> keyGenerateStrategyList;
|
||||
private final IKeyGenerateStrategy defaultKeyGenerateStrategy;
|
||||
|
||||
public RateLimitKeyParser(List<IKeyGenerateStrategy> 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 <T> 当前运行的注解泛型
|
||||
* @return 获取到的泛型的值
|
||||
*/
|
||||
<T extends Annotation> T getAnnotation();
|
||||
}
|
||||
@@ -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<Object[]> argsSupplier;
|
||||
/**
|
||||
* 当前方法的全称
|
||||
* 使用{@link MethodUtils#getClassMethodName(Method)}方法获得
|
||||
*/
|
||||
private final String classMethodName;
|
||||
/**
|
||||
* 速率限制所用到的注解
|
||||
*/
|
||||
private final RateLimit rateLimit;
|
||||
|
||||
public RateLimitMethodMetaData(Method method, Supplier<Object[]> 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 extends Annotation> T getAnnotation() {
|
||||
return (T) rateLimit;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
32
src/main/java/com/deepinnet/ratelimit/util/MethodUtils.java
Normal file
32
src/main/java/com/deepinnet/ratelimit/util/MethodUtils.java
Normal file
@@ -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 <T> 注解类
|
||||
* @return 指定注解信息
|
||||
*/
|
||||
public static <T extends Annotation> T getAnnotation(Method method, Class<T> annotationClass) {
|
||||
return method.getAnnotation(annotationClass);
|
||||
}
|
||||
}
|
||||
90
src/main/java/com/deepinnet/ratelimit/util/RequestUtils.java
Normal file
90
src/main/java/com/deepinnet/ratelimit/util/RequestUtils.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user