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