ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
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/Excels.java
@@ -14,5 +14,5 @@ @Retention(RetentionPolicy.RUNTIME) public @interface Excels { Excel[] value(); public Excel[] value(); } 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
@@ -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;"; } } 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; } // 回显数据字典