第一步:spring-boot-ratelimit-redisson
<dependencies >
<!-- redisson-->
<dependency >
<groupId >org.redisson</groupId >
<artifactId >redisson-spring-boot-starter</artifactId >
<version >${redisson.version}</version >
</dependency >
<!-- 切面-->
<dependency >
<groupId >org.springframework.boot</groupId >
<artifactId >spring-boot-starter-aop</artifactId >
</dependency >
<!-- 常用工具类 -->
<dependency >
<groupId >org.apache.commons</groupId >
<artifactId >commons-lang3</artifactId >
</dependency >
<dependency >
<groupId >cn.hutool</groupId >
<artifactId >hutool-all</artifactId >
</dependency >
</dependencies >
spring :
redis :
# 地址
host : localhost
# 端口,默认为6379
port : 6379
# 数据库索引
database : 0
# 密码(如没有密码请注释掉)
# password:
# 连接超时时间
timeout : 10s
# 是否开启ssl
ssl : false
redisson :
# 线程池数量
threads : 4
# Netty线程池数量
nettyThreads : 8
# 单节点配置
singleServerConfig :
# 客户端名称
clientName : ${ruoyi.name}
# 最小空闲连接数
connectionMinimumIdleSize : 8
# 连接池大小
connectionPoolSize : 32
# 连接空闲超时,单位:毫秒
idleConnectionTimeout : 10000
# 命令等待超时,单位:毫秒
timeout : 3000
# 发布和订阅连接池大小
subscriptionConnectionPoolSize : 50
/**
* redis配置
*
* @author Lion Li
*/
@ Slf4j
@ Configuration
@ EnableCaching
@ EnableConfigurationProperties (RedissonProperties .class )
public class RedisConfig extends CachingConfigurerSupport {
@ Autowired
private RedissonProperties redissonProperties ;
@ Autowired
private ObjectMapper objectMapper ;
@ Bean
public RedissonAutoConfigurationCustomizer redissonCustomizer () {
return config -> {
config .setThreads (redissonProperties .getThreads ())
.setNettyThreads (redissonProperties .getNettyThreads ())
.setCodec (new JsonJacksonCodec (objectMapper ));
RedissonProperties .SingleServerConfig singleServerConfig = redissonProperties .getSingleServerConfig ();
if (ObjectUtil .isNotNull (singleServerConfig )) {
// 使用单机模式
config .useSingleServer ()
.setTimeout (singleServerConfig .getTimeout ())
.setClientName (singleServerConfig .getClientName ())
.setIdleConnectionTimeout (singleServerConfig .getIdleConnectionTimeout ())
.setSubscriptionConnectionPoolSize (singleServerConfig .getSubscriptionConnectionPoolSize ())
.setConnectionMinimumIdleSize (singleServerConfig .getConnectionMinimumIdleSize ())
.setConnectionPoolSize (singleServerConfig .getConnectionPoolSize ());
}
// 集群配置方式 参考下方注释
RedissonProperties .ClusterServersConfig clusterServersConfig = redissonProperties .getClusterServersConfig ();
if (ObjectUtil .isNotNull (clusterServersConfig )) {
config .useClusterServers ()
.setTimeout (clusterServersConfig .getTimeout ())
.setClientName (clusterServersConfig .getClientName ())
.setIdleConnectionTimeout (clusterServersConfig .getIdleConnectionTimeout ())
.setSubscriptionConnectionPoolSize (clusterServersConfig .getSubscriptionConnectionPoolSize ())
.setMasterConnectionMinimumIdleSize (clusterServersConfig .getMasterConnectionMinimumIdleSize ())
.setMasterConnectionPoolSize (clusterServersConfig .getMasterConnectionPoolSize ())
.setSlaveConnectionMinimumIdleSize (clusterServersConfig .getSlaveConnectionMinimumIdleSize ())
.setSlaveConnectionPoolSize (clusterServersConfig .getSlaveConnectionPoolSize ())
.setReadMode (clusterServersConfig .getReadMode ())
.setSubscriptionMode (clusterServersConfig .getSubscriptionMode ());
}
log .info ("初始化 redis 配置" );
};
}
}
@ Target (ElementType .METHOD )
@ Retention (RetentionPolicy .RUNTIME )
@ Documented
public @interface RateLimiter {
/**
* 限流key
*/
String key () default "rate_limit:" ;
/**
* 限流时间,单位秒
*/
int time () default 60 ;
/**
* 限流次数
*/
int count () default 100 ;
/**
* 限流类型
*/
LimitType limitType () default LimitType .DEFAULT ;
}
/**
* 限流处理
*
*/
@ Slf4j
@ Aspect
@ Component
public class RateLimiterAspect {
@ Autowired
private RedissonClient redissonClient ;
@ Before ("@annotation(rateLimiter)" )
public void doBefore (JoinPoint point , RateLimiter rateLimiter ) throws Throwable {
int time = rateLimiter .time ();
int count = rateLimiter .count ();
String combineKey = getCombineKey (rateLimiter , point );
try {
RateType rateType = RateType .OVERALL ;
if (rateLimiter .limitType () == LimitType .CLUSTER ) {
rateType = RateType .PER_CLIENT ;
}
long number = this .rateLimiter (combineKey , rateType , count , time );
if (number == -1 ) {
throw new RateException (1 , "操作频繁,限流中,稍后再试" );
}
log .info ("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'" , count , number , combineKey );
} catch (RateException 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 () == LimitType .IP ) {
// 获取请求ip
stringBuffer .append (ServletUtils .getClientIP ()).append ("-" );
} else if (rateLimiter .limitType () == LimitType .CLUSTER ) {
// 获取客户端实例id
stringBuffer .append (redissonClient .getId ()).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 ();
}
/**
* 限流
*
* @param key 限流key
* @param rateType 限流类型
* @param rate 速率
* @param rateInterval 速率间隔
* @return -1 表示失败
*/
public long rateLimiter (String key , RateType rateType , int rate , int rateInterval ) {
RRateLimiter rateLimiter = redissonClient .getRateLimiter (key );
rateLimiter .trySetRate (rateType , rate , rateInterval , RateIntervalUnit .SECONDS );
if (rateLimiter .tryAcquire ()) {
return rateLimiter .availablePermits ();
} else {
return -1L ;
}
}
}
public enum LimitType {
/**
* 默认策略全局限流
*/
DEFAULT ,
/**
* 根据请求者IP进行限流
*/
IP ,
/**
* 实例限流(集群多后端实例)
*/
CLUSTER
}
@ Getter
public class RateException extends RuntimeException {
private Integer code ;
public RateException (Integer code , String msg ) {
super (msg );
this .code = code ;
}
}
@ RestControllerAdvice
public class RateExceptionHandler {
@ ExceptionHandler (RateException .class )
public Dict rateException (RateException rateException ) {
Dict dict = new Dict ();
dict .put ("code" , rateException .getCode ());
dict .put ("msg" , rateException .getMessage ());
return dict ;
}
}
@ RestController
@ RequestMapping ("/limit" )
@ Slf4j
public class IndexController {
@ RateLimiter (count = 2 , time = 10 , limitType = LimitType .IP )
@ GetMapping ("/test" )
public Dict test () {
return Dict .create ().set ("msg" , "hello,world!" ).set ("description" , "别想一直看到我,不信你快速刷新看看~" );
}
@ RateLimiter (count = 2 , time = 10 )
@ GetMapping ("/test1" )
public Dict test1 () {
return Dict .create ().set ("msg" , "hello,world!" ).set ("description" , "别想一直看到我,不信你快速刷新看看~" );
}
}