init
This commit is contained in:
		| @ -0,0 +1,47 @@ | ||||
| package org.dromara.common.ratelimiter.annotation; | ||||
|  | ||||
| import org.dromara.common.ratelimiter.enums.LimitType; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * 限流注解 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Target(ElementType.METHOD) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Documented | ||||
| public @interface RateLimiter { | ||||
|     /** | ||||
|      * 限流key,支持使用Spring el表达式来动态获取方法上的参数值 | ||||
|      * 格式类似于  #code.id #{#code} | ||||
|      */ | ||||
|     String key() default ""; | ||||
|  | ||||
|     /** | ||||
|      * 限流时间,单位秒 | ||||
|      */ | ||||
|     int time() default 60; | ||||
|  | ||||
|     /** | ||||
|      * 限流次数 | ||||
|      */ | ||||
|     int count() default 100; | ||||
|  | ||||
|     /** | ||||
|      * 限流类型 | ||||
|      */ | ||||
|     LimitType limitType() default LimitType.DEFAULT; | ||||
|  | ||||
|     /** | ||||
|      * 提示消息 支持国际化 格式为 {code} | ||||
|      */ | ||||
|     String message() default "{rate.limiter.message}"; | ||||
|  | ||||
|     /** | ||||
|      * 限流策略超时时间 默认一天(策略存活时间 会清除已存在的策略数据) | ||||
|      */ | ||||
|     int timeout() default 86400; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,112 @@ | ||||
| package org.dromara.common.ratelimiter.aspectj; | ||||
|  | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.aspectj.lang.JoinPoint; | ||||
| import org.aspectj.lang.annotation.Aspect; | ||||
| import org.aspectj.lang.annotation.Before; | ||||
| import org.aspectj.lang.reflect.MethodSignature; | ||||
| import org.dromara.common.core.constant.GlobalConstants; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
| import org.dromara.common.core.utils.MessageUtils; | ||||
| import org.dromara.common.core.utils.ServletUtils; | ||||
| import org.dromara.common.core.utils.SpringUtils; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import org.dromara.common.ratelimiter.annotation.RateLimiter; | ||||
| import org.dromara.common.ratelimiter.enums.LimitType; | ||||
| import org.dromara.common.redis.utils.RedisUtils; | ||||
| import org.redisson.api.RateType; | ||||
| import org.springframework.context.expression.BeanFactoryResolver; | ||||
| import org.springframework.context.expression.MethodBasedEvaluationContext; | ||||
| import org.springframework.core.DefaultParameterNameDiscoverer; | ||||
| import org.springframework.core.ParameterNameDiscoverer; | ||||
| import org.springframework.expression.Expression; | ||||
| import org.springframework.expression.ExpressionParser; | ||||
| import org.springframework.expression.ParserContext; | ||||
| import org.springframework.expression.common.TemplateParserContext; | ||||
| import org.springframework.expression.spel.standard.SpelExpressionParser; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
|  | ||||
| /** | ||||
|  * 限流处理 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Slf4j | ||||
| @Aspect | ||||
| public class RateLimiterAspect { | ||||
|  | ||||
|     /** | ||||
|      * 定义spel表达式解析器 | ||||
|      */ | ||||
|     private final ExpressionParser parser = new SpelExpressionParser(); | ||||
|     /** | ||||
|      * 定义spel解析模版 | ||||
|      */ | ||||
|     private final ParserContext parserContext = new TemplateParserContext(); | ||||
|     /** | ||||
|      * 方法参数解析器 | ||||
|      */ | ||||
|     private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer(); | ||||
|  | ||||
|  | ||||
|     @Before("@annotation(rateLimiter)") | ||||
|     public void doBefore(JoinPoint point, RateLimiter rateLimiter) { | ||||
|         int time = rateLimiter.time(); | ||||
|         int count = rateLimiter.count(); | ||||
|         int timeout = rateLimiter.timeout(); | ||||
|         try { | ||||
|             String combineKey = getCombineKey(rateLimiter, point); | ||||
|             RateType rateType = RateType.OVERALL; | ||||
|             if (rateLimiter.limitType() == LimitType.CLUSTER) { | ||||
|                 rateType = RateType.PER_CLIENT; | ||||
|             } | ||||
|             long number = RedisUtils.rateLimiter(combineKey, rateType, count, time, timeout); | ||||
|             if (number == -1) { | ||||
|                 String message = rateLimiter.message(); | ||||
|                 if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) { | ||||
|                     message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1)); | ||||
|                 } | ||||
|                 throw new ServiceException(message); | ||||
|             } | ||||
|             log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, combineKey); | ||||
|         } catch (Exception e) { | ||||
|             if (e instanceof ServiceException) { | ||||
|                 throw e; | ||||
|             } else { | ||||
|                 throw new RuntimeException("服务器限流异常,请稍候再试", e); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) { | ||||
|         String key = rateLimiter.key(); | ||||
|         // 判断 key 不为空 和 不是表达式 | ||||
|         if (StringUtils.isNotBlank(key) && StringUtils.containsAny(key, "#")) { | ||||
|             MethodSignature signature = (MethodSignature) point.getSignature(); | ||||
|             Method targetMethod = signature.getMethod(); | ||||
|             Object[] args = point.getArgs(); | ||||
|             MethodBasedEvaluationContext context = | ||||
|                 new MethodBasedEvaluationContext(null, targetMethod, args, pnd); | ||||
|             context.setBeanResolver(new BeanFactoryResolver(SpringUtils.getBeanFactory())); | ||||
|             Expression expression; | ||||
|             if (StringUtils.startsWith(key, parserContext.getExpressionPrefix()) | ||||
|                 && StringUtils.endsWith(key, parserContext.getExpressionSuffix())) { | ||||
|                 expression = parser.parseExpression(key, parserContext); | ||||
|             } else { | ||||
|                 expression = parser.parseExpression(key); | ||||
|             } | ||||
|             key = expression.getValue(context, String.class); | ||||
|         } | ||||
|         StringBuilder stringBuffer = new StringBuilder(GlobalConstants.RATE_LIMIT_KEY); | ||||
|         stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":"); | ||||
|         if (rateLimiter.limitType() == LimitType.IP) { | ||||
|             // 获取请求ip | ||||
|             stringBuffer.append(ServletUtils.getClientIP()).append(":"); | ||||
|         } else if (rateLimiter.limitType() == LimitType.CLUSTER) { | ||||
|             // 获取客户端实例id | ||||
|             stringBuffer.append(RedisUtils.getClient().getId()).append(":"); | ||||
|         } | ||||
|         return stringBuffer.append(key).toString(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| package org.dromara.common.ratelimiter.config; | ||||
|  | ||||
| import org.dromara.common.ratelimiter.aspectj.RateLimiterAspect; | ||||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.data.redis.connection.RedisConfiguration; | ||||
|  | ||||
| /** | ||||
|  * @author guangxin | ||||
|  * @date 2023/1/18 | ||||
|  */ | ||||
| @AutoConfiguration(after = RedisConfiguration.class) | ||||
| public class RateLimiterConfig { | ||||
|  | ||||
|     @Bean | ||||
|     public RateLimiterAspect rateLimiterAspect() { | ||||
|         return new RateLimiterAspect(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,24 @@ | ||||
| package org.dromara.common.ratelimiter.enums; | ||||
|  | ||||
| /** | ||||
|  * 限流类型 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
|  | ||||
| public enum LimitType { | ||||
|     /** | ||||
|      * 默认策略全局限流 | ||||
|      */ | ||||
|     DEFAULT, | ||||
|  | ||||
|     /** | ||||
|      * 根据请求者IP进行限流 | ||||
|      */ | ||||
|     IP, | ||||
|  | ||||
|     /** | ||||
|      * 实例限流(集群多后端实例) | ||||
|      */ | ||||
|     CLUSTER | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| org.dromara.common.ratelimiter.config.RateLimiterConfig | ||||
| @ -0,0 +1,7 @@ | ||||
| { | ||||
|   "org.dromara.common.ratelimiter.annotation.RateLimiter@key": { | ||||
|     "method": { | ||||
|       "parameters": true | ||||
|     } | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user