refactor(security/limiter): 重构限流器,支持自定义限流器名称生成器

This commit is contained in:
2024-06-28 23:34:51 +08:00
parent 13b3f24845
commit 51c47751f4
14 changed files with 429 additions and 340 deletions

View File

@@ -28,7 +28,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Charles7c
* @since 1.0.0
*/
@ConfigurationProperties(prefix = "spring.data.redisson")
@ConfigurationProperties("spring.data.redisson")
public class RedissonProperties {
/**

View File

@@ -262,6 +262,16 @@ public class StringConstants {
*/
public static final String CHINESE_COMMA = "";
/**
* 圆括号(左) {@code "("}
*/
public static final String ROUND_BRACKET_START = "(";
/**
* 圆括号(右) {@code ")"}
*/
public static final String ROUND_BRACKET_END = ")";
/**
* 路径模式
*/

View File

@@ -8,32 +8,27 @@
</parent>
<artifactId>continew-starter-security-limiter</artifactId>
<description>ContiNew Starter 安全模块 - 限流模块</description>
<description>ContiNew Starter 安全模块 - 限流</description>
<dependencies>
<!-- Redisson不仅仅是一个 Redis Java 客户端Redisson 充分的利用了 Redis 键值数据库提供的一系列优势,为使用者提供了一系列具有分布式特性的常用工具:分布式锁、限流器等) -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<!--aop-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--Redisson缓存 模块-->
<!-- Hutool 加密解密模块(封装 JDK 中加密解密算法) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
</dependency>
<!-- Web 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-web</artifactId>
</dependency>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>

View File

@@ -33,40 +33,37 @@ import java.lang.annotation.*;
public @interface RateLimiter {
/**
* LimitType 限流模式
* DEFAULT 全局限流
* IP IP限流
* CLUSTER 实例限流
* 类型
*/
LimitType limitType() default LimitType.DEFAULT;
LimitType type() default LimitType.DEFAULT;
/**
* 缓存实例名称
* 名称
*/
String name() default "";
/**
* 限流key 支持 Spring EL 表达式
* 键(支持 Spring EL 表达式
*/
String key() default "";
/**
* 单位时间产生的令牌数
* 速率(指定时间间隔产生的令牌数
*/
int rate() default Integer.MAX_VALUE;
/**
* 限流时间
* 速率间隔(时间间隔)
*/
int rateInterval() default 0;
int interval() default 0;
/**
* 时间单位默认毫秒
* 速率间隔时间单位默认毫秒
*/
RateIntervalUnit timeUnit() default RateIntervalUnit.MILLISECONDS;
RateIntervalUnit unit() default RateIntervalUnit.MILLISECONDS;
/**
* 拒绝请求时的提示信息
* 提示信息
*/
String message() default "操作过于频繁,请稍后再试";
String message() default "操作过于频繁,请稍后再试";
}

View File

@@ -19,7 +19,7 @@ package top.continew.starter.security.limiter.annotation;
import java.lang.annotation.*;
/**
* 限流组
* 限流组注解
*
* @author KAI
* @since 2.2.0
@@ -28,8 +28,9 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimiters {
/**
* 用于管理多个 RateLimiter
* 限流组
*/
RateLimiter[] value();
}

View File

