From b8f7b48b79e496c3d4e6a0661a2f0f6e436587fd Mon Sep 17 00:00:00 2001 From: RuoYi <yzz_ivy@163.com> Date: 星期二, 17 八月 2021 14:14:51 +0800 Subject: [PATCH] 支持自定义注解实现接口限流 --- src/main/java/com/ruoyi/framework/aspectj/lang/annotation/RateLimiter.java | 40 ++++++++++ src/main/java/com/ruoyi/common/constant/Constants.java | 5 + src/main/java/com/ruoyi/framework/config/RedisConfig.java | 29 +++++++ src/main/java/com/ruoyi/framework/aspectj/lang/annotation/Excels.java | 4 src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java | 116 +++++++++++++++++++++++++++++ src/main/java/com/ruoyi/framework/aspectj/lang/enums/LimitType.java | 20 +++++ 6 files changed, 212 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ruoyi/common/constant/Constants.java b/src/main/java/com/ruoyi/common/constant/Constants.java index 7fdf163..7a57fc0 100644 --- a/src/main/java/com/ruoyi/common/constant/Constants.java +++ b/src/main/java/com/ruoyi/common/constant/Constants.java @@ -75,6 +75,11 @@ public static final String REPEAT_SUBMIT_KEY = "repeat_submit:"; /** + * 闄愭祦 redis key + */ + public static final String RATE_LIMIT_KEY = "rate_limit:"; + + /** * 楠岃瘉鐮佹湁鏁堟湡锛堝垎閽燂級 */ public static final Integer CAPTCHA_EXPIRATION = 2; diff --git a/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java b/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java new file mode 100644 index 0000000..c31a67e --- /dev/null +++ b/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java @@ -0,0 +1,116 @@ +package com.ruoyi.framework.aspectj; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.framework.aspectj.lang.annotation.RateLimiter; +import com.ruoyi.framework.aspectj.lang.enums.LimitType; + +/** + * 闄愭祦澶勭悊 + * + * @author ruoyi + */ +@Aspect +@Component +public class RateLimiterAspect +{ + private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); + + private RedisTemplate<Object, Object> redisTemplate; + + private RedisScript<Long> limitScript; + + @Autowired + public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate) + { + this.redisTemplate = redisTemplate; + } + + @Autowired + public void setLimitScript(RedisScript<Long> limitScript) + { + this.limitScript = limitScript; + } + + // 閰嶇疆缁囧叆鐐� + @Pointcut("@annotation(com.ruoyi.framework.aspectj.lang.annotation.RateLimiter)") + public void rateLimiterPointCut() + { + } + + @Before("rateLimiterPointCut()") + public void doBefore(JoinPoint point) throws Throwable + { + RateLimiter rateLimiter = getAnnotationRateLimiter(point); + String key = rateLimiter.key(); + int time = rateLimiter.time(); + int count = rateLimiter.count(); + + String combineKey = getCombineKey(rateLimiter, point); + List<Object> keys = Collections.singletonList(combineKey); + try + { + Long number = redisTemplate.execute(limitScript, keys, count, time); + if (StringUtils.isNull(number) || number.intValue() > count) + { + throw new ServiceException("璁块棶杩囦簬棰戠箒锛岃绋嶅悗鍐嶈瘯"); + } + log.info("闄愬埗璇锋眰'{}',褰撳墠璇锋眰'{}',缂撳瓨key'{}'", count, number.intValue(), key); + } + catch (ServiceException e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException("鏈嶅姟鍣ㄩ檺娴佸紓甯革紝璇风◢鍚庡啀璇�"); + } + } + + /** + * 鏄惁瀛樺湪娉ㄨВ锛屽鏋滃瓨鍦ㄥ氨鑾峰彇 + */ + private RateLimiter getAnnotationRateLimiter(JoinPoint joinPoint) + { + Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + Method method = methodSignature.getMethod(); + + if (method != null) + { + return method.getAnnotation(RateLimiter.class); + } + return null; + } + + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) + { + StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); + if (rateLimiter.limitType() == LimitType.IP) + { + stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())); + } + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class<?> targetClass = method.getDeclaringClass(); + stringBuffer.append("-").append(targetClass.getName()).append("- ").append(method.getName()); + return stringBuffer.toString(); + } +} diff --git a/src/main/java/com/ruoyi/framework/aspectj/lang/annotation/Excels.java b/src/main/java/com/ruoyi/framework/aspectj/lang/annotation/Excels.java index d9645a0..d2e5d74 100644 --- a/src/main/java/com/ruoyi/framework/aspectj/lang/annotation/Excels.java +++ b/src/main/java/com/ruoyi/framework/aspectj/lang/annotation/Excels.java @@ -14,5 +14,5 @@ @Retention(RetentionPolicy.RUNTIME) public @interface Excels { - Excel[] value(); -} \ No newline at end of file + public Excel[] value(); +} diff --git a/src/main/java/com/ruoyi/framework/aspectj/lang/annotation/RateLimiter.java b/src/main/java/com/ruoyi/framework/aspectj/lang/annotation/RateLimiter.java new file mode 100644 index 0000000..722451e --- /dev/null +++ b/src/main/java/com/ruoyi/framework/aspectj/lang/annotation/RateLimiter.java @@ -0,0 +1,40 @@ +package com.ruoyi.framework.aspectj.lang.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.framework.aspectj.lang.enums.LimitType; + +/** + * 闄愭祦娉ㄨВ + * + * @author ruoyi + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter +{ + /** + * 闄愭祦key + */ + public String key() default Constants.RATE_LIMIT_KEY; + + /** + * 闄愭祦鏃堕棿,鍗曚綅绉� + */ + public int time() default 60; + + /** + * 闄愭祦娆℃暟 + */ + public int count() default 100; + + /** + * 闄愭祦绫诲瀷 + */ + public LimitType limitType() default LimitType.DEFAULT; +} diff --git a/src/main/java/com/ruoyi/framework/aspectj/lang/enums/LimitType.java b/src/main/java/com/ruoyi/framework/aspectj/lang/enums/LimitType.java new file mode 100644 index 0000000..12d9bb9 --- /dev/null +++ b/src/main/java/com/ruoyi/framework/aspectj/lang/enums/LimitType.java @@ -0,0 +1,20 @@ +package com.ruoyi.framework.aspectj.lang.enums; + +/** + * 闄愭祦绫诲瀷 + * + * @author ruoyi + */ + +public enum LimitType +{ + /** + * 榛樿绛栫暐鍏ㄥ眬闄愭祦 + */ + DEFAULT, + + /** + * 鏍规嵁璇锋眰鑰匢P杩涜闄愭祦 + */ + IP +} diff --git a/src/main/java/com/ruoyi/framework/config/RedisConfig.java b/src/main/java/com/ruoyi/framework/config/RedisConfig.java index 7422f3f..322d6e3 100644 --- a/src/main/java/com/ruoyi/framework/config/RedisConfig.java +++ b/src/main/java/com/ruoyi/framework/config/RedisConfig.java @@ -6,6 +6,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.serializer.StringRedisSerializer; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonTypeInfo; @@ -47,4 +48,32 @@ template.afterPropertiesSet(); return template; } + + @Bean + public DefaultRedisScript<Long> limitScript() + { + DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(limitScriptText()); + redisScript.setResultType(Long.class); + return redisScript; + } + + /** + * 闄愭祦鑴氭湰 + */ + private String limitScriptText() + { + return "local key = KEYS[1]\n" + + "local count = tonumber(ARGV[1])\n" + + "local time = tonumber(ARGV[2])\n" + + "local current = redis.call('get', key);\n" + + "if current and tonumber(current) > count then\n" + + " return current;\n" + + "end\n" + + "current = redis.call('incr', key)\n" + + "if tonumber(current) == 1 then\n" + + " redis.call('expire', key, time)\n" + + "end\n" + + "return current;"; + } } -- Gitblit v1.9.3