ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-ui/src/utils/request.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-ui/src/utils/ruoyi.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,40 @@ package com.ruoyi.common.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.common.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; } ruoyi-common/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; ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,20 @@ package com.ruoyi.common.enums; /** * éæµç±»å * * @author ruoyi */ public enum LimitType { /** * é»è®¤çç¥å ¨å±éæµ */ DEFAULT, /** * æ ¹æ®è¯·æ±è IPè¿è¡éæµ */ IP } ruoyi-framework/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.annotation.RateLimiter; import com.ruoyi.common.enums.LimitType; 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; /** * éæµå¤ç * * @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.common.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(); } } ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java
@@ -89,4 +89,32 @@ return new RedissonSpringCacheManager(redissonClient, config, JsonJacksonCodec.INSTANCE); } @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;"; } } ruoyi-ui/src/utils/request.js
@@ -29,9 +29,9 @@ if (typeof value === 'object') { for (const key of Object.keys(value)) { if (value[key] !== null && typeof (value[key]) !== 'undefined') { let params = propName + '[' + key + ']' let subPart = encodeURIComponent(params) + '=' url += subPart + encodeURIComponent(value[key]) + '&' let params = propName + '[' + key + ']'; let subPart = encodeURIComponent(params) + '='; url += subPart + encodeURIComponent(value[key]) + '&'; } } } else { ruoyi-ui/src/utils/ruoyi.js
@@ -55,17 +55,17 @@ // æ·»å æ¥æèå´ export function addDateRange(params, dateRange, propName) { let search = params search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {} dateRange = Array.isArray(dateRange) ? dateRange : [] if (typeof (propName) === 'undefined') { search.params['beginTime'] = dateRange[0] search.params['endTime'] = dateRange[1] } else { search.params['begin' + propName] = dateRange[0] search.params['end' + propName] = dateRange[1] } return search let search = params; search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}; dateRange = Array.isArray(dateRange) ? dateRange : []; if (typeof (propName) === 'undefined') { search.params['beginTime'] = dateRange[0]; search.params['endTime'] = dateRange[1]; } else { search.params['begin' + propName] = dateRange[0]; search.params['end' + propName] = dateRange[1]; } return search; } // åæ¾æ°æ®åå ¸