@@ -1,265 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.security.limiter.aop;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.extra.spring.SpringUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import top.continew.starter.security.limiter.annotation.RateLimiter;
import top.continew.starter.security.limiter.annotation.RateLimiters;
import top.continew.starter.security.limiter.autoconfigure.RateLimiterProperties;
import top.continew.starter.security.limiter.enums.LimitType;
import top.continew.starter.security.limiter.exception.RateLimiterException;
import top.continew.starter.web.util.ServletUtils;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
* 限流 AOP 拦截器
*
* @author KAI
* @since 2.2.0
*/
@Aspect
@Component
public class RateLimiterAspect {
private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
private static final ConcurrentHashMap<String, RRateLimiter> rateLimiterCache = new ConcurrentHashMap<>();
private static final RedissonClient CLIENT = SpringUtil.getBean(RedissonClient.class);
private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
private final ExpressionParser parser = new SpelExpressionParser();
private final ParserContext parserContext = new TemplateParserContext();
@Autowired
private RateLimiterProperties rateLimiterProperties;
/**
* 单个限流注解切点
*/
@Pointcut("@annotation(top.continew.starter.security.limiter.annotation.RateLimiter)")
public void rateLimiterSinglePointCut() {
}
/**
* 多个限流注解切点
*/
@Pointcut("@annotation(top.continew.starter.security.limiter.annotation.RateLimiters)")
public void rateLimiterBatchPointCut() {
}
/**
* 环绕通知,处理单个限流注解
*
* @param joinPoint 切点
* @param rateLimiter 限流注解
* @return 返回目标方法的执行结果
* @throws Throwable 异常
*/
@Around("@annotation(rateLimiter)")
public Object aroundSingle(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable {
// 未开启限流功能,直接执行目标方法
if (!rateLimiterProperties.isEnabled()) {
return joinPoint.proceed();
}
if (isRateLimited(joinPoint, rateLimiter)) {
throw new RateLimiterException(rateLimiter.message());
}
return joinPoint.proceed();
}
/**
* 环绕通知,处理多个限流注解
*
* @param joinPoint 切点
* @param rateLimiters 多个限流注解
* @return 返回目标方法的执行结果
* @throws Throwable 异常
*/
@Around("@annotation(rateLimiters)")
public Object aroundBatch(ProceedingJoinPoint joinPoint, RateLimiters rateLimiters) throws Throwable {
// 未开启限流功能,直接执行目标方法
if (!rateLimiterProperties.isEnabled()) {
return joinPoint.proceed();
}
for (RateLimiter rateLimiter : rateLimiters.value()) {
if (isRateLimited(joinPoint, rateLimiter)) {
throw new RateLimiterException(rateLimiter.message());
}
}
return joinPoint.proceed();
}
/**
* 执行限流逻辑
*
* @param joinPoint 切点
* @param rateLimiter 限流注解
* @throws Throwable 异常
*/
private boolean isRateLimited(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable {
try {
// 生成限流 Key
String redisKey = generateRedisKey(rateLimiter, joinPoint);
String encipherKey = SecureUtil.md5(redisKey);
// 确定限流类型
RateType rateType = rateLimiter.limitType() == LimitType.CLUSTER ? RateType.PER_CLIENT : RateType.OVERALL;
// 获取redisson限流实例
RRateLimiter rRateLimiter = getRateLimiter(encipherKey);
RateIntervalUnit rateIntervalUnit = rateLimiter.timeUnit();
int rateInterval = rateLimiter.rateInterval();
int rate = rateLimiter.rate();
// 判断是否需要更新限流器
if (shouldUpdateRateLimiter(rRateLimiter, rateType, rate, rateInterval, rateIntervalUnit)) {
// 更新限流器
rRateLimiter.setRate(rateType, rate, rateInterval, rateIntervalUnit);
}
// 尝试获取令牌
return !rRateLimiter.tryAcquire();
} catch (RateLimiterException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException("服务器限流异常,请稍候再试", e);
}
}
/**
* 获取 Redisson RateLimiter 实例
*
* @param key 限流器的 Key
* @return RateLimiter 实例
*/
private RRateLimiter getRateLimiter(String key) {
RRateLimiter rRateLimiter = rateLimiterCache.get(key);
if (rRateLimiter == null) {
// 直接创建 RateLimiter 实例
rRateLimiter = CLIENT.getRateLimiter(key);
rateLimiterCache.put(key, rRateLimiter);
}
return rRateLimiter;
}
/**
* 判断是否需要更新限流器配置
*
* @param rRateLimiter 现有的限流器
* @param rateType 限流类型OVERALL全局限流PER_CLIENT单机限流
* @param rate 速率(指定时间间隔产生的令牌数)
* @param rateInterval 速率间隔
* @param rateIntervalUnit 时间单位
* @return 是否需要更新配置
*/
private boolean shouldUpdateRateLimiter(RRateLimiter rRateLimiter,
RateType rateType,
long rate,
long rateInterval,
RateIntervalUnit rateIntervalUnit) {
RateLimiterConfig config = rRateLimiter.getConfig();
return !Objects.equals(config.getRateType(), rateType) || !Objects.equals(config.getRate(), rate) || !Objects
.equals(config.getRateInterval(), rateIntervalUnit.toMillis(rateInterval));
}
/**
* 获取限流Key
*
* @param rateLimiter RateLimiter实例
* @param point 切点
* @return 限流Key
*/
private String generateRedisKey(RateLimiter rateLimiter, JoinPoint point) {
// 获取限流器配置的 key
String key = rateLimiter.key();
// 如果 key 不为空,则解析表达式并获取最终的 key
key = Optional.ofNullable(key).map(k -> {
// 获取方法签名
MethodSignature signature = (MethodSignature)point.getSignature();
// 获取方法参数
Object[] args = point.getArgs();
// 创建表达式上下文
MethodBasedEvaluationContext context = new MethodBasedEvaluationContext(null, signature
.getMethod(), args, discoverer);
// 设置 Bean 解析器
context.setBeanResolver(new BeanFactoryResolver(SpringUtil.getBeanFactory()));
// 解析表达式
Expression expression;
if (StringUtils.startsWithIgnoreCase(k, parserContext.getExpressionPrefix()) && StringUtils
.endsWithIgnoreCase(k, parserContext.getExpressionSuffix())) {
expression = parser.parseExpression(k, parserContext);
} else {
expression = parser.parseExpression(k);
}
// 获取表达式结果
return expression.getValue(context, String.class);
}).orElse(key);
// 拼接最终的 key
StringBuilder redisKey = new StringBuilder(rateLimiterProperties.getLimiterKey()).append(ServletUtils
.getRequest()
.getRequestURI()).append(":");
//如果缓存name 不为空 则拼接上去
String name = rateLimiter.name();
if (StringUtils.hasText(name)) {
redisKey.append(name);
if (!name.endsWith(":")) {
redisKey.append(":");
}
}
// 根据限流类型添加不同的信息
switch (rateLimiter.limitType()) {
case IP:
// 获取请求 IP
redisKey.append(JakartaServletUtil.getClientIP(ServletUtils.getRequest())).append(":");
break;
case CLUSTER:
// 获取客户端实例 ID
redisKey.append(CLIENT.getId()).append(":");
break;
default:
break;
}
// 添加解析后的 key
return redisKey.append(key).toString();
}
}

View File

@@ -16,29 +16,45 @@
package top.continew.starter.security.limiter.autoconfigure;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import top.continew.starter.security.limiter.aop.RateLimiterAspect;
import org.springframework.context.annotation.ComponentScan;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.security.limiter.core.DefaultRateLimiterNameGenerator;
import top.continew.starter.security.limiter.core.RateLimiterNameGenerator;
/**
* 限流配置注入器
* 限流器自动配置
*
* @author KAI
* @author Charles7c
* @since 2.2.0
*/
@AutoConfiguration
@EnableConfigurationProperties(RateLimiterProperties.class)
@ComponentScan({"top.continew.starter.security.limiter.core"})
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_LIMITER, name = PropertiesConstants.ENABLED, matchIfMissing = true)
public class RateLimiterAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(RateLimiterAutoConfiguration.class);
/**
* 限流器名称生成器
*/
@Bean
public RateLimiterAspect rateLimiterAspect() {
log.info("[ContiNew Starter] - Auto Configuration 'RateLimiterAspect' completed initialization.");
return new RateLimiterAspect();
@ConditionalOnMissingBean
public RateLimiterNameGenerator nameGenerator() {
return new DefaultRateLimiterNameGenerator();
}
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Security-RateLimiter' completed initialization.");
}
}

