Commit cc50891632d91a84c9eacf2a540af01fe94594c2

Authored by YunaiV
1 parent 2e03dcba

【新增】RateLimiter 限流器,支持全局、用户、IP 等级别的限流

Showing 15 changed files with 440 additions and 8 deletions
README.md
... ... @@ -207,9 +207,7 @@
207 207 | 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
208 208 | 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
209 209 | 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
210   -| 🚀 | 分布式锁 | 基于 Redis 实现分布式锁,满足并发场景 |
211   -| 🚀 | 幂等组件 | 基于 Redis 实现幂等组件,解决重复请求问题 |
212   -| 🚀 | 服务保障 | 基于 Resilience4j 实现服务的稳定性,包括限流、熔断等功能 |
  210 +| 🚀 | 服务保障 | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景 |
213 211 | 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
214 212 | 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
215 213  
... ... @@ -304,7 +302,6 @@
304 302 | [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 7.0.0 | [文档](https://doc.iocoder.cn/bpm/) |
305 303 | [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
306 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 305 | [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 9.0.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
309 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 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 20 <dependency>
21 21 <groupId>cn.iocoder.boot</groupId>
22 22 <artifactId>yudao-spring-boot-starter-web</artifactId>
23   - <scope>provided</scope> <!-- 设置为 provided,只有 OncePerRequestFilter 使用到 -->
  23 + <scope>provided</scope> <!-- 设置为 provided,只有限流、幂等使用到 -->
24 24 </dependency>
25 25  
26 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 37 }
38 38  
39 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 41 // 获得 IdempotentKeyResolver
42 42 IdempotentKeyResolver keyResolver = keyResolvers.get(idempotent.keyResolver());
43 43 Assert.notNull(keyResolver, "找不到对应的 IdempotentKeyResolver");
... ... @@ -48,7 +48,7 @@ public class IdempotentAspect {
48 48 boolean success = idempotentRedisDAO.setIfAbsent(key, idempotent.timeout(), idempotent.timeUnit());
49 49 // 锁定失败,抛出异常
50 50 if (!success) {
51   - log.info("[beforePointCut][方法({}) 参数({}) 存在重复请求]", joinPoint.getSignature().toString(), joinPoint.getArgs());
  51 + log.info("[aroundPointCut][方法({}) 参数({}) 存在重复请求]", joinPoint.getSignature().toString(), joinPoint.getArgs());
52 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 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
  1 +/**
  2 + * 限流组件,基于 Redisson {@link org.redisson.api.RRateLimiter} 限流实现
  3 + */
  4 +package cn.iocoder.yudao.framework.ratelimiter;
0 5 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
1 1 cn.iocoder.yudao.framework.idempotent.config.YudaoIdempotentConfiguration
2   -cn.iocoder.yudao.framework.lock4j.config.YudaoLock4jConfiguration
3 2 \ No newline at end of file
  3 +cn.iocoder.yudao.framework.lock4j.config.YudaoLock4jConfiguration
  4 +cn.iocoder.yudao.framework.ratelimiter.config.YudaoRateLimiterConfiguration
4 5 \ No newline at end of file
... ...