Commit cc50891632d91a84c9eacf2a540af01fe94594c2
1 parent
2e03dcba
【新增】RateLimiter 限流器,支持全局、用户、IP 等级别的限流
Showing
15 changed files
with
440 additions
and
8 deletions
README.md
| @@ -207,9 +207,7 @@ | @@ -207,9 +207,7 @@ | ||
| 207 | | 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 | | 207 | | 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 | |
| 208 | | 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 | | 208 | | 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 | |
| 209 | | 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 | | 209 | | 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 | |
| 210 | -| 🚀 | 分布式锁 | 基于 Redis 实现分布式锁,满足并发场景 | | ||
| 211 | -| 🚀 | 幂等组件 | 基于 Redis 实现幂等组件,解决重复请求问题 | | ||
| 212 | -| 🚀 | 服务保障 | 基于 Resilience4j 实现服务的稳定性,包括限流、熔断等功能 | | 210 | +| 🚀 | 服务保障 | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景 | |
| 213 | | 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 | | 211 | | 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 | |
| 214 | | 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 | | 212 | | 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 | |
| 215 | 213 | ||
| @@ -304,7 +302,6 @@ | @@ -304,7 +302,6 @@ | ||
| 304 | | [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 7.0.0 | [文档](https://doc.iocoder.cn/bpm/) | | 302 | | [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 7.0.0 | [文档](https://doc.iocoder.cn/bpm/) | |
| 305 | | [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) | | 303 | | [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) | |
| 306 | | [Springdoc](https://springdoc.org/) | Swagger 文档 | 2.2.0 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) | | 304 | | [Springdoc](https://springdoc.org/) | Swagger 文档 | 2.2.0 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) | |
| 307 | -| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 2.1.0 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) | | ||
| 308 | | [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 9.0.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) | | 305 | | [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 9.0.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) | |
| 309 | | [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 3.1.8 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) | | 306 | | [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 3.1.8 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) | |
| 310 | | [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.15.3 | | | 307 | | [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.15.3 | | |
yudao-framework/yudao-spring-boot-starter-protection/pom.xml
| @@ -20,7 +20,7 @@ | @@ -20,7 +20,7 @@ | ||
| 20 | <dependency> | 20 | <dependency> |
| 21 | <groupId>cn.iocoder.boot</groupId> | 21 | <groupId>cn.iocoder.boot</groupId> |
| 22 | <artifactId>yudao-spring-boot-starter-web</artifactId> | 22 | <artifactId>yudao-spring-boot-starter-web</artifactId> |
| 23 | - <scope>provided</scope> <!-- 设置为 provided,只有 OncePerRequestFilter 使用到 --> | 23 | + <scope>provided</scope> <!-- 设置为 provided,只有限流、幂等使用到 --> |
| 24 | </dependency> | 24 | </dependency> |
| 25 | 25 | ||
| 26 | <!-- DB 相关 --> | 26 | <!-- DB 相关 --> |
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/idempotent/core/aop/IdempotentAspect.java
| @@ -37,7 +37,7 @@ public class IdempotentAspect { | @@ -37,7 +37,7 @@ public class IdempotentAspect { | ||
| 37 | } | 37 | } |
| 38 | 38 | ||
| 39 | @Around(value = "@annotation(idempotent)") | 39 | @Around(value = "@annotation(idempotent)") |
| 40 | - public Object beforePointCut(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable { | 40 | + public Object aroundPointCut(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable { |
| 41 | // 获得 IdempotentKeyResolver | 41 | // 获得 IdempotentKeyResolver |
| 42 | IdempotentKeyResolver keyResolver = keyResolvers.get(idempotent.keyResolver()); | 42 | IdempotentKeyResolver keyResolver = keyResolvers.get(idempotent.keyResolver()); |
| 43 | Assert.notNull(keyResolver, "找不到对应的 IdempotentKeyResolver"); | 43 | Assert.notNull(keyResolver, "找不到对应的 IdempotentKeyResolver"); |
| @@ -48,7 +48,7 @@ public class IdempotentAspect { | @@ -48,7 +48,7 @@ public class IdempotentAspect { | ||
| 48 | boolean success = idempotentRedisDAO.setIfAbsent(key, idempotent.timeout(), idempotent.timeUnit()); | 48 | boolean success = idempotentRedisDAO.setIfAbsent(key, idempotent.timeout(), idempotent.timeUnit()); |
| 49 | // 锁定失败,抛出异常 | 49 | // 锁定失败,抛出异常 |
| 50 | if (!success) { | 50 | if (!success) { |
| 51 | - log.info("[beforePointCut][方法({}) 参数({}) 存在重复请求]", joinPoint.getSignature().toString(), joinPoint.getArgs()); | 51 | + log.info("[aroundPointCut][方法({}) 参数({}) 存在重复请求]", joinPoint.getSignature().toString(), joinPoint.getArgs()); |
| 52 | throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), idempotent.message()); | 52 | throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), idempotent.message()); |
| 53 | } | 53 | } |
| 54 | 54 |
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/config/YudaoRateLimiterConfiguration.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ratelimiter.config; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.ratelimiter.core.aop.RateLimiterAspect; | ||
| 4 | +import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
| 5 | +import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl.*; | ||
| 6 | +import cn.iocoder.yudao.framework.ratelimiter.core.redis.RateLimiterRedisDAO; | ||
| 7 | +import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; | ||
| 8 | +import org.redisson.api.RedissonClient; | ||
| 9 | +import org.springframework.boot.autoconfigure.AutoConfiguration; | ||
| 10 | +import org.springframework.context.annotation.Bean; | ||
| 11 | + | ||
| 12 | +import java.util.List; | ||
| 13 | + | ||
| 14 | +@AutoConfiguration(after = YudaoRedisAutoConfiguration.class) | ||
| 15 | +public class YudaoRateLimiterConfiguration { | ||
| 16 | + | ||
| 17 | + @Bean | ||
| 18 | + public RateLimiterAspect rateLimiterAspect(List<RateLimiterKeyResolver> keyResolvers, RateLimiterRedisDAO rateLimiterRedisDAO) { | ||
| 19 | + return new RateLimiterAspect(keyResolvers, rateLimiterRedisDAO); | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + @Bean | ||
| 23 | + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") | ||
| 24 | + public RateLimiterRedisDAO rateLimiterRedisDAO(RedissonClient redissonClient) { | ||
| 25 | + return new RateLimiterRedisDAO(redissonClient); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + // ========== 各种 RateLimiterRedisDAO Bean ========== | ||
| 29 | + | ||
| 30 | + @Bean | ||
| 31 | + public DefaultRateLimiterKeyResolver defaultRateLimiterKeyResolver() { | ||
| 32 | + return new DefaultRateLimiterKeyResolver(); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + @Bean | ||
| 36 | + public UserRateLimiterKeyResolver userRateLimiterKeyResolver() { | ||
| 37 | + return new UserRateLimiterKeyResolver(); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + @Bean | ||
| 41 | + public ClientIpRateLimiterKeyResolver clientIpRateLimiterKeyResolver() { | ||
| 42 | + return new ClientIpRateLimiterKeyResolver(); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + @Bean | ||
| 46 | + public ServerNodeRateLimiterKeyResolver serverNodeRateLimiterKeyResolver() { | ||
| 47 | + return new ServerNodeRateLimiterKeyResolver(); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + @Bean | ||
| 51 | + public ExpressionRateLimiterKeyResolver expressionRateLimiterKeyResolver() { | ||
| 52 | + return new ExpressionRateLimiterKeyResolver(); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | +} |
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/annotation/RateLimiter.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ratelimiter.core.annotation; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; | ||
| 4 | +import cn.iocoder.yudao.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver; | ||
| 5 | +import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
| 6 | +import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl.ClientIpRateLimiterKeyResolver; | ||
| 7 | +import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl.DefaultRateLimiterKeyResolver; | ||
| 8 | +import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl.ServerNodeRateLimiterKeyResolver; | ||
| 9 | +import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl.UserRateLimiterKeyResolver; | ||
| 10 | + | ||
| 11 | +import java.lang.annotation.ElementType; | ||
| 12 | +import java.lang.annotation.Retention; | ||
| 13 | +import java.lang.annotation.RetentionPolicy; | ||
| 14 | +import java.lang.annotation.Target; | ||
| 15 | +import java.util.concurrent.TimeUnit; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * 限流注解 | ||
| 19 | + * | ||
| 20 | + * @author 芋道源码 | ||
| 21 | + */ | ||
| 22 | +@Target({ElementType.METHOD}) | ||
| 23 | +@Retention(RetentionPolicy.RUNTIME) | ||
| 24 | +public @interface RateLimiter { | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 限流的时间,默认为 1 秒 | ||
| 28 | + */ | ||
| 29 | + int time() default 1; | ||
| 30 | + /** | ||
| 31 | + * 时间单位,默认为 SECONDS 秒 | ||
| 32 | + */ | ||
| 33 | + TimeUnit timeUnit() default TimeUnit.SECONDS; | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * 限流次数 | ||
| 37 | + */ | ||
| 38 | + int count() default 100; | ||
| 39 | + | ||
| 40 | + /** | ||
| 41 | + * 提示信息,请求过快的提示 | ||
| 42 | + * | ||
| 43 | + * @see GlobalErrorCodeConstants#TOO_MANY_REQUESTS | ||
| 44 | + */ | ||
| 45 | + String message() default ""; // 为空时,使用 TOO_MANY_REQUESTS 错误提示 | ||
| 46 | + | ||
| 47 | + /** | ||
| 48 | + * 使用的 Key 解析器 | ||
| 49 | + * | ||
| 50 | + * @see DefaultRateLimiterKeyResolver 全局级别 | ||
| 51 | + * @see UserRateLimiterKeyResolver 用户 ID 级别 | ||
| 52 | + * @see ClientIpRateLimiterKeyResolver 用户 IP 级别 | ||
| 53 | + * @see ServerNodeRateLimiterKeyResolver 服务器 Node 级别 | ||
| 54 | + * @see ExpressionIdempotentKeyResolver 自定义表达式,通过 {@link #keyArg()} 计算 | ||
| 55 | + */ | ||
| 56 | + Class<? extends RateLimiterKeyResolver> keyResolver() default DefaultRateLimiterKeyResolver.class; | ||
| 57 | + /** | ||
| 58 | + * 使用的 Key 参数 | ||
| 59 | + */ | ||
| 60 | + String keyArg() default ""; | ||
| 61 | + | ||
| 62 | +} |
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/aop/RateLimiterAspect.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ratelimiter.core.aop; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.common.exception.ServiceException; | ||
| 5 | +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; | ||
| 6 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||
| 7 | +import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter; | ||
| 8 | +import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
| 9 | +import cn.iocoder.yudao.framework.ratelimiter.core.redis.RateLimiterRedisDAO; | ||
| 10 | +import lombok.extern.slf4j.Slf4j; | ||
| 11 | +import org.aspectj.lang.JoinPoint; | ||
| 12 | +import org.aspectj.lang.annotation.Aspect; | ||
| 13 | +import org.aspectj.lang.annotation.Before; | ||
| 14 | +import org.springframework.util.Assert; | ||
| 15 | + | ||
| 16 | +import java.util.List; | ||
| 17 | +import java.util.Map; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * 拦截声明了 {@link RateLimiter} 注解的方法,实现限流操作 | ||
| 21 | + * | ||
| 22 | + * @author 芋道源码 | ||
| 23 | + */ | ||
| 24 | +@Aspect | ||
| 25 | +@Slf4j | ||
| 26 | +public class RateLimiterAspect { | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * RateLimiterKeyResolver 集合 | ||
| 30 | + */ | ||
| 31 | + private final Map<Class<? extends RateLimiterKeyResolver>, RateLimiterKeyResolver> keyResolvers; | ||
| 32 | + | ||
| 33 | + private final RateLimiterRedisDAO rateLimiterRedisDAO; | ||
| 34 | + | ||
| 35 | + public RateLimiterAspect(List<RateLimiterKeyResolver> keyResolvers, RateLimiterRedisDAO rateLimiterRedisDAO) { | ||
| 36 | + this.keyResolvers = CollectionUtils.convertMap(keyResolvers, RateLimiterKeyResolver::getClass); | ||
| 37 | + this.rateLimiterRedisDAO = rateLimiterRedisDAO; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + @Before("@annotation(rateLimiter)") | ||
| 41 | + public void beforePointCut(JoinPoint joinPoint, RateLimiter rateLimiter) { | ||
| 42 | + // 获得 IdempotentKeyResolver 对象 | ||
| 43 | + RateLimiterKeyResolver keyResolver = keyResolvers.get(rateLimiter.keyResolver()); | ||
| 44 | + Assert.notNull(keyResolver, "找不到对应的 RateLimiterKeyResolver"); | ||
| 45 | + // 解析 Key | ||
| 46 | + String key = keyResolver.resolver(joinPoint, rateLimiter); | ||
| 47 | + | ||
| 48 | + // 获取 1 次限流 | ||
| 49 | + boolean success = rateLimiterRedisDAO.tryAcquire(key, | ||
| 50 | + rateLimiter.count(), rateLimiter.time(), rateLimiter.timeUnit()); | ||
| 51 | + if (!success) { | ||
| 52 | + log.info("[beforePointCut][方法({}) 参数({}) 请求过于频繁]", joinPoint.getSignature().toString(), joinPoint.getArgs()); | ||
| 53 | + String message = StrUtil.blankToDefault(rateLimiter.message(), | ||
| 54 | + GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getMsg()); | ||
| 55 | + throw new ServiceException(GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getCode(), message); | ||
| 56 | + } | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | +} | ||
| 60 | + |
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/RateLimiterKeyResolver.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter; | ||
| 4 | +import org.aspectj.lang.JoinPoint; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 限流 Key 解析器接口 | ||
| 8 | + * | ||
| 9 | + * @author 芋道源码 | ||
| 10 | + */ | ||
| 11 | +public interface RateLimiterKeyResolver { | ||
| 12 | + | ||
| 13 | + /** | ||
| 14 | + * 解析一个 Key | ||
| 15 | + * | ||
| 16 | + * @param rateLimiter 限流注解 | ||
| 17 | + * @param joinPoint AOP 切面 | ||
| 18 | + * @return Key | ||
| 19 | + */ | ||
| 20 | + String resolver(JoinPoint joinPoint, RateLimiter rateLimiter); | ||
| 21 | + | ||
| 22 | +} |
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/impl/ClientIpRateLimiterKeyResolver.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | +import cn.hutool.crypto.SecureUtil; | ||
| 5 | +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; | ||
| 6 | +import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter; | ||
| 7 | +import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
| 8 | +import org.aspectj.lang.JoinPoint; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * IP 级别的限流 Key 解析器,使用方法名 + 方法参数 + IP,组装成一个 Key | ||
| 12 | + * | ||
| 13 | + * 为了避免 Key 过长,使用 MD5 进行“压缩” | ||
| 14 | + * | ||
| 15 | + * @author 芋道源码 | ||
| 16 | + */ | ||
| 17 | +public class ClientIpRateLimiterKeyResolver implements RateLimiterKeyResolver { | ||
| 18 | + | ||
| 19 | + @Override | ||
| 20 | + public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) { | ||
| 21 | + String methodName = joinPoint.getSignature().toString(); | ||
| 22 | + String argsStr = StrUtil.join(",", joinPoint.getArgs()); | ||
| 23 | + String clientIp = ServletUtils.getClientIP(); | ||
| 24 | + return SecureUtil.md5(methodName + argsStr + clientIp); | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | +} |
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/impl/DefaultRateLimiterKeyResolver.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | +import cn.hutool.crypto.SecureUtil; | ||
| 5 | +import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter; | ||
| 6 | +import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
| 7 | +import org.aspectj.lang.JoinPoint; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 默认(全局级别)限流 Key 解析器,使用方法名 + 方法参数,组装成一个 Key | ||
| 11 | + * | ||
| 12 | + * 为了避免 Key 过长,使用 MD5 进行“压缩” | ||
| 13 | + * | ||
| 14 | + * @author 芋道源码 | ||
| 15 | + */ | ||
| 16 | +public class DefaultRateLimiterKeyResolver implements RateLimiterKeyResolver { | ||
| 17 | + | ||
| 18 | + @Override | ||
| 19 | + public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) { | ||
| 20 | + String methodName = joinPoint.getSignature().toString(); | ||
| 21 | + String argsStr = StrUtil.join(",", joinPoint.getArgs()); | ||
| 22 | + return SecureUtil.md5(methodName + argsStr); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | +} |
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/impl/ExpressionRateLimiterKeyResolver.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.ArrayUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter; | ||
| 5 | +import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
| 6 | +import org.aspectj.lang.JoinPoint; | ||
| 7 | +import org.aspectj.lang.reflect.MethodSignature; | ||
| 8 | +import org.springframework.core.DefaultParameterNameDiscoverer; | ||
| 9 | +import org.springframework.core.ParameterNameDiscoverer; | ||
| 10 | +import org.springframework.expression.Expression; | ||
| 11 | +import org.springframework.expression.ExpressionParser; | ||
| 12 | +import org.springframework.expression.spel.standard.SpelExpressionParser; | ||
| 13 | +import org.springframework.expression.spel.support.StandardEvaluationContext; | ||
| 14 | + | ||
| 15 | +import java.lang.reflect.Method; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * 基于 Spring EL 表达式的 {@link RateLimiterKeyResolver} 实现类 | ||
| 19 | + * | ||
| 20 | + * @author 芋道源码 | ||
| 21 | + */ | ||
| 22 | +public class ExpressionRateLimiterKeyResolver implements RateLimiterKeyResolver { | ||
| 23 | + | ||
| 24 | + private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); | ||
| 25 | + | ||
| 26 | + private final ExpressionParser expressionParser = new SpelExpressionParser(); | ||
| 27 | + | ||
| 28 | + @Override | ||
| 29 | + public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) { | ||
| 30 | + // 获得被拦截方法参数名列表 | ||
| 31 | + Method method = getMethod(joinPoint); | ||
| 32 | + Object[] args = joinPoint.getArgs(); | ||
| 33 | + String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method); | ||
| 34 | + // 准备 Spring EL 表达式解析的上下文 | ||
| 35 | + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); | ||
| 36 | + if (ArrayUtil.isNotEmpty(parameterNames)) { | ||
| 37 | + for (int i = 0; i < parameterNames.length; i++) { | ||
| 38 | + evaluationContext.setVariable(parameterNames[i], args[i]); | ||
| 39 | + } | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + // 解析参数 | ||
| 43 | + Expression expression = expressionParser.parseExpression(rateLimiter.keyArg()); | ||
| 44 | + return expression.getValue(evaluationContext, String.class); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + private static Method getMethod(JoinPoint point) { | ||
| 48 | + // 处理,声明在类上的情况 | ||
| 49 | + MethodSignature signature = (MethodSignature) point.getSignature(); | ||
| 50 | + Method method = signature.getMethod(); | ||
| 51 | + if (!method.getDeclaringClass().isInterface()) { | ||
| 52 | + return method; | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + // 处理,声明在接口上的情况 | ||
| 56 | + try { | ||
| 57 | + return point.getTarget().getClass().getDeclaredMethod( | ||
| 58 | + point.getSignature().getName(), method.getParameterTypes()); | ||
| 59 | + } catch (NoSuchMethodException e) { | ||
| 60 | + throw new RuntimeException(e); | ||
| 61 | + } | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | +} |
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/impl/ServerNodeRateLimiterKeyResolver.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | +import cn.hutool.crypto.SecureUtil; | ||
| 5 | +import cn.hutool.system.SystemUtil; | ||
| 6 | +import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter; | ||
| 7 | +import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
| 8 | +import org.aspectj.lang.JoinPoint; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * Server 节点级别的限流 Key 解析器,使用方法名 + 方法参数 + IP,组装成一个 Key | ||
| 12 | + * | ||
| 13 | + * 为了避免 Key 过长,使用 MD5 进行“压缩” | ||
| 14 | + * | ||
| 15 | + * @author 芋道源码 | ||
| 16 | + */ | ||
| 17 | +public class ServerNodeRateLimiterKeyResolver implements RateLimiterKeyResolver { | ||
| 18 | + | ||
| 19 | + @Override | ||
| 20 | + public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) { | ||
| 21 | + String methodName = joinPoint.getSignature().toString(); | ||
| 22 | + String argsStr = StrUtil.join(",", joinPoint.getArgs()); | ||
| 23 | + String serverNode = String.format("%s@%d", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID()); | ||
| 24 | + return SecureUtil.md5(methodName + argsStr + serverNode); | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | +} | ||
| 0 | \ No newline at end of file | 28 | \ No newline at end of file |
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/impl/UserRateLimiterKeyResolver.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | +import cn.hutool.crypto.SecureUtil; | ||
| 5 | +import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter; | ||
| 6 | +import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver; | ||
| 7 | +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; | ||
| 8 | +import org.aspectj.lang.JoinPoint; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 用户级别的限流 Key 解析器,使用方法名 + 方法参数 + userId + userType,组装成一个 Key | ||
| 12 | + * | ||
| 13 | + * 为了避免 Key 过长,使用 MD5 进行“压缩” | ||
| 14 | + * | ||
| 15 | + * @author 芋道源码 | ||
| 16 | + */ | ||
| 17 | +public class UserRateLimiterKeyResolver implements RateLimiterKeyResolver { | ||
| 18 | + | ||
| 19 | + @Override | ||
| 20 | + public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) { | ||
| 21 | + String methodName = joinPoint.getSignature().toString(); | ||
| 22 | + String argsStr = StrUtil.join(",", joinPoint.getArgs()); | ||
| 23 | + Long userId = WebFrameworkUtils.getLoginUserId(); | ||
| 24 | + Integer userType = WebFrameworkUtils.getLoginUserType(); | ||
| 25 | + return SecureUtil.md5(methodName + argsStr + userId + userType); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | +} |
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/redis/RateLimiterRedisDAO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ratelimiter.core.redis; | ||
| 2 | + | ||
| 3 | +import lombok.AllArgsConstructor; | ||
| 4 | +import org.redisson.api.*; | ||
| 5 | + | ||
| 6 | +import java.util.Objects; | ||
| 7 | +import java.util.concurrent.TimeUnit; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 限流 Redis DAO | ||
| 11 | + * | ||
| 12 | + * @author 芋道源码 | ||
| 13 | + */ | ||
| 14 | +@AllArgsConstructor | ||
| 15 | +public class RateLimiterRedisDAO { | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * 限流操作 | ||
| 19 | + * | ||
| 20 | + * KEY 格式:rate_limiter:%s // 参数为 uuid | ||
| 21 | + * VALUE 格式:String | ||
| 22 | + * 过期时间:不固定 | ||
| 23 | + */ | ||
| 24 | + private static final String RATE_LIMITER = "rate_limiter:%s"; | ||
| 25 | + | ||
| 26 | + private final RedissonClient redissonClient; | ||
| 27 | + | ||
| 28 | + public Boolean tryAcquire(String key, int count, int time, TimeUnit timeUnit) { | ||
| 29 | + // 1. 获得 RRateLimiter,并设置 rate 速率 | ||
| 30 | + RRateLimiter rateLimiter = getRRateLimiter(key, count, time, timeUnit); | ||
| 31 | + // 2. 尝试获取 1 个 | ||
| 32 | + return rateLimiter.tryAcquire(); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + private static String formatKey(String key) { | ||
| 36 | + return String.format(RATE_LIMITER, key); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + private RRateLimiter getRRateLimiter(String key, long count, int time, TimeUnit timeUnit) { | ||
| 40 | + String redisKey = formatKey(key); | ||
| 41 | + RRateLimiter rateLimiter = redissonClient.getRateLimiter(redisKey); | ||
| 42 | + long rateInterval = timeUnit.toSeconds(time); | ||
| 43 | + // 1. 如果不存在,设置 rate 速率 | ||
| 44 | + RateLimiterConfig config = rateLimiter.getConfig(); | ||
| 45 | + if (config == null) { | ||
| 46 | + rateLimiter.trySetRate(RateType.OVERALL, count, rateInterval, RateIntervalUnit.SECONDS); | ||
| 47 | + return rateLimiter; | ||
| 48 | + } | ||
| 49 | + // 2. 如果存在,并且配置相同,则直接返回 | ||
| 50 | + if (config.getRateType() == RateType.OVERALL | ||
| 51 | + && Objects.equals(config.getRate(), count) | ||
| 52 | + && Objects.equals(config.getRateInterval(), TimeUnit.SECONDS.toMillis(rateInterval))) { | ||
| 53 | + return rateLimiter; | ||
| 54 | + } | ||
| 55 | + // 3. 如果存在,并且配置不同,则进行新建 | ||
| 56 | + rateLimiter.setRate(RateType.OVERALL, count, rateInterval, RateIntervalUnit.SECONDS); | ||
| 57 | + return rateLimiter; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | +} |
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/package-info.java
0 → 100644
yudao-framework/yudao-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
| 1 | cn.iocoder.yudao.framework.idempotent.config.YudaoIdempotentConfiguration | 1 | cn.iocoder.yudao.framework.idempotent.config.YudaoIdempotentConfiguration |
| 2 | -cn.iocoder.yudao.framework.lock4j.config.YudaoLock4jConfiguration | ||
| 3 | \ No newline at end of file | 2 | \ No newline at end of file |
| 3 | +cn.iocoder.yudao.framework.lock4j.config.YudaoLock4jConfiguration | ||
| 4 | +cn.iocoder.yudao.framework.ratelimiter.config.YudaoRateLimiterConfiguration | ||
| 4 | \ No newline at end of file | 5 | \ No newline at end of file |