View File

@@ -17,7 +17,7 @@
package top.continew.starter.security.limiter.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
import top.continew.starter.core.constant.PropertiesConstants;
/**
* 限流器配置属性
@@ -25,32 +25,19 @@ import org.springframework.util.StringUtils;
* @author KAI
* @since 2.2.0
*/
@ConfigurationProperties(prefix = "continew-starter.security.limiter")
@ConfigurationProperties(PropertiesConstants.SECURITY_LIMITER)
public class RateLimiterProperties {
private boolean enabled = false;
private String limiterKey = "RateLimiter:";
/**
* Key 前缀
*/
private String keyPrefix = "RateLimiter";
public boolean isEnabled() {
return enabled;
public String getKeyPrefix() {
return keyPrefix;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getLimiterKey() {
return limiterKey;
}
public void setLimiterKey(String limiterKey) {
//不为空且不以":"结尾,则添加":"
if (StringUtils.hasText(limiterKey)) {
if (!limiterKey.endsWith(":")) {
limiterKey = limiterKey + ":";
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
}
this.limiterKey = limiterKey;
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.security.limiter.core;
import cn.hutool.core.util.ClassUtil;
import top.continew.starter.core.constant.StringConstants;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
/**
* 默认限流器名称生成器
*
* @author Charles7c
* @since 2.2.0
*/
public class DefaultRateLimiterNameGenerator implements RateLimiterNameGenerator {
protected final ConcurrentHashMap<Method, String> nameMap = new ConcurrentHashMap<>();
@Override
public String generate(Object target, Method method, Object... args) {
return nameMap.computeIfAbsent(method, key -> {
final StringBuilder nameSb = new StringBuilder();
String className = method.getDeclaringClass().getName();
nameSb.append(ClassUtil.getShortClassName(className));
nameSb.append(StringConstants.DOT);
nameSb.append(method.getName());
nameSb.append(StringConstants.ROUND_BRACKET_START);
for (Class<?> clazz : method.getParameterTypes()) {
this.getDescriptor(nameSb, clazz);
}
nameSb.append(StringConstants.ROUND_BRACKET_END);
String str = nameSb.toString();
nameMap.put(method, str);
return str;
});
}
/**
* 获取指定数据类型的描述
*
* @param sb 名称字符串缓存
* @param typeClass 数据类型
*/
private void getDescriptor(final StringBuilder sb, final Class<?> typeClass) {
Class<?> clazz = typeClass;
while (true) {
if (clazz.isPrimitive()) {
sb.append(this.getPrimitiveChar(clazz));
return;
} else if (clazz.isArray()) {
sb.append(StringConstants.BRACKET_START);
clazz = clazz.getComponentType();
} else {
sb.append('L');
String name = clazz.getName();
name = ClassUtil.getShortClassName(name);
sb.append(name);
sb.append(StringConstants.COLON);
return;
}
}
}
/**
* 根据基本数据获取类型字符
*
* @param clazz 数据类型
* @return 类型字符
*/
private char getPrimitiveChar(Class<?> clazz) {
char c;
if (clazz == Integer.TYPE) {
c = 'I';
} else if (clazz == Void.TYPE) {
c = 'V';
} else if (clazz == Boolean.TYPE) {
c = 'Z';
} else if (clazz == Byte.TYPE) {
c = 'B';
} else if (clazz == Character.TYPE) {
c = 'C';
} else if (clazz == Short.TYPE) {
c = 'S';
} else if (clazz == Double.TYPE) {
c = 'D';
} else if (clazz == Float.TYPE) {
c = 'F';
} else {
c = 'J';
}
return c;
}
}

View File

@@ -0,0 +1,200 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.security.limiter.core;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.*;
import org.springframework.stereotype.Component;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.expression.ExpressionUtils;
import top.continew.starter.security.limiter.annotation.RateLimiter;
import top.continew.starter.security.limiter.annotation.RateLimiters;
import top.continew.starter.security.limiter.autoconfigure.RateLimiterProperties;
import top.continew.starter.security.limiter.enums.LimitType;
import top.continew.starter.security.limiter.exception.RateLimiterException;
import top.continew.starter.web.util.ServletUtils;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* 限流器切面
*
* @author KAI
* @author Charles7c
* @since 2.2.0
*/
@Aspect
@Component
public class RateLimiterAspect {
private static final ConcurrentHashMap<String, RRateLimiter> RATE_LIMITER_CACHE = new ConcurrentHashMap<>();
private final RateLimiterProperties properties;
private final RateLimiterNameGenerator nameGenerator;
private final RedissonClient redissonClient;
public RateLimiterAspect(RateLimiterProperties properties,
RateLimiterNameGenerator nameGenerator,
RedissonClient redissonClient) {
this.properties = properties;
this.nameGenerator = nameGenerator;
this.redissonClient = redissonClient;
}
/**
* 单个限流注解切点
*/
@Pointcut("@annotation(top.continew.starter.security.limiter.annotation.RateLimiter)")
public void rateLimiterPointCut() {
}
/**
* 多个限流注解切点
*/
@Pointcut("@annotation(top.continew.starter.security.limiter.annotation.RateLimiters)")
public void rateLimitersPointCut() {
}
/**
* 单限流场景
*
* @param joinPoint 切点
* @param rateLimiter 限流注解
* @return 目标方法的执行结果
* @throws Throwable /
*/
@Around("@annotation(rateLimiter)")
public Object aroundRateLimiter(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable {
if (isRateLimited(joinPoint, rateLimiter)) {
throw new RateLimiterException(rateLimiter.message());
}
return joinPoint.proceed();
}
/**
* 多限流场景
*
* @param joinPoint 切点
* @param rateLimiters 限流组注解
* @return 目标方法的执行结果
* @throws Throwable /
*/
@Around("@annotation(rateLimiters)")
public Object aroundRateLimiters(ProceedingJoinPoint joinPoint, RateLimiters rateLimiters) throws Throwable {
for (RateLimiter rateLimiter : rateLimiters.value()) {
if (isRateLimited(joinPoint, rateLimiter)) {
throw new RateLimiterException(rateLimiter.message());
}
}
return joinPoint.proceed();
}
/**
* 是否需要限流
*
* @param joinPoint 切点
* @param rateLimiter 限流注解
* @return true: 需要限流false不需要限流
*/
private boolean isRateLimited(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) {
try {
String cacheKey = this.getCacheKey(joinPoint, rateLimiter);
RRateLimiter rRateLimiter = RATE_LIMITER_CACHE.computeIfAbsent(cacheKey, key -> redissonClient
.getRateLimiter(cacheKey));
// 限流器配置
RateType rateType = rateLimiter.type() == LimitType.CLUSTER ? RateType.PER_CLIENT : RateType.OVERALL;
int rate = rateLimiter.rate();
int rateInterval = rateLimiter.interval();
RateIntervalUnit rateIntervalUnit = rateLimiter.unit();
// 判断是否需要更新限流器
if (this.isConfigurationUpdateNeeded(rRateLimiter, rateType, rate, rateInterval, rateIntervalUnit)) {
// 更新限流器
rRateLimiter.setRate(rateType, rate, rateInterval, rateIntervalUnit);
}
// 尝试获取令牌
return !rRateLimiter.tryAcquire();
} catch (Exception e) {
throw new RateLimiterException("服务器限流异常,请稍候再试", e);
}
}
/**
* 获取限流缓存 Key
*
* @param joinPoint 切点
* @param rateLimiter 限流注解
* @return 限流缓存 Key
*/
private String getCacheKey(JoinPoint joinPoint, RateLimiter rateLimiter) {
Object target = joinPoint.getTarget();
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
Method method = methodSignature.getMethod();
Object[] args = joinPoint.getArgs();
// 获取限流名称
String name = rateLimiter.name();
if (CharSequenceUtil.isBlank(name)) {
name = nameGenerator.generate(target, method, args);
}
// 解析限流 Key
String key = rateLimiter.key();
if (CharSequenceUtil.isNotBlank(key)) {
Object eval = ExpressionUtils.eval(key, target, method, args);
if (ObjectUtil.isNull(eval)) {
throw new RateLimiterException("限流 Key 解析错误");
}
key = Convert.toStr(eval);
}
// 获取后缀
String suffix = switch (rateLimiter.type()) {
case IP -> JakartaServletUtil.getClientIP(ServletUtils.getRequest());
case CLUSTER -> redissonClient.getId();
default -> StringConstants.EMPTY;
};
return RedisUtils.formatKey(properties.getKeyPrefix(), name, key, suffix);
}
/**
* 判断是否需要更新限流器配置
*
* @param rRateLimiter 限流器
* @param rateType 限流类型OVERALL全局限流PER_CLIENT单机限流
* @param rate 速率(指定时间间隔产生的令牌数)
* @param rateInterval 速率间隔
* @param rateIntervalUnit 时间单位
* @return 是否需要更新配置
*/
private boolean isConfigurationUpdateNeeded(RRateLimiter rRateLimiter,
RateType rateType,
long rate,
long rateInterval,
RateIntervalUnit rateIntervalUnit) {
RateLimiterConfig config = rRateLimiter.getConfig();
return !Objects.equals(config.getRateType(), rateType) || !Objects.equals(config.getRate(), rate) || !Objects
.equals(config.getRateInterval(), rateIntervalUnit.toMillis(rateInterval));
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.security.limiter.core;
import java.lang.reflect.Method;
/**
* 限流器名称生成器
*
* @author Charles7c
* @since 2.2.0
*/
@FunctionalInterface
public interface RateLimiterNameGenerator {
/**
* Generate a rate limiter name for the given method and its parameters.
*
* @param target the target instance
* @param method the method being called
* @param args the method parameters (with any var-args expanded)
* @return a generated rate limiter name
*/
String generate(Object target, Method method, Object... args);
}

View File

@@ -28,10 +28,12 @@ public enum LimitType {
* 全局限流
*/
DEFAULT,
/**
* 根据 IP 限流
*/
IP,
/**
* 根据实例限流(支持集群多实例)
*/

View File

@@ -25,7 +25,12 @@ import top.continew.starter.core.exception.BaseException;
* @since 2.2.0
*/
public class RateLimiterException extends BaseException {
public RateLimiterException(String message) {
super(message);
}
public RateLimiterException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -26,12 +26,5 @@
<groupId>top.continew</groupId>
<artifactId>continew-starter-core</artifactId>
</dependency>
<!-- Web 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-web</artifactId>
</dependency>
</dependencies>
</project>