parent
0e994a168c
commit
0914ee4f37
@ -0,0 +1,35 @@
|
|||||||
|
package com.xjs.annotation;
|
||||||
|
|
||||||
|
import com.xjs.enums.InterfaceLimitType;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接口限流注解
|
||||||
|
* @author xiejs
|
||||||
|
* @since 2022-05-16
|
||||||
|
*/
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface RateLimiter {
|
||||||
|
/**
|
||||||
|
* 限流key
|
||||||
|
*/
|
||||||
|
String key() default "rate_limit:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限流时间,单位秒
|
||||||
|
*/
|
||||||
|
int time() default 60;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限流次数
|
||||||
|
*/
|
||||||
|
int count() default 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限流类型
|
||||||
|
*/
|
||||||
|
InterfaceLimitType limitType() default InterfaceLimitType.DEFAULT;
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package com.xjs.aop;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.utils.ip.IpUtils;
|
||||||
|
import com.xjs.annotation.RateLimiter;
|
||||||
|
import com.xjs.enums.InterfaceLimitType;
|
||||||
|
import com.xjs.exception.BusinessException;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
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.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 org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接口限流aop
|
||||||
|
* @author xiejs
|
||||||
|
* @since 2022-05-16
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
@Log4j2
|
||||||
|
public class RateLimiterAspect {
|
||||||
|
@Autowired
|
||||||
|
private RedisTemplate<Object, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisScript<Long> limitScript;
|
||||||
|
|
||||||
|
@Before("@annotation(rateLimiter)")
|
||||||
|
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
|
||||||
|
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 (number==null || number.intValue() > count) {
|
||||||
|
throw new BusinessException("访问过于频繁,请稍候再试");
|
||||||
|
}
|
||||||
|
log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key);
|
||||||
|
} catch (BusinessException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("服务器限流异常,请稍候再试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
|
||||||
|
StringBuilder stringBuffer = new StringBuilder(rateLimiter.key());
|
||||||
|
if (rateLimiter.limitType() == InterfaceLimitType.IP) {
|
||||||
|
stringBuffer.append(IpUtils.getIpAddr(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest())).append("-");
|
||||||
|
}
|
||||||
|
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||||
|
Method method = signature.getMethod();
|
||||||
|
Class<?> targetClass = method.getDeclaringClass();
|
||||||
|
stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
|
||||||
|
return stringBuffer.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.xjs.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接口限流类别枚举类
|
||||||
|
* @author xiejs
|
||||||
|
* @since 2022-05-16
|
||||||
|
*/
|
||||||
|
public enum InterfaceLimitType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认策略全局限流
|
||||||
|
*/
|
||||||
|
DEFAULT,
|
||||||
|
/**
|
||||||
|
* 根据请求者IP进行限流
|
||||||
|
*/
|
||||||
|
IP
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.xjs.script;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
|
import org.springframework.scripting.support.ResourceScriptSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* redis 脚本执行
|
||||||
|
* @author xiejs
|
||||||
|
* @since 2022-05-16
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class RedisScript {
|
||||||
|
@Bean
|
||||||
|
public DefaultRedisScript<Long> limitScript() {
|
||||||
|
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
|
||||||
|
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua")));
|
||||||
|
redisScript.setResultType(Long.class);
|
||||||
|
return redisScript;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
local key = KEYS[1]
|
||||||
|
local count = tonumber(ARGV[1])
|
||||||
|
local time = tonumber(ARGV[2])
|
||||||
|
local current = redis.call('get', key)
|
||||||
|
if current and tonumber(current) > count then
|
||||||
|
return tonumber(current)
|
||||||
|
end
|
||||||
|
current = redis.call('incr', key)
|
||||||
|
if tonumber(current) == 1 then
|
||||||
|
redis.call('expire', key, time)
|
||||||
|
end
|
||||||
|
return tonumber(current)
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue