-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master-jdk21' of https://gitee.com/zhijiantianya/yudao-…
…cloud # Conflicts: # yudao-dependencies/pom.xml # yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
- Loading branch information
Showing
30 changed files
with
456 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
...ain/java/cn/iocoder/yudao/framework/ratelimiter/config/YudaoRateLimiterConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package cn.iocoder.yudao.framework.ratelimiter.config; | ||
|
||
import cn.iocoder.yudao.framework.ratelimiter.core.aop.RateLimiterAspect; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl.*; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.redis.RateLimiterRedisDAO; | ||
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; | ||
import org.redisson.api.RedissonClient; | ||
import org.springframework.boot.autoconfigure.AutoConfiguration; | ||
import org.springframework.context.annotation.Bean; | ||
|
||
import java.util.List; | ||
|
||
@AutoConfiguration(after = YudaoRedisAutoConfiguration.class) | ||
public class YudaoRateLimiterConfiguration { | ||
|
||
@Bean | ||
public RateLimiterAspect rateLimiterAspect(List<RateLimiterKeyResolver> keyResolvers, RateLimiterRedisDAO rateLimiterRedisDAO) { | ||
return new RateLimiterAspect(keyResolvers, rateLimiterRedisDAO); | ||
} | ||
|
||
@Bean | ||
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") | ||
public RateLimiterRedisDAO rateLimiterRedisDAO(RedissonClient redissonClient) { | ||
return new RateLimiterRedisDAO(redissonClient); | ||
} | ||
|
||
// ========== 各种 RateLimiterRedisDAO Bean ========== | ||
|
||
@Bean | ||
public DefaultRateLimiterKeyResolver defaultRateLimiterKeyResolver() { | ||
return new DefaultRateLimiterKeyResolver(); | ||
} | ||
|
||
@Bean | ||
public UserRateLimiterKeyResolver userRateLimiterKeyResolver() { | ||
return new UserRateLimiterKeyResolver(); | ||
} | ||
|
||
@Bean | ||
public ClientIpRateLimiterKeyResolver clientIpRateLimiterKeyResolver() { | ||
return new ClientIpRateLimiterKeyResolver(); | ||
} | ||
|
||
@Bean | ||
public ServerNodeRateLimiterKeyResolver serverNodeRateLimiterKeyResolver() { | ||
return new ServerNodeRateLimiterKeyResolver(); | ||
} | ||
|
||
@Bean | ||
public ExpressionRateLimiterKeyResolver expressionRateLimiterKeyResolver() { | ||
return new ExpressionRateLimiterKeyResolver(); | ||
} | ||
|
||
} |
62 changes: 62 additions & 0 deletions
62
...ion/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/annotation/RateLimiter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package cn.iocoder.yudao.framework.ratelimiter.core.annotation; | ||
|
||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; | ||
import cn.iocoder.yudao.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl.ClientIpRateLimiterKeyResolver; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl.DefaultRateLimiterKeyResolver; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl.ServerNodeRateLimiterKeyResolver; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl.UserRateLimiterKeyResolver; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* 限流注解 | ||
* | ||
* @author 芋道源码 | ||
*/ | ||
@Target({ElementType.METHOD}) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface RateLimiter { | ||
|
||
/** | ||
* 限流的时间,默认为 1 秒 | ||
*/ | ||
int time() default 1; | ||
/** | ||
* 时间单位,默认为 SECONDS 秒 | ||
*/ | ||
TimeUnit timeUnit() default TimeUnit.SECONDS; | ||
|
||
/** | ||
* 限流次数 | ||
*/ | ||
int count() default 100; | ||
|
||
/** | ||
* 提示信息,请求过快的提示 | ||
* | ||
* @see GlobalErrorCodeConstants#TOO_MANY_REQUESTS | ||
*/ | ||
String message() default ""; // 为空时,使用 TOO_MANY_REQUESTS 错误提示 | ||
|
||
/** | ||
* 使用的 Key 解析器 | ||
* | ||
* @see DefaultRateLimiterKeyResolver 全局级别 | ||
* @see UserRateLimiterKeyResolver 用户 ID 级别 | ||
* @see ClientIpRateLimiterKeyResolver 用户 IP 级别 | ||
* @see ServerNodeRateLimiterKeyResolver 服务器 Node 级别 | ||
* @see ExpressionIdempotentKeyResolver 自定义表达式,通过 {@link #keyArg()} 计算 | ||
*/ | ||
Class<? extends RateLimiterKeyResolver> keyResolver() default DefaultRateLimiterKeyResolver.class; | ||
/** | ||
* 使用的 Key 参数 | ||
*/ | ||
String keyArg() default ""; | ||
|
||
} |
60 changes: 60 additions & 0 deletions
60
...tion/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/aop/RateLimiterAspect.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package cn.iocoder.yudao.framework.ratelimiter.core.aop; | ||
|
||
import cn.hutool.core.util.StrUtil; | ||
import cn.iocoder.yudao.framework.common.exception.ServiceException; | ||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; | ||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.redis.RateLimiterRedisDAO; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.aspectj.lang.JoinPoint; | ||
import org.aspectj.lang.annotation.Aspect; | ||
import org.aspectj.lang.annotation.Before; | ||
import org.springframework.util.Assert; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
||
/** | ||
* 拦截声明了 {@link RateLimiter} 注解的方法,实现限流操作 | ||
* | ||
* @author 芋道源码 | ||
*/ | ||
@Aspect | ||
@Slf4j | ||
public class RateLimiterAspect { | ||
|
||
/** | ||
* RateLimiterKeyResolver 集合 | ||
*/ | ||
private final Map<Class<? extends RateLimiterKeyResolver>, RateLimiterKeyResolver> keyResolvers; | ||
|
||
private final RateLimiterRedisDAO rateLimiterRedisDAO; | ||
|
||
public RateLimiterAspect(List<RateLimiterKeyResolver> keyResolvers, RateLimiterRedisDAO rateLimiterRedisDAO) { | ||
this.keyResolvers = CollectionUtils.convertMap(keyResolvers, RateLimiterKeyResolver::getClass); | ||
this.rateLimiterRedisDAO = rateLimiterRedisDAO; | ||
} | ||
|
||
@Before("@annotation(rateLimiter)") | ||
public void beforePointCut(JoinPoint joinPoint, RateLimiter rateLimiter) { | ||
// 获得 IdempotentKeyResolver 对象 | ||
RateLimiterKeyResolver keyResolver = keyResolvers.get(rateLimiter.keyResolver()); | ||
Assert.notNull(keyResolver, "找不到对应的 RateLimiterKeyResolver"); | ||
// 解析 Key | ||
String key = keyResolver.resolver(joinPoint, rateLimiter); | ||
|
||
// 获取 1 次限流 | ||
boolean success = rateLimiterRedisDAO.tryAcquire(key, | ||
rateLimiter.count(), rateLimiter.time(), rateLimiter.timeUnit()); | ||
if (!success) { | ||
log.info("[beforePointCut][方法({}) 参数({}) 请求过于频繁]", joinPoint.getSignature().toString(), joinPoint.getArgs()); | ||
String message = StrUtil.blankToDefault(rateLimiter.message(), | ||
GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getMsg()); | ||
throw new ServiceException(GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getCode(), message); | ||
} | ||
} | ||
|
||
} | ||
|
22 changes: 22 additions & 0 deletions
22
.../java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/RateLimiterKeyResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver; | ||
|
||
import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter; | ||
import org.aspectj.lang.JoinPoint; | ||
|
||
/** | ||
* 限流 Key 解析器接口 | ||
* | ||
* @author 芋道源码 | ||
*/ | ||
public interface RateLimiterKeyResolver { | ||
|
||
/** | ||
* 解析一个 Key | ||
* | ||
* @param rateLimiter 限流注解 | ||
* @param joinPoint AOP 切面 | ||
* @return Key | ||
*/ | ||
String resolver(JoinPoint joinPoint, RateLimiter rateLimiter); | ||
|
||
} |
27 changes: 27 additions & 0 deletions
27
...der/yudao/framework/ratelimiter/core/keyresolver/impl/ClientIpRateLimiterKeyResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl; | ||
|
||
import cn.hutool.core.util.StrUtil; | ||
import cn.hutool.crypto.SecureUtil; | ||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
import org.aspectj.lang.JoinPoint; | ||
|
||
/** | ||
* IP 级别的限流 Key 解析器,使用方法名 + 方法参数 + IP,组装成一个 Key | ||
* | ||
* 为了避免 Key 过长,使用 MD5 进行“压缩” | ||
* | ||
* @author 芋道源码 | ||
*/ | ||
public class ClientIpRateLimiterKeyResolver implements RateLimiterKeyResolver { | ||
|
||
@Override | ||
public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) { | ||
String methodName = joinPoint.getSignature().toString(); | ||
String argsStr = StrUtil.join(",", joinPoint.getArgs()); | ||
String clientIp = ServletUtils.getClientIP(); | ||
return SecureUtil.md5(methodName + argsStr + clientIp); | ||
} | ||
|
||
} |
25 changes: 25 additions & 0 deletions
25
...oder/yudao/framework/ratelimiter/core/keyresolver/impl/DefaultRateLimiterKeyResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl; | ||
|
||
import cn.hutool.core.util.StrUtil; | ||
import cn.hutool.crypto.SecureUtil; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
import org.aspectj.lang.JoinPoint; | ||
|
||
/** | ||
* 默认(全局级别)限流 Key 解析器,使用方法名 + 方法参数,组装成一个 Key | ||
* | ||
* 为了避免 Key 过长,使用 MD5 进行“压缩” | ||
* | ||
* @author 芋道源码 | ||
*/ | ||
public class DefaultRateLimiterKeyResolver implements RateLimiterKeyResolver { | ||
|
||
@Override | ||
public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) { | ||
String methodName = joinPoint.getSignature().toString(); | ||
String argsStr = StrUtil.join(",", joinPoint.getArgs()); | ||
return SecureUtil.md5(methodName + argsStr); | ||
} | ||
|
||
} |
64 changes: 64 additions & 0 deletions
64
...r/yudao/framework/ratelimiter/core/keyresolver/impl/ExpressionRateLimiterKeyResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl; | ||
|
||
import cn.hutool.core.util.ArrayUtil; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter; | ||
import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
import org.aspectj.lang.JoinPoint; | ||
import org.aspectj.lang.reflect.MethodSignature; | ||
import org.springframework.core.DefaultParameterNameDiscoverer; | ||
import org.springframework.core.ParameterNameDiscoverer; | ||
import org.springframework.expression.Expression; | ||
import org.springframework.expression.ExpressionParser; | ||
import org.springframework.expression.spel.standard.SpelExpressionParser; | ||
import org.springframework.expression.spel.support.StandardEvaluationContext; | ||
|
||
import java.lang.reflect.Method; | ||
|
||
/** | ||
* 基于 Spring EL 表达式的 {@link RateLimiterKeyResolver} 实现类 | ||
* | ||
* @author 芋道源码 | ||
*/ | ||
public class ExpressionRateLimiterKeyResolver implements RateLimiterKeyResolver { | ||
|
||
private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); | ||
|
||
private final ExpressionParser expressionParser = new SpelExpressionParser(); | ||
|
||
@Override | ||
public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) { | ||
// 获得被拦截方法参数名列表 | ||
Method method = getMethod(joinPoint); | ||
Object[] args = joinPoint.getArgs(); | ||
String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method); | ||
// 准备 Spring EL 表达式解析的上下文 | ||
StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); | ||
if (ArrayUtil.isNotEmpty(parameterNames)) { | ||
for (int i = 0; i < parameterNames.length; i++) { | ||
evaluationContext.setVariable(parameterNames[i], args[i]); | ||
} | ||
} | ||
|
||
// 解析参数 | ||
Expression expression = expressionParser.parseExpression(rateLimiter.keyArg()); | ||
return expression.getValue(evaluationContext, String.class); | ||
} | ||
|
||
private static Method getMethod(JoinPoint point) { | ||
// 处理,声明在类上的情况 | ||
MethodSignature signature = (MethodSignature) point.getSignature(); | ||
Method method = signature.getMethod(); | ||
if (!method.getDeclaringClass().isInterface()) { | ||
return method; | ||
} | ||
|
||
// 处理,声明在接口上的情况 | ||
try { | ||
return point.getTarget().getClass().getDeclaredMethod( | ||
point.getSignature().getName(), method.getParameterTypes()); | ||
} catch (NoSuchMethodException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.