From 383f6fbb322ce86bb99a05febaaf54fd7d556f6a Mon Sep 17 00:00:00 2001 From: zk020106 <1373639299@qq.com> Date: Fri, 24 May 2024 09:16:57 +0800 Subject: [PATCH 01/14] =?UTF-8?q?feat(continew-starter-messaging-mail):?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8A=A8=E6=80=81=E9=82=AE=E7=AE=B1=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DynamicMailSenderAutoConfiguration.java | 109 +++++++++++ .../messaging/mail/core/MailConfig.java | 185 ++++++++++++++++++ .../messaging/mail/util/MailUtils.java | 27 ++- 3 files changed, 316 insertions(+), 5 deletions(-) create mode 100644 continew-starter-messaging/continew-starter-messaging-mail/src/main/java/top/continew/starter/messaging/mail/core/DynamicMailSenderAutoConfiguration.java create mode 100644 continew-starter-messaging/continew-starter-messaging-mail/src/main/java/top/continew/starter/messaging/mail/core/MailConfig.java diff --git a/continew-starter-messaging/continew-starter-messaging-mail/src/main/java/top/continew/starter/messaging/mail/core/DynamicMailSenderAutoConfiguration.java b/continew-starter-messaging/continew-starter-messaging-mail/src/main/java/top/continew/starter/messaging/mail/core/DynamicMailSenderAutoConfiguration.java new file mode 100644 index 00000000..a6745b08 --- /dev/null +++ b/continew-starter-messaging/continew-starter-messaging-mail/src/main/java/top/continew/starter/messaging/mail/core/DynamicMailSenderAutoConfiguration.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.messaging.mail.core; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.spring.SpringUtil; +import jakarta.mail.Authenticator; +import jakarta.mail.PasswordAuthentication; +import jakarta.mail.Session; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +/** + * 邮件发送器 + * + * @author KAI + * @since 1.0.0 + */ +public class DynamicMailSenderAutoConfiguration extends JavaMailSenderImpl { + private static final Logger log = LoggerFactory.getLogger(DynamicMailSenderAutoConfiguration.class); + + // 邮件会话属性 + private final Properties javaMailProperties; + + /** + * 构造函数,使用给定的邮件会话属性初始化邮件发送器。 + * + * @param javaMailProperties 邮件会话属性 + */ + public DynamicMailSenderAutoConfiguration(Properties javaMailProperties) { + super(); // 调用父类的构造函数 + this.javaMailProperties = javaMailProperties; + + // 设置用户名和密码 + setUsername(javaMailProperties.getProperty("mail.smtp.username")); + setPassword(javaMailProperties.getProperty("mail.smtp.password")); + + // 设置默认编码 + String defaultEncoding = javaMailProperties.getProperty("mail.default-encoding", "utf-8"); + setDefaultEncoding(defaultEncoding); + } + + /** + * 获取邮件会话。 + * + * @return 邮件会话 + */ + @Override + public Session getSession() { + if (ObjectUtil.isNotEmpty(javaMailProperties)) { + // 使用提供的身份验证器创建邮件会话 + log.info("[ContiNew Starter] DynamicMailSenderAutoConfiguration creating session with properties: {}", javaMailProperties); + return Session.getInstance(javaMailProperties, new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + // 返回用户名和密码认证信息 + return new PasswordAuthentication(javaMailProperties + .getProperty("mail.username"), javaMailProperties.getProperty("mail.password")); + } + }); + } + // 如果没有提供属性,使用父类的会话 + return super.getSession(); + } + + /** + * 创建默认的邮件发送器。 + * + * @return 默认的邮件发送器 + */ + public static JavaMailSender createDefault() { + return SpringUtil.getBean(JavaMailSender.class); + } + + /** + * 根据邮件配置构建邮件发送器。 + * + * @param mailConfig 邮件配置对象 + * @return 构建的邮件发送器 + */ + public static JavaMailSender build(MailConfig mailConfig) { + if (mailConfig != null) { + Properties properties = mailConfig.toJavaMailProperties(); + log.info("[ContiNew Starter] DynamicMailSenderAutoConfiguration build with mailConfig"); + return new DynamicMailSenderAutoConfiguration(properties); + } else { + log.error("[ContiNew Starter] Mail configuration is null, using default mail configuration."); + return createDefault(); + } + } +} \ No newline at end of file diff --git a/continew-starter-messaging/continew-starter-messaging-mail/src/main/java/top/continew/starter/messaging/mail/core/MailConfig.java b/continew-starter-messaging/continew-starter-messaging-mail/src/main/java/top/continew/starter/messaging/mail/core/MailConfig.java new file mode 100644 index 00000000..63ae1225 --- /dev/null +++ b/continew-starter-messaging/continew-starter-messaging-mail/src/main/java/top/continew/starter/messaging/mail/core/MailConfig.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.messaging.mail.core; + +import top.continew.starter.core.util.validate.ValidationUtils; + +import java.util.Properties; + +/** + * 邮件配置类 + * + * @author KAI + * @since 1.0.0 + */ +public class MailConfig { + + /** + * 邮件发送协议 (例如: smtp, smtps) + */ + private String mailProtocol = "smtp"; + + /** + * SMTP 服务器地址 + */ + private String mailHost; + + /** + * SMTP 服务器端口 + */ + private int mailPort; + + /** + * SMTP 用户名 + */ + private String mailUsername; + + /** + * SMTP 授权码 + */ + private String mailPassword; + + /** + * 发件人邮箱地址 + */ + private String mailFrom; + + /** + * 是否启用 SSL 连接 + */ + private boolean sslEnabled = false; + + /** + * SSL 端口 + */ + private int sslPort; + + public String getMailProtocol() { + return mailProtocol; + } + + public void setMailProtocol(String mailProtocol) { + this.mailProtocol = mailProtocol; + } + + public String getMailHost() { + return mailHost; + } + + public void setMailHost(String mailHost) { + this.mailHost = mailHost; + } + + public int getMailPort() { + return mailPort; + } + + public void setMailPort(int mailPort) { + this.mailPort = mailPort; + } + + public String getMailUsername() { + return mailUsername; + } + + public void setMailUsername(String mailUsername) { + this.mailUsername = mailUsername; + } + + public String getMailPassword() { + return mailPassword; + } + + public void setMailPassword(String mailPassword) { + this.mailPassword = mailPassword; + } + + public String getMailFrom() { + return mailFrom; + } + + public void setMailFrom(String mailFrom) { + this.mailFrom = mailFrom; + } + + public boolean isSslEnabled() { + return sslEnabled; + } + + public void setSslEnabled(boolean sslEnabled) { + this.sslEnabled = sslEnabled; + } + + public int getSslPort() { + return sslPort; + } + + public void setSslPort(int sslPort) { + this.sslPort = sslPort; + } + + /** + * 将当前配置转换为 JavaMail 的 Properties 对象 + * + * @return 配置好的 Properties 对象 + */ + public Properties toJavaMailProperties() { + Properties javaMailProperties = new Properties(); + + ValidationUtils.throwIf(!this.mailProtocol.equals("smtp"), "不支持的邮件发送协议: " + this.mailProtocol); + // 设置邮件发送协议 + javaMailProperties.put("mail.transport.protocol", this.mailProtocol.toLowerCase()); + + // 设置 SMTP 服务器地址 + ValidationUtils.throwIfBlank(this.mailHost, "SMTP服务器地址不能为空"); + javaMailProperties.put("mail.smtp.host", this.mailHost); + + // 设置 SMTP 服务器端口 + ValidationUtils.throwIfNull(this.mailPort, "SMTP服务端口不能为空"); + javaMailProperties.put("mail.smtp.port", this.mailPort); + + // 设置 SMTP 用户名 + ValidationUtils.throwIfBlank(this.mailUsername, "SMTP用户名不能为空"); + javaMailProperties.put("mail.smtp.user", this.mailUsername); + + // 设置 SMTP 授权码 + ValidationUtils.throwIfBlank(this.mailPassword, "SMTP授权码不能为空"); + javaMailProperties.put("mail.smtp.password", this.mailPassword); + + // 启用 SMTP 认证 + javaMailProperties.put("mail.smtp.auth", "true"); + + // 设置 SSL 连接 + javaMailProperties.put("mail.smtp.ssl.enable", this.sslEnabled); + + // 设置默认发件人地址 + ValidationUtils.throwIfBlank(this.mailFrom, "默认发件人地址不能为空"); + javaMailProperties.put("mail.from", this.mailFrom); + + // 设置默认编码为 UTF-8 + javaMailProperties.put("mail.defaultEncoding", "utf-8"); + + // 如果启用 SSL,设置 SSL Socket 工厂和端口 + if (sslEnabled) { + ValidationUtils.throwIfNull(this.sslPort, "SSL端口不能为空"); + javaMailProperties.put("mail.smtp.socketFactory.port", this.sslPort); + javaMailProperties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + } + + return javaMailProperties; + } +} \ No newline at end of file diff --git a/continew-starter-messaging/continew-starter-messaging-mail/src/main/java/top/continew/starter/messaging/mail/util/MailUtils.java b/continew-starter-messaging/continew-starter-messaging-mail/src/main/java/top/continew/starter/messaging/mail/util/MailUtils.java index 3554177d..9f36456d 100644 --- a/continew-starter-messaging/continew-starter-messaging-mail/src/main/java/top/continew/starter/messaging/mail/util/MailUtils.java +++ b/continew-starter-messaging/continew-starter-messaging-mail/src/main/java/top/continew/starter/messaging/mail/util/MailUtils.java @@ -18,14 +18,17 @@ package top.continew.starter.messaging.mail.util; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.extra.spring.SpringUtil; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.util.StringUtils; import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.messaging.mail.core.MailConfig; +import top.continew.starter.messaging.mail.core.DynamicMailSenderAutoConfiguration; import java.io.File; import java.nio.charset.StandardCharsets; @@ -39,9 +42,14 @@ import java.util.List; * @author Charles7c * @since 1.0.0 */ + public class MailUtils { - private static final JavaMailSender MAIL_SENDER = SpringUtil.getBean(JavaMailSender.class); + private static MailConfig mailConfig; // 邮件配置 + + public static void setMailConfig(MailConfig mailConfig) { + MailUtils.mailConfig = mailConfig; + } private MailUtils() { } @@ -157,11 +165,20 @@ public class MailUtils { boolean isHtml, File... files) throws MessagingException { Assert.isFalse(CollUtil.isEmpty(tos), "请至少指定一名收件人"); - MimeMessage mimeMessage = MAIL_SENDER.createMimeMessage(); + JavaMailSender mailSender = DynamicMailSenderAutoConfiguration.build(mailConfig); + String form; + if (mailConfig != null && StringUtils.hasText(mailConfig.getMailFrom())) { + form = mailConfig.getMailFrom(); + } else { + form = SpringUtil.getProperty("spring.mail.username"); + } + MimeMessage mimeMessage = mailSender.createMimeMessage(); + // 创建邮件发送器 MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true, StandardCharsets.UTF_8 .displayName()); + // 设置基本信息 - messageHelper.setFrom(SpringUtil.getProperty("spring.mail.username")); + messageHelper.setFrom(form); messageHelper.setSubject(subject); messageHelper.setText(content, isHtml); // 设置收信人 @@ -182,7 +199,7 @@ public class MailUtils { } } // 发送邮件 - MAIL_SENDER.send(mimeMessage); + mailSender.send(mimeMessage); } /** -- Gitee From 561208a1d483489ee36f3c6634703f48ed377953 Mon Sep 17 00:00:00 2001 From: zk020106 <1373639299@qq.com> Date: Tue, 28 May 2024 15:48:35 +0800 Subject: [PATCH 02/14] =?UTF-8?q?feat(continew-starter-security-limiter):?= =?UTF-8?q?=E9=99=90=E6=B5=81=E6=8B=A6=E6=88=AA=E6=B3=A8=E8=A7=A3=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- continew-starter-dependencies/pom.xml | 6 + .../continew-starter-security-limiter/pom.xml | 31 ++++ .../limiter/annotation/RateLimiter.java | 60 +++++++ .../limiter/annotation/RateLimiters.java | 29 ++++ .../limiter/aop/RateLimiterAspect.java | 156 ++++++++++++++++++ .../converter/RateLimiterKeyConverter.java | 96 +++++++++++ .../exception/RateLimiterException.java | 25 +++ continew-starter-security/pom.xml | 1 + 8 files changed, 404 insertions(+) create mode 100644 continew-starter-security/continew-starter-security-limiter/pom.xml create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiters.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/exception/RateLimiterException.java diff --git a/continew-starter-dependencies/pom.xml b/continew-starter-dependencies/pom.xml index eb95bf9b..417be71b 100644 --- a/continew-starter-dependencies/pom.xml +++ b/continew-starter-dependencies/pom.xml @@ -458,6 +458,12 @@ ${revision} + + top.continew + continew-starter-security-limiter + ${revision} + + top.continew diff --git a/continew-starter-security/continew-starter-security-limiter/pom.xml b/continew-starter-security/continew-starter-security-limiter/pom.xml new file mode 100644 index 00000000..6b65e7ce --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + top.continew + continew-starter-security + ${revision} + + + continew-starter-security-limiter + ContiNew Starter 安全模块 - 限流模块 + + + + + org.redisson + redisson-spring-boot-starter + + + + org.aspectj + aspectjrt + 1.9.4 + + + org.aspectj + aspectjweaver + 1.9.4 + + + diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java new file mode 100644 index 00000000..59a7fe70 --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.annotation; + +import org.redisson.api.RateIntervalUnit; +import org.redisson.api.RateType; + +import java.lang.annotation.*; + +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface RateLimiter { + + /** + * RateLimiter 限流模式 + * OVERALL 所有客户端加总限流 + * PER_CLIENT 每个客户端单独计算流量 + */ + RateType mode() default RateType.PER_CLIENT; + + /** + * Spel表达式 + */ + String[] keys() default {}; + + /** + * 单位时间产生的令牌数,默认100个 例如 rateInterval = 1000 rate = 100 timeUnit= RateIntervalUnit.MILLISECONDS 表示每1000毫秒产生100个令牌 + */ + long rate() default 100; + + /** + * 时间间隔,默认1000 + */ + long rateInterval() default 1000; + + /** + * 时间单位,默认毫秒 + */ + RateIntervalUnit timeUnit() default RateIntervalUnit.MILLISECONDS; + + /** + * 拒绝请求时的提示信息 + */ + String notAllowMessage() default "系统繁忙,请稍候再试"; +} diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiters.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiters.java new file mode 100644 index 00000000..08da165b --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiters.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.annotation; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface RateLimiters { + /** + * 用于管理多个 RateLimiter + */ + RateLimiter[] value(); +} diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java new file mode 100644 index 00000000..778d2ad6 --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 jakarta.annotation.PostConstruct; +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.RRateLimiter; +import org.redisson.api.RateIntervalUnit; +import org.redisson.api.RedissonClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import top.continew.starter.security.limiter.annotation.RateLimiter; +import top.continew.starter.security.limiter.annotation.RateLimiters; +import top.continew.starter.security.limiter.converter.RateLimiterKeyConverter; +import top.continew.starter.security.limiter.exception.RateLimiterException; + +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; +/** + * 限流 AOP 拦截器 + */ +@Aspect +@Component +public class RateLimiterAspect { + + private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); + + @Autowired + private RedissonClient redissonClient; + + @Autowired + private RateLimiterKeyConverter rateLimiterKeyConverter; + + /** + * 初始化方法 + */ + @PostConstruct + public void init() { + log.warn("[ContiNew Starter] - RateLimiterAspect has been initialized!"); + } + + /** + * 单个限流注解切点 + */ + @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 切点 + * @return 返回目标方法的执行结果 + * @throws Throwable 异常 + */ + @Around("rateLimiterSinglePointCut()") + public Object aroundSingle(ProceedingJoinPoint joinPoint) throws Throwable { + // 获取目标方法上的 RateLimiter 注解 + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + Method method = methodSignature.getMethod(); + RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class); + isRateLimiter(joinPoint, rateLimiter); + return joinPoint.proceed(); + } + + /** + * 环绕通知,处理多个限流注解 + * + * @param joinPoint 切点 + * @return 返回目标方法的执行结果 + * @throws Throwable 异常 + */ + @Around("rateLimiterBatchPointCut()") + public Object aroundBatch(ProceedingJoinPoint joinPoint) throws Throwable { + // 获取目标方法上的 RateLimiters 注解 + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + Method method = methodSignature.getMethod(); + RateLimiters rateLimiters = method.getAnnotation(RateLimiters.class); + RateLimiter[] rateLimiterList = rateLimiters.value(); + for (RateLimiter rateLimiter : rateLimiterList) { + isRateLimiter(joinPoint, rateLimiter); + } + return joinPoint.proceed(); + } + + /** + * 执行限流逻辑 + * + * @param joinPoint 切点 + * @param rateLimiter 限流注解 + * @throws Throwable 异常 + */ + public void isRateLimiter(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable { + // 生成限流 Key + String key = rateLimiterKeyConverter.getKey(joinPoint, rateLimiter); + + // 获取 Redisson RateLimiter 实例 + RRateLimiter rateLimiterInstance = redissonClient.getRateLimiter(key); + + // 设置限流规则 + rateLimiterInstance.trySetRate(rateLimiter.mode(), rateLimiter.rate(), rateLimiter.rateInterval(), rateLimiter + .timeUnit()); + + // 尝试获取令牌 + boolean tryAcquire = rateLimiterInstance.tryAcquire(1, rateLimiter.rateInterval(), toTimeUnit(rateLimiter + .timeUnit())); + + // 如果获取不到令牌,则抛出限流异常 + if (!tryAcquire) { + throw new RateLimiterException(rateLimiter.notAllowMessage()); + } + } + + /** + * 将 RateIntervalUnit 转换为 TimeUnit + * + * @param rateIntervalUnit RateIntervalUnit 时间单位 + * @return TimeUnit 时间单位 + */ + public static TimeUnit toTimeUnit(RateIntervalUnit rateIntervalUnit) { + return switch (rateIntervalUnit) { + case MILLISECONDS -> TimeUnit.MILLISECONDS; + case SECONDS -> TimeUnit.SECONDS; + case MINUTES -> TimeUnit.MINUTES; + case HOURS -> TimeUnit.HOURS; + case DAYS -> TimeUnit.DAYS; + }; + } +} \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java new file mode 100644 index 00000000..b5a86ce3 --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java @@ -0,0 +1,96 @@ +package top.continew.starter.security.limiter.converter; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.expression.MethodBasedEvaluationContext; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.ExpressionParser; +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 java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * 限流Key生成器 + */ +@Component +public class RateLimiterKeyConverter { + + private static final Logger log = LoggerFactory.getLogger(RateLimiterKeyConverter.class); + + private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); + + private final ExpressionParser parser = new SpelExpressionParser(); + + /** + * 生成限流 Key + * + * @param joinPoint 切点 + * @param rateLimiter 限流注解 + * @return 限流 Key + */ + public String getKey(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) { + + // 获取目标方法 + Method method = getMethod(joinPoint); + + // 解析 SpEL 表达式 + List keys = getSpelDefinitionKey(rateLimiter.keys(), method, joinPoint.getArgs()); + + // 拼接限流 Key + return StringUtils.collectionToDelimitedString(keys, ".", "", ""); + } + + /** + * 获取目标方法 + * + * @param joinPoint 切点 + * @return 目标方法 + */ + public Method getMethod(ProceedingJoinPoint joinPoint) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + + // 如果目标方法是接口方法,则获取其实现类的方法 + if (method.getDeclaringClass().isInterface()) { + try { + method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), + method.getParameterTypes()); + } catch (Exception e) { + log.error(null, e); + } + } + return method; + } + + /** + * 解析 SpEL 表达式 + * + * @param definitionKeys SpEL 表达式数组 + * @param method 目标方法 + * @param parameterValues 方法参数值 + * @return 解析后的 SpEL 表达式值列表 + */ + private List getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) { + List keyList = new ArrayList<>(); + for (String definitionKey : definitionKeys) { + if (StringUtils.hasText(definitionKey)) { + // 创建 SpEL 表达式上下文 + EvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, discoverer); + // 解析 SpEL 表达式并获取值 + String key = Objects.requireNonNull(parser.parseExpression(definitionKey).getValue(context)).toString(); + keyList.add(key); + } + } + return keyList; + } +} \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/exception/RateLimiterException.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/exception/RateLimiterException.java new file mode 100644 index 00000000..49467e0a --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/exception/RateLimiterException.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.exception; + +import top.continew.starter.core.exception.BaseException; + +public class RateLimiterException extends BaseException { + public RateLimiterException(String message) { + super(message); + } +} diff --git a/continew-starter-security/pom.xml b/continew-starter-security/pom.xml index dd87ac2f..1a0f4f54 100644 --- a/continew-starter-security/pom.xml +++ b/continew-starter-security/pom.xml @@ -17,6 +17,7 @@ continew-starter-security-password continew-starter-security-mask continew-starter-security-crypto + continew-starter-security-limiter -- Gitee From a4cdb62d0b90adb2d1054b9df14c58fcf83cdd69 Mon Sep 17 00:00:00 2001 From: zk020106 <1373639299@qq.com> Date: Tue, 28 May 2024 19:08:43 +0800 Subject: [PATCH 03/14] =?UTF-8?q?feat(continew-starter-security-limiter):?= =?UTF-8?q?=E9=99=90=E6=B5=81=E6=8B=A6=E6=88=AA=E6=B3=A8=E8=A7=A3=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../limiter/aop/RateLimiterAspect.java | 25 ++++------- .../RateLimiterAutoConfiguration.java | 36 ++++++++++++++++ .../converter/RateLimiterKeyConverter.java | 42 ++++++++++++++++--- ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../exception/GlobalExceptionHandler.java | 8 ++++ 5 files changed, 89 insertions(+), 23 deletions(-) create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java index 778d2ad6..9bd65c68 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java @@ -16,7 +16,6 @@ package top.continew.starter.security.limiter.aop; -import jakarta.annotation.PostConstruct; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @@ -36,6 +35,7 @@ import top.continew.starter.security.limiter.exception.RateLimiterException; import java.lang.reflect.Method; import java.util.concurrent.TimeUnit; + /** * 限流 AOP 拦截器 */ @@ -51,14 +51,6 @@ public class RateLimiterAspect { @Autowired private RateLimiterKeyConverter rateLimiterKeyConverter; - /** - * 初始化方法 - */ - @PostConstruct - public void init() { - log.warn("[ContiNew Starter] - RateLimiterAspect has been initialized!"); - } - /** * 单个限流注解切点 */ @@ -83,7 +75,7 @@ public class RateLimiterAspect { @Around("rateLimiterSinglePointCut()") public Object aroundSingle(ProceedingJoinPoint joinPoint) throws Throwable { // 获取目标方法上的 RateLimiter 注解 - MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); Method method = methodSignature.getMethod(); RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class); isRateLimiter(joinPoint, rateLimiter); @@ -100,7 +92,7 @@ public class RateLimiterAspect { @Around("rateLimiterBatchPointCut()") public Object aroundBatch(ProceedingJoinPoint joinPoint) throws Throwable { // 获取目标方法上的 RateLimiters 注解 - MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); Method method = methodSignature.getMethod(); RateLimiters rateLimiters = method.getAnnotation(RateLimiters.class); RateLimiter[] rateLimiterList = rateLimiters.value(); @@ -119,18 +111,17 @@ public class RateLimiterAspect { */ public void isRateLimiter(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable { // 生成限流 Key - String key = rateLimiterKeyConverter.getKey(joinPoint, rateLimiter); - + String key = this.rateLimiterKeyConverter.getKey(joinPoint, rateLimiter); // 获取 Redisson RateLimiter 实例 - RRateLimiter rateLimiterInstance = redissonClient.getRateLimiter(key); + RRateLimiter rateLimiterInstance = this.redissonClient.getRateLimiter(key); // 设置限流规则 rateLimiterInstance.trySetRate(rateLimiter.mode(), rateLimiter.rate(), rateLimiter.rateInterval(), rateLimiter - .timeUnit()); + .timeUnit()); // 尝试获取令牌 - boolean tryAcquire = rateLimiterInstance.tryAcquire(1, rateLimiter.rateInterval(), toTimeUnit(rateLimiter - .timeUnit())); + boolean tryAcquire = rateLimiterInstance.tryAcquire(1L, rateLimiter.rateInterval(), toTimeUnit(rateLimiter + .timeUnit())); // 如果获取不到令牌,则抛出限流异常 if (!tryAcquire) { diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java new file mode 100644 index 00000000..f8d754d1 --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.autoconfigure; + +import jakarta.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.ComponentScan; + +@ComponentScan(basePackages = {"top.continew.starter.security.limiter.aop", + "top.continew.starter.security.limiter.converter"}) +@AutoConfiguration +public class RateLimiterAutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(RateLimiterAutoConfiguration.class); + + @PostConstruct + public void init() { + log.info("[ContiNew Starter] - Auto Configuration 'RateLimiterAspect' completed initialization."); + } +} diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java index b5a86ce3..f41ac99a 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.converter; import org.aspectj.lang.ProceedingJoinPoint; @@ -9,6 +25,7 @@ import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.ExpressionParser; +import org.springframework.expression.ParseException; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -57,14 +74,15 @@ public class RateLimiterKeyConverter { * @return 目标方法 */ public Method getMethod(ProceedingJoinPoint joinPoint) { - MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + MethodSignature signature = (MethodSignature)joinPoint.getSignature(); Method method = signature.getMethod(); // 如果目标方法是接口方法,则获取其实现类的方法 if (method.getDeclaringClass().isInterface()) { try { - method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), - method.getParameterTypes()); + method = joinPoint.getTarget() + .getClass() + .getDeclaredMethod(signature.getName(), method.getParameterTypes()); } catch (Exception e) { log.error(null, e); } @@ -82,15 +100,27 @@ public class RateLimiterKeyConverter { */ private List getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) { List keyList = new ArrayList<>(); + //如果key为空则用类名+方法名称 + if (definitionKeys == null || definitionKeys.length == 0) { + keyList.add(method.getDeclaringClass().getName() + "." + method.getName()); + return keyList; + } for (String definitionKey : definitionKeys) { if (StringUtils.hasText(definitionKey)) { // 创建 SpEL 表达式上下文 EvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, discoverer); - // 解析 SpEL 表达式并获取值 - String key = Objects.requireNonNull(parser.parseExpression(definitionKey).getValue(context)).toString(); - keyList.add(key); + try { + // 解析 SpEL 表达式并获取值 + String key = Objects.requireNonNull(parser.parseExpression(definitionKey).getValue(context)) + .toString(); + keyList.add(key); + } catch (ParseException e) { + keyList.add(definitionKey); + } + } } return keyList; } + } \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/continew-starter-security/continew-starter-security-limiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..d365e008 --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +top.continew.starter.security.limiter.autoconfigure.RateLimiterAutoConfiguration \ No newline at end of file diff --git a/continew-starter-web/src/main/java/top/continew/starter/web/autoconfigure/exception/GlobalExceptionHandler.java b/continew-starter-web/src/main/java/top/continew/starter/web/autoconfigure/exception/GlobalExceptionHandler.java index 98398eb5..2e01bb09 100644 --- a/continew-starter-web/src/main/java/top/continew/starter/web/autoconfigure/exception/GlobalExceptionHandler.java +++ b/continew-starter-web/src/main/java/top/continew/starter/web/autoconfigure/exception/GlobalExceptionHandler.java @@ -38,6 +38,8 @@ import top.continew.starter.core.exception.BadRequestException; import top.continew.starter.core.exception.BusinessException; import top.continew.starter.web.model.R; +import java.awt.image.RasterFormatException; + /** * 全局异常处理器 * @@ -152,4 +154,10 @@ public class GlobalExceptionHandler { log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e); return R.fail(e.getMessage()); } + + @ExceptionHandler(RasterFormatException.class) + public R handleRasterFormatException(RasterFormatException e, HttpServletRequest request) { + log.error("请求地址 [{}],限流拦截。", request.getRequestURI(), e); + return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); + } } \ No newline at end of file -- Gitee From 10c2274e592e273e09de59be6d46af67b765856b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AB=A0=E5=87=AF?= <1373639299@qq.com> Date: Wed, 29 May 2024 00:19:24 +0800 Subject: [PATCH 04/14] =?UTF-8?q?feat(continew-starter-security-limiter):?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=99=90=E6=B5=81=E5=BC=80=E5=85=B3=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=92=8C=E8=87=AA=E5=AE=9A=E4=B9=89=E9=99=90=E6=B5=81?= =?UTF-8?q?KEY,=E4=BB=A5=E5=8F=8A=E5=AE=8C=E5=96=84=E4=BA=86=E9=99=90?= =?UTF-8?q?=E6=B5=81=E7=AC=AC=E4=B8=80=E7=89=88=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../limiter/aop/RateLimiterAspect.java | 42 +++++++-------- .../RateLimiterAutoConfiguration.java | 2 + .../autoconfigure/RateLimiterProperties.java | 54 +++++++++++++++++++ 3 files changed, 76 insertions(+), 22 deletions(-) create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterProperties.java diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java index 9bd65c68..9b9bdf10 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java @@ -21,8 +21,9 @@ 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.RMap; import org.redisson.api.RRateLimiter; -import org.redisson.api.RateIntervalUnit; +import org.redisson.api.RateLimiterConfig; import org.redisson.api.RedissonClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,6 +31,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; 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.converter.RateLimiterKeyConverter; import top.continew.starter.security.limiter.exception.RateLimiterException; @@ -51,6 +53,9 @@ public class RateLimiterAspect { @Autowired private RateLimiterKeyConverter rateLimiterKeyConverter; + @Autowired + private RateLimiterProperties rateLimiterProperties; + /** * 单个限流注解切点 */ @@ -74,6 +79,10 @@ public class RateLimiterAspect { */ @Around("rateLimiterSinglePointCut()") public Object aroundSingle(ProceedingJoinPoint joinPoint) throws Throwable { + //未开启限流功能,直接执行目标方法 + if (!rateLimiterProperties.isEnabled()) { + joinPoint.proceed(); + } // 获取目标方法上的 RateLimiter 注解 MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); Method method = methodSignature.getMethod(); @@ -91,6 +100,10 @@ public class RateLimiterAspect { */ @Around("rateLimiterBatchPointCut()") public Object aroundBatch(ProceedingJoinPoint joinPoint) throws Throwable { + //未开启限流功能,直接执行目标方法 + if (!rateLimiterProperties.isEnabled()) { + joinPoint.proceed(); + } // 获取目标方法上的 RateLimiters 注解 MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); Method method = methodSignature.getMethod(); @@ -110,18 +123,18 @@ public class RateLimiterAspect { * @throws Throwable 异常 */ public void isRateLimiter(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable { + // 生成限流 Key String key = this.rateLimiterKeyConverter.getKey(joinPoint, rateLimiter); // 获取 Redisson RateLimiter 实例 - RRateLimiter rateLimiterInstance = this.redissonClient.getRateLimiter(key); + RRateLimiter rRateLimiter = redissonClient.getRateLimiter(rateLimiterProperties.getLimiterKey() + key); - // 设置限流规则 - rateLimiterInstance.trySetRate(rateLimiter.mode(), rateLimiter.rate(), rateLimiter.rateInterval(), rateLimiter + // 设置限流策略 + rRateLimiter.trySetRate(rateLimiter.mode(), rateLimiter.rate(), rateLimiter.rateInterval(), rateLimiter .timeUnit()); - // 尝试获取令牌 - boolean tryAcquire = rateLimiterInstance.tryAcquire(1L, rateLimiter.rateInterval(), toTimeUnit(rateLimiter - .timeUnit())); + // 尝试获取令牌 100ms 超时 + boolean tryAcquire = rRateLimiter.tryAcquire(1L, 100, TimeUnit.MILLISECONDS); // 如果获取不到令牌,则抛出限流异常 if (!tryAcquire) { @@ -129,19 +142,4 @@ public class RateLimiterAspect { } } - /** - * 将 RateIntervalUnit 转换为 TimeUnit - * - * @param rateIntervalUnit RateIntervalUnit 时间单位 - * @return TimeUnit 时间单位 - */ - public static TimeUnit toTimeUnit(RateIntervalUnit rateIntervalUnit) { - return switch (rateIntervalUnit) { - case MILLISECONDS -> TimeUnit.MILLISECONDS; - case SECONDS -> TimeUnit.SECONDS; - case MINUTES -> TimeUnit.MINUTES; - case HOURS -> TimeUnit.HOURS; - case DAYS -> TimeUnit.DAYS; - }; - } } \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java index f8d754d1..fe25931e 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java @@ -20,11 +20,13 @@ import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.ComponentScan; @ComponentScan(basePackages = {"top.continew.starter.security.limiter.aop", "top.continew.starter.security.limiter.converter"}) @AutoConfiguration +@EnableConfigurationProperties(RateLimiterProperties.class) public class RateLimiterAutoConfiguration { private static final Logger log = LoggerFactory.getLogger(RateLimiterAutoConfiguration.class); diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterProperties.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterProperties.java new file mode 100644 index 00000000..e978f6e4 --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterProperties.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.autoconfigure; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.StringUtils; + +/** + * @author KAI + * @date 2024/05/28 23:28> + */ +@ConfigurationProperties(prefix = "continew-starter.security.limiter") +public class RateLimiterProperties { + private boolean enabled = false; + + private String limiterKey = "RateLimiter:"; + + public boolean isEnabled() { + return enabled; + } + + 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 + ":"; + } + } + this.limiterKey = limiterKey; + } + +} -- Gitee From d7481acf81e8f6bcaf633327ce8c13b0762510d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AB=A0=E5=87=AF?= <1373639299@qq.com> Date: Thu, 30 May 2024 00:36:19 +0800 Subject: [PATCH 05/14] =?UTF-8?q?feat(continew-starter-security-limiter):?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=99=90=E6=B5=81=E5=BC=80=E5=85=B3=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=92=8C=E8=87=AA=E5=AE=9A=E4=B9=89=E9=99=90=E6=B5=81?= =?UTF-8?q?KEY?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../limiter/annotation/RateLimiter.java | 21 ++++- .../limiter/aop/RateLimiterAspect.java | 30 +++++-- .../converter/RateLimiterKeyConverter.java | 88 ++++++++++--------- 3 files changed, 86 insertions(+), 53 deletions(-) diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java index 59a7fe70..fd3575a2 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java @@ -34,12 +34,25 @@ public @interface RateLimiter { RateType mode() default RateType.PER_CLIENT; /** - * Spel表达式 + * 动态限流 key 生成的 SpEL 表达式 */ String[] keys() default {}; /** - * 单位时间产生的令牌数,默认100个 例如 rateInterval = 1000 rate = 100 timeUnit= RateIntervalUnit.MILLISECONDS 表示每1000毫秒产生100个令牌 + * 静态限流 key 生成方式 + * 默认为 "className + methodName" + */ + String staticKey() default ""; + + /** + * 是否动态生成限流 key + * true: 动态生成,使用 `keys` 属性配置的 SpEL 表达式 + * false: 使用默认 key 生成方式,默认类名 + 方法名 + */ + boolean isKeyDynamic() default false; + + /** + * 单位时间产生的令牌数,默认100个 例如 rateInterval = 1000 rate = 100 timeUnit= RateIntervalUnit.MILLISECONDS 表示每100毫秒产生100个令牌 */ long rate() default 100; @@ -56,5 +69,5 @@ public @interface RateLimiter { /** * 拒绝请求时的提示信息 */ - String notAllowMessage() default "系统繁忙,请稍候再试"; -} + String notAllowMessage() default "您操作过于频繁,请稍后再试!"; +} \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java index 9b9bdf10..47fee947 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java @@ -21,10 +21,7 @@ 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.RMap; -import org.redisson.api.RRateLimiter; -import org.redisson.api.RateLimiterConfig; -import org.redisson.api.RedissonClient; +import org.redisson.api.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -129,9 +126,11 @@ public class RateLimiterAspect { // 获取 Redisson RateLimiter 实例 RRateLimiter rRateLimiter = redissonClient.getRateLimiter(rateLimiterProperties.getLimiterKey() + key); - // 设置限流策略 - rRateLimiter.trySetRate(rateLimiter.mode(), rateLimiter.rate(), rateLimiter.rateInterval(), rateLimiter - .timeUnit()); + // 判断是否需要更新限流器配置 + if (shouldUpdateRateLimiter(rRateLimiter, rateLimiter)) { + rRateLimiter.setRate(rateLimiter.mode(), rateLimiter.rate(), rateLimiter.rateInterval(), rateLimiter + .timeUnit()); + } // 尝试获取令牌 100ms 超时 boolean tryAcquire = rRateLimiter.tryAcquire(1L, 100, TimeUnit.MILLISECONDS); @@ -142,4 +141,21 @@ public class RateLimiterAspect { } } + /** + * 判断是否需要更新限流器配置 + * + * @param rRateLimiter 现有的限流器 + * @param rateLimiter 注解中的限流配置 + * @return 是否需要更新配置 + */ + private boolean shouldUpdateRateLimiter(RRateLimiter rRateLimiter, RateLimiter rateLimiter) { + RateType rateType = rateLimiter.mode(); + long rate = rateLimiter.rate(); + long rateInterval = rateLimiter.rateInterval(); + RateIntervalUnit unit = rateLimiter.timeUnit(); + RateLimiterConfig config = rRateLimiter.getConfig(); + return config.getRateType() != rateType || config.getRate() != rate || config.getRateInterval() != unit + .toMillis(rateInterval); + } + } \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java index f41ac99a..971f9da2 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java @@ -34,11 +34,8 @@ import top.continew.starter.security.limiter.annotation.RateLimiter; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import java.util.Objects; +import java.util.Optional; -/** - * 限流Key生成器 - */ @Component public class RateLimiterKeyConverter { @@ -56,17 +53,58 @@ public class RateLimiterKeyConverter { * @return 限流 Key */ public String getKey(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) { - // 获取目标方法 Method method = getMethod(joinPoint); - - // 解析 SpEL 表达式 - List keys = getSpelDefinitionKey(rateLimiter.keys(), method, joinPoint.getArgs()); + List keys = new ArrayList<>(); + + // 判断是否开启SpEL表达式解析 + if (rateLimiter.isKeyDynamic()) { + // 解析 SpEL 表达式 + keys = getSpelDefinitionKey(rateLimiter.keys(), method, joinPoint.getArgs()); + } else { + // 使用 staticKey 生成限流 Key + if (StringUtils.hasText(rateLimiter.staticKey())) { + keys.add(rateLimiter.staticKey()); + } else { + // 使用默认 key 生成方式 + keys.add(method.getDeclaringClass().getName() + "." + method.getName()); + } + } // 拼接限流 Key return StringUtils.collectionToDelimitedString(keys, ".", "", ""); } + /** + * 解析 SpEL 表达式,生成动态限流 Key + * + * @param definitionKeys SpEL 表达式数组 + * @param method 目标方法 + * @param parameterValues 方法参数值 + * @return 解析后的 SpEL 表达式值列表 + */ + private List getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) { + List keyList = new ArrayList<>(); + + for (String definitionKey : definitionKeys) { + if (StringUtils.hasText(definitionKey)) { + try { + // 创建 SpEL 表达式上下文 + EvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, discoverer); + // 解析 SpEL 表达式并获取值 + String key = Optional.ofNullable(parser.parseExpression(definitionKey) + .getValue(context, String.class)) + .orElseThrow(() -> new IllegalArgumentException("SpEL 表达式解析结果为空: " + definitionKey)); + keyList.add(key); + } catch (ParseException e) { + log.error("解析 SpEL 表达式错误: {}", definitionKey, e); + throw e; + } + } + } + return keyList; + } + /** * 获取目标方法 * @@ -89,38 +127,4 @@ public class RateLimiterKeyConverter { } return method; } - - /** - * 解析 SpEL 表达式 - * - * @param definitionKeys SpEL 表达式数组 - * @param method 目标方法 - * @param parameterValues 方法参数值 - * @return 解析后的 SpEL 表达式值列表 - */ - private List getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) { - List keyList = new ArrayList<>(); - //如果key为空则用类名+方法名称 - if (definitionKeys == null || definitionKeys.length == 0) { - keyList.add(method.getDeclaringClass().getName() + "." + method.getName()); - return keyList; - } - for (String definitionKey : definitionKeys) { - if (StringUtils.hasText(definitionKey)) { - // 创建 SpEL 表达式上下文 - EvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, discoverer); - try { - // 解析 SpEL 表达式并获取值 - String key = Objects.requireNonNull(parser.parseExpression(definitionKey).getValue(context)) - .toString(); - keyList.add(key); - } catch (ParseException e) { - keyList.add(definitionKey); - } - - } - } - return keyList; - } - } \ No newline at end of file -- Gitee From 1488fa62ac22762294b051303001f1dadb0db82d Mon Sep 17 00:00:00 2001 From: zk020106 <1373639299@qq.com> Date: Thu, 30 May 2024 10:24:37 +0800 Subject: [PATCH 06/14] =?UTF-8?q?feat(continew-starter-security-limiter):?= =?UTF-8?q?=20=E9=99=90=E6=B5=81=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.对限流Key进行了Md5加密 2.RRateLimiter加入了缓存 3.修复了限流注解配置更新,Redis里不能更新的问题 --- .../continew-starter-security-limiter/pom.xml | 5 +++ .../limiter/aop/RateLimiterAspect.java | 43 +++++++++++++++---- .../converter/RateLimiterKeyConverter.java | 10 ++++- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/continew-starter-security/continew-starter-security-limiter/pom.xml b/continew-starter-security/continew-starter-security-limiter/pom.xml index 6b65e7ce..b32dabf3 100644 --- a/continew-starter-security/continew-starter-security-limiter/pom.xml +++ b/continew-starter-security/continew-starter-security-limiter/pom.xml @@ -27,5 +27,10 @@ aspectjweaver 1.9.4 + + + cn.hutool + hutool-all + diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java index 47fee947..a6b8208e 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java @@ -16,6 +16,8 @@ package top.continew.starter.security.limiter.aop; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.extra.spring.SpringUtil; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @@ -30,13 +32,15 @@ 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.converter.RateLimiterKeyConverter; -import top.continew.starter.security.limiter.exception.RateLimiterException; import java.lang.reflect.Method; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.ConcurrentHashMap; /** * 限流 AOP 拦截器 + * + * @author KAI + * @date 2024-5-28 10:19:42 */ @Aspect @Component @@ -44,8 +48,9 @@ public class RateLimiterAspect { private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); - @Autowired - private RedissonClient redissonClient; + private static final RedissonClient CLIENT = SpringUtil.getBean(RedissonClient.class); + + private static ConcurrentHashMap rateLimiterCache = new ConcurrentHashMap<>(); @Autowired private RateLimiterKeyConverter rateLimiterKeyConverter; @@ -123,21 +128,25 @@ public class RateLimiterAspect { // 生成限流 Key String key = this.rateLimiterKeyConverter.getKey(joinPoint, rateLimiter); + // 对 Key 进行 MD5 加密 控制key的长度 + String redisKey = rateLimiterProperties.getLimiterKey() + SecureUtil.md5(key); // 获取 Redisson RateLimiter 实例 - RRateLimiter rRateLimiter = redissonClient.getRateLimiter(rateLimiterProperties.getLimiterKey() + key); + RRateLimiter rRateLimiter = getRateLimiter(redisKey); // 判断是否需要更新限流器配置 if (shouldUpdateRateLimiter(rRateLimiter, rateLimiter)) { + // 更新限流配置 rRateLimiter.setRate(rateLimiter.mode(), rateLimiter.rate(), rateLimiter.rateInterval(), rateLimiter .timeUnit()); } - // 尝试获取令牌 100ms 超时 - boolean tryAcquire = rRateLimiter.tryAcquire(1L, 100, TimeUnit.MILLISECONDS); - + // 尝试获取令牌 + boolean tryAcquire = rRateLimiter.tryAcquire(1L); // 如果获取不到令牌,则抛出限流异常 if (!tryAcquire) { - throw new RateLimiterException(rateLimiter.notAllowMessage()); + log.error("限流Key:{},限流模式:{}; 限流数量:{}; 限流时间间隔:{}", key, rateLimiter.mode().toString(), rateLimiter + .rate(), rateLimiter.rateInterval()); + throw new Throwable(rateLimiter.notAllowMessage()); } } @@ -154,8 +163,24 @@ public class RateLimiterAspect { long rateInterval = rateLimiter.rateInterval(); RateIntervalUnit unit = rateLimiter.timeUnit(); RateLimiterConfig config = rRateLimiter.getConfig(); + //比较各个配置是否一致 return config.getRateType() != rateType || config.getRate() != rate || config.getRateInterval() != unit .toMillis(rateInterval); } + /** + * 获取 Redisson RateLimiter 实例 + * + * @param key 限流器的 Key + * @return RateLimiter 实例 + */ + private RRateLimiter getRateLimiter(String key) { + return rateLimiterCache.computeIfAbsent(key, k -> { + RRateLimiter rateLimiter = CLIENT.getRateLimiter(key); + // 将 rateLimiter 添加到缓存中 + rateLimiterCache.put(key, rateLimiter); + return rateLimiter; + }); + } + } \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java index 971f9da2..df4e3f6c 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java @@ -36,6 +36,12 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +/** + * 限流Key转换器 + * + * @author KAI + * @date 2024-5-28 21:19:14 + */ @Component public class RateLimiterKeyConverter { @@ -55,6 +61,7 @@ public class RateLimiterKeyConverter { public String getKey(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) { // 获取目标方法 Method method = getMethod(joinPoint); + // 创建限流key存储对象 List keys = new ArrayList<>(); // 判断是否开启SpEL表达式解析 @@ -122,7 +129,8 @@ public class RateLimiterKeyConverter { .getClass() .getDeclaredMethod(signature.getName(), method.getParameterTypes()); } catch (Exception e) { - log.error(null, e); + log.error(e.getMessage()); + throw new IllegalArgumentException(e.getMessage()); } } return method; -- Gitee From e91f99a668b39762e431910c8add19b3e284022c Mon Sep 17 00:00:00 2001 From: zk020106 <1373639299@qq.com> Date: Thu, 30 May 2024 18:11:28 +0800 Subject: [PATCH 07/14] =?UTF-8?q?feat(continew-starter-security-limiter):?= =?UTF-8?q?=20=E9=99=90=E6=B5=81=E9=85=8D=E7=BD=AE=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../limiter/annotation/RateLimiter.java | 27 ++++---- .../limiter/aop/RateLimiterAspect.java | 36 +++++----- .../converter/RateLimiterKeyConverter.java | 65 ++++++++----------- 3 files changed, 60 insertions(+), 68 deletions(-) diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java index fd3575a2..fcdd980b 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java @@ -18,6 +18,7 @@ package top.continew.starter.security.limiter.annotation; import org.redisson.api.RateIntervalUnit; import org.redisson.api.RateType; +import top.continew.starter.security.limiter.converter.RateLimiterKeyConverter; import java.lang.annotation.*; @@ -34,40 +35,34 @@ public @interface RateLimiter { RateType mode() default RateType.PER_CLIENT; /** - * 动态限流 key 生成的 SpEL 表达式 + * 限流器名称 */ - String[] keys() default {}; + String name() default ""; /** - * 静态限流 key 生成方式 - * 默认为 "className + methodName" + * 限流条件key 需要通过 Spring EL 表达式的形式传入 */ - String staticKey() default ""; + String condition() default ""; - /** - * 是否动态生成限流 key - * true: 动态生成,使用 `keys` 属性配置的 SpEL 表达式 - * false: 使用默认 key 生成方式,默认类名 + 方法名 - */ - boolean isKeyDynamic() default false; /** - * 单位时间产生的令牌数,默认100个 例如 rateInterval = 1000 rate = 100 timeUnit= RateIntervalUnit.MILLISECONDS 表示每100毫秒产生100个令牌 + * 单位时间产生的令牌数 */ - long rate() default 100; + long rate() default Integer.MAX_VALUE; /** - * 时间间隔,默认1000 + * 时间间隔 */ - long rateInterval() default 1000; + long rateInterval() default 0; /** * 时间单位,默认毫秒 */ RateIntervalUnit timeUnit() default RateIntervalUnit.MILLISECONDS; + /** * 拒绝请求时的提示信息 */ - String notAllowMessage() default "您操作过于频繁,请稍后再试!"; + String message() default "您操作过于频繁,请稍后再试!"; } \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java index a6b8208e..f4b8afb5 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java @@ -32,13 +32,14 @@ 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.converter.RateLimiterKeyConverter; +import top.continew.starter.security.limiter.exception.RateLimiterException; import java.lang.reflect.Method; import java.util.concurrent.ConcurrentHashMap; /** * 限流 AOP 拦截器 - * + * * @author KAI * @date 2024-5-28 10:19:42 */ @@ -86,7 +87,7 @@ public class RateLimiterAspect { joinPoint.proceed(); } // 获取目标方法上的 RateLimiter 注解 - MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class); isRateLimiter(joinPoint, rateLimiter); @@ -107,7 +108,7 @@ public class RateLimiterAspect { joinPoint.proceed(); } // 获取目标方法上的 RateLimiters 注解 - MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); RateLimiters rateLimiters = method.getAnnotation(RateLimiters.class); RateLimiter[] rateLimiterList = rateLimiters.value(); @@ -125,9 +126,8 @@ public class RateLimiterAspect { * @throws Throwable 异常 */ public void isRateLimiter(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable { - // 生成限流 Key - String key = this.rateLimiterKeyConverter.getKey(joinPoint, rateLimiter); + String key = rateLimiterKeyConverter.getKey(joinPoint, rateLimiter); // 对 Key 进行 MD5 加密 控制key的长度 String redisKey = rateLimiterProperties.getLimiterKey() + SecureUtil.md5(key); // 获取 Redisson RateLimiter 实例 @@ -137,7 +137,7 @@ public class RateLimiterAspect { if (shouldUpdateRateLimiter(rRateLimiter, rateLimiter)) { // 更新限流配置 rRateLimiter.setRate(rateLimiter.mode(), rateLimiter.rate(), rateLimiter.rateInterval(), rateLimiter - .timeUnit()); + .timeUnit()); } // 尝试获取令牌 @@ -145,8 +145,8 @@ public class RateLimiterAspect { // 如果获取不到令牌,则抛出限流异常 if (!tryAcquire) { log.error("限流Key:{},限流模式:{}; 限流数量:{}; 限流时间间隔:{}", key, rateLimiter.mode().toString(), rateLimiter - .rate(), rateLimiter.rateInterval()); - throw new Throwable(rateLimiter.notAllowMessage()); + .rate(), rateLimiter.rateInterval()); + throw new RateLimiterException(rateLimiter.message()); } } @@ -165,7 +165,7 @@ public class RateLimiterAspect { RateLimiterConfig config = rRateLimiter.getConfig(); //比较各个配置是否一致 return config.getRateType() != rateType || config.getRate() != rate || config.getRateInterval() != unit - .toMillis(rateInterval); + .toMillis(rateInterval); } /** @@ -175,12 +175,18 @@ public class RateLimiterAspect { * @return RateLimiter 实例 */ private RRateLimiter getRateLimiter(String key) { - return rateLimiterCache.computeIfAbsent(key, k -> { - RRateLimiter rateLimiter = CLIENT.getRateLimiter(key); - // 将 rateLimiter 添加到缓存中 - rateLimiterCache.put(key, rateLimiter); - return rateLimiter; - }); + RRateLimiter rRateLimiter = rateLimiterCache.get(key); + if (rRateLimiter == null) { + synchronized (this) { + rRateLimiter = rateLimiterCache.get(key); // double check + if (rRateLimiter == null) { + // 创建 RateLimiter 实例 + rRateLimiter = CLIENT.getRateLimiter(key); + rateLimiterCache.put(key, rRateLimiter); + } + } + } + return rRateLimiter; } } \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java index df4e3f6c..3ca7cd19 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java @@ -38,7 +38,7 @@ import java.util.Optional; /** * 限流Key转换器 - * + * * @author KAI * @date 2024-5-28 21:19:14 */ @@ -63,21 +63,11 @@ public class RateLimiterKeyConverter { Method method = getMethod(joinPoint); // 创建限流key存储对象 List keys = new ArrayList<>(); + // 添加限流名称 + keys.add(Optional.ofNullable(rateLimiter.name()).orElse(method.getDeclaringClass().getName() + "." + method.getName())); - // 判断是否开启SpEL表达式解析 - if (rateLimiter.isKeyDynamic()) { - // 解析 SpEL 表达式 - keys = getSpelDefinitionKey(rateLimiter.keys(), method, joinPoint.getArgs()); - } else { - // 使用 staticKey 生成限流 Key - if (StringUtils.hasText(rateLimiter.staticKey())) { - keys.add(rateLimiter.staticKey()); - } else { - // 使用默认 key 生成方式 - keys.add(method.getDeclaringClass().getName() + "." + method.getName()); - } - } - + // 解析Spel表达式 生成限流条件 + getSpelDefinitionKey(rateLimiter.condition(), method, joinPoint.getArgs(), keys); // 拼接限流 Key return StringUtils.collectionToDelimitedString(keys, ".", "", ""); } @@ -85,31 +75,32 @@ public class RateLimiterKeyConverter { /** * 解析 SpEL 表达式,生成动态限流 Key * - * @param definitionKeys SpEL 表达式数组 + * @param spelKey SpEL 表达式 * @param method 目标方法 * @param parameterValues 方法参数值 - * @return 解析后的 SpEL 表达式值列表 + * @param keys 限流 Key 存储对象 */ - private List getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) { - List keyList = new ArrayList<>(); + private void getSpelDefinitionKey(String spelKey, Method method, Object[] parameterValues, List keys) { - for (String definitionKey : definitionKeys) { - if (StringUtils.hasText(definitionKey)) { - try { - // 创建 SpEL 表达式上下文 - EvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, discoverer); - // 解析 SpEL 表达式并获取值 - String key = Optional.ofNullable(parser.parseExpression(definitionKey) - .getValue(context, String.class)) - .orElseThrow(() -> new IllegalArgumentException("SpEL 表达式解析结果为空: " + definitionKey)); - keyList.add(key); - } catch (ParseException e) { - log.error("解析 SpEL 表达式错误: {}", definitionKey, e); - throw e; + + if (StringUtils.hasText(spelKey)) { + try { + // 创建 SpEL 表达式上下文 + EvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, discoverer); + // 解析 SpEL 表达式并获取值,使用 Optional 的 orElseThrow 方法处理空值 + String key = parser.parseExpression(spelKey) + .getValue(context, String.class); + if (StringUtils.hasText(key)) { + keys.add(key); + } else { + throw new IllegalArgumentException("SpEL 表达式解析结果为空: " + spelKey); } + } catch (ParseException e) { + log.error("解析 SpEL 表达式错误: {}", spelKey, e); + throw e; } } - return keyList; + } /** @@ -119,15 +110,15 @@ public class RateLimiterKeyConverter { * @return 目标方法 */ public Method getMethod(ProceedingJoinPoint joinPoint) { - MethodSignature signature = (MethodSignature)joinPoint.getSignature(); + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); // 如果目标方法是接口方法,则获取其实现类的方法 if (method.getDeclaringClass().isInterface()) { try { method = joinPoint.getTarget() - .getClass() - .getDeclaredMethod(signature.getName(), method.getParameterTypes()); + .getClass() + .getDeclaredMethod(signature.getName(), method.getParameterTypes()); } catch (Exception e) { log.error(e.getMessage()); throw new IllegalArgumentException(e.getMessage()); @@ -135,4 +126,4 @@ public class RateLimiterKeyConverter { } return method; } -} \ No newline at end of file +} -- Gitee From 6aadfe48df1fecdcc9e8dd8a9a77c0b18e768b09 Mon Sep 17 00:00:00 2001 From: KAI <1373639299@qq.com> Date: Mon, 3 Jun 2024 18:22:01 +0800 Subject: [PATCH 08/14] =?UTF-8?q?feat:(continew-starter-security-limiter):?= =?UTF-8?q?=20=E5=AD=97=E6=AE=B5=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/limiter/annotation/RateLimiter.java | 3 --- .../security/limiter/aop/RateLimiterAspect.java | 10 +++++----- .../limiter/converter/RateLimiterKeyConverter.java | 7 +++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java index fcdd980b..354a9e1e 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java @@ -18,7 +18,6 @@ package top.continew.starter.security.limiter.annotation; import org.redisson.api.RateIntervalUnit; import org.redisson.api.RateType; -import top.continew.starter.security.limiter.converter.RateLimiterKeyConverter; import java.lang.annotation.*; @@ -44,7 +43,6 @@ public @interface RateLimiter { */ String condition() default ""; - /** * 单位时间产生的令牌数 */ @@ -60,7 +58,6 @@ public @interface RateLimiter { */ RateIntervalUnit timeUnit() default RateIntervalUnit.MILLISECONDS; - /** * 拒绝请求时的提示信息 */ diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java index f4b8afb5..b0aa6d65 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java @@ -87,7 +87,7 @@ public class RateLimiterAspect { joinPoint.proceed(); } // 获取目标方法上的 RateLimiter 注解 - MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); Method method = methodSignature.getMethod(); RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class); isRateLimiter(joinPoint, rateLimiter); @@ -108,7 +108,7 @@ public class RateLimiterAspect { joinPoint.proceed(); } // 获取目标方法上的 RateLimiters 注解 - MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); Method method = methodSignature.getMethod(); RateLimiters rateLimiters = method.getAnnotation(RateLimiters.class); RateLimiter[] rateLimiterList = rateLimiters.value(); @@ -137,7 +137,7 @@ public class RateLimiterAspect { if (shouldUpdateRateLimiter(rRateLimiter, rateLimiter)) { // 更新限流配置 rRateLimiter.setRate(rateLimiter.mode(), rateLimiter.rate(), rateLimiter.rateInterval(), rateLimiter - .timeUnit()); + .timeUnit()); } // 尝试获取令牌 @@ -145,7 +145,7 @@ public class RateLimiterAspect { // 如果获取不到令牌,则抛出限流异常 if (!tryAcquire) { log.error("限流Key:{},限流模式:{}; 限流数量:{}; 限流时间间隔:{}", key, rateLimiter.mode().toString(), rateLimiter - .rate(), rateLimiter.rateInterval()); + .rate(), rateLimiter.rateInterval()); throw new RateLimiterException(rateLimiter.message()); } } @@ -165,7 +165,7 @@ public class RateLimiterAspect { RateLimiterConfig config = rRateLimiter.getConfig(); //比较各个配置是否一致 return config.getRateType() != rateType || config.getRate() != rate || config.getRateInterval() != unit - .toMillis(rateInterval); + .toMillis(rateInterval); } /** diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java index 3ca7cd19..acd17ed6 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java @@ -64,7 +64,8 @@ public class RateLimiterKeyConverter { // 创建限流key存储对象 List keys = new ArrayList<>(); // 添加限流名称 - keys.add(Optional.ofNullable(rateLimiter.name()).orElse(method.getDeclaringClass().getName() + "." + method.getName())); + keys.add(Optional.ofNullable(rateLimiter.name()) + .orElse(method.getDeclaringClass().getName() + "." + method.getName())); // 解析Spel表达式 生成限流条件 getSpelDefinitionKey(rateLimiter.condition(), method, joinPoint.getArgs(), keys); @@ -82,14 +83,12 @@ public class RateLimiterKeyConverter { */ private void getSpelDefinitionKey(String spelKey, Method method, Object[] parameterValues, List keys) { - if (StringUtils.hasText(spelKey)) { try { // 创建 SpEL 表达式上下文 EvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, discoverer); // 解析 SpEL 表达式并获取值,使用 Optional 的 orElseThrow 方法处理空值 - String key = parser.parseExpression(spelKey) - .getValue(context, String.class); + String key = parser.parseExpression(spelKey).getValue(context, String.class); if (StringUtils.hasText(key)) { keys.add(key); } else { -- Gitee From 64b731c683361d4fa36ebde0a0faf41ed2672833 Mon Sep 17 00:00:00 2001 From: KAI <1373639299@qq.com> Date: Fri, 14 Jun 2024 10:20:32 +0800 Subject: [PATCH 09/14] =?UTF-8?q?=E9=99=90=E6=B5=81=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=E7=9A=84=E4=BC=98=E5=8C=96=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cache/redisson/util/RedisUtils.java | 19 ++ .../limiter/annotation/RateLimiter.java | 26 +-- .../limiter/aop/RateLimiterAspect.java | 188 +++++++++--------- .../RateLimiterAutoConfiguration.java | 11 +- .../converter/RateLimiterKeyConverter.java | 128 ------------ .../security/limiter/enums/LimitType.java | 33 +++ .../src/main/resources/spel-extension.json | 7 + continew-starter-security/pom.xml | 12 ++ 8 files changed, 185 insertions(+), 239 deletions(-) delete mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/enums/LimitType.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/resources/spel-extension.json diff --git a/continew-starter-cache/continew-starter-cache-redisson/src/main/java/top/continew/starter/cache/redisson/util/RedisUtils.java b/continew-starter-cache/continew-starter-cache-redisson/src/main/java/top/continew/starter/cache/redisson/util/RedisUtils.java index c814abc0..0b0474f7 100644 --- a/continew-starter-cache/continew-starter-cache-redisson/src/main/java/top/continew/starter/cache/redisson/util/RedisUtils.java +++ b/continew-starter-cache/continew-starter-cache-redisson/src/main/java/top/continew/starter/cache/redisson/util/RedisUtils.java @@ -180,6 +180,25 @@ public class RedisUtils { return rateLimiter.tryAcquire(1); } + /** + * 限流 + * + * @param key 限流key + * @param rateType 限流类型 + * @param rate 速率 + * @param rateInterval 速率间隔 + * @return -1 表示失败 + */ + public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval, RateIntervalUnit unit) { + RRateLimiter rateLimiter = CLIENT.getRateLimiter(key); + rateLimiter.trySetRate(rateType, rate, rateInterval, unit); + if (rateLimiter.tryAcquire()) { + return rateLimiter.availablePermits(); + } else { + return -1L; + } + } + /** * 格式化键,将各子键用 : 拼接起来 * diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java index 354a9e1e..19fae1ba 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java @@ -17,7 +17,7 @@ package top.continew.starter.security.limiter.annotation; import org.redisson.api.RateIntervalUnit; -import org.redisson.api.RateType; +import top.continew.starter.security.limiter.enums.LimitType; import java.lang.annotation.*; @@ -27,31 +27,27 @@ import java.lang.annotation.*; public @interface RateLimiter { /** - * RateLimiter 限流模式 - * OVERALL 所有客户端加总限流 - * PER_CLIENT 每个客户端单独计算流量 + * LimitType 限流模式 + * DEFAULT 全局限流 + * IP IP限流 + * CLUSTER 实例限流 */ - RateType mode() default RateType.PER_CLIENT; + LimitType limitType() default LimitType.DEFAULT; /** - * 限流器名称 + * 限流key 支持 Spring EL 表达式 */ - String name() default ""; - - /** - * 限流条件key 需要通过 Spring EL 表达式的形式传入 - */ - String condition() default ""; + String key() default ""; /** * 单位时间产生的令牌数 */ - long rate() default Integer.MAX_VALUE; + int rate() default Integer.MAX_VALUE; /** - * 时间间隔 + * 限流时间 */ - long rateInterval() default 0; + int rateInterval() default 0; /** * 时间单位,默认毫秒 diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java index b0aa6d65..113f591c 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java @@ -16,26 +16,37 @@ 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.redisson.api.RateType; +import org.redisson.api.RedissonClient; 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.cache.redisson.util.RedisUtils; 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.converter.RateLimiterKeyConverter; +import top.continew.starter.security.limiter.enums.LimitType; import top.continew.starter.security.limiter.exception.RateLimiterException; - -import java.lang.reflect.Method; -import java.util.concurrent.ConcurrentHashMap; +import top.continew.starter.web.util.ServletUtils; /** * 限流 AOP 拦截器 @@ -48,14 +59,11 @@ import java.util.concurrent.ConcurrentHashMap; public class RateLimiterAspect { private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); - + private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); + private final ExpressionParser parser = new SpelExpressionParser(); + private final ParserContext parserContext = new TemplateParserContext(); private static final RedissonClient CLIENT = SpringUtil.getBean(RedissonClient.class); - private static ConcurrentHashMap rateLimiterCache = new ConcurrentHashMap<>(); - - @Autowired - private RateLimiterKeyConverter rateLimiterKeyConverter; - @Autowired private RateLimiterProperties rateLimiterProperties; @@ -76,44 +84,37 @@ public class RateLimiterAspect { /** * 环绕通知,处理单个限流注解 * - * @param joinPoint 切点 + * @param joinPoint 切点 + * @param rateLimiter 限流注解 * @return 返回目标方法的执行结果 * @throws Throwable 异常 */ - @Around("rateLimiterSinglePointCut()") - public Object aroundSingle(ProceedingJoinPoint joinPoint) throws Throwable { - //未开启限流功能,直接执行目标方法 + @Around("@annotation(rateLimiter)") + public Object aroundSingle(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable { + // 未开启限流功能,直接执行目标方法 if (!rateLimiterProperties.isEnabled()) { - joinPoint.proceed(); + return joinPoint.proceed(); } - // 获取目标方法上的 RateLimiter 注解 - MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); - Method method = methodSignature.getMethod(); - RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class); - isRateLimiter(joinPoint, rateLimiter); + checkRateLimiter(joinPoint, rateLimiter); return joinPoint.proceed(); } /** * 环绕通知,处理多个限流注解 * - * @param joinPoint 切点 + * @param joinPoint 切点 + * @param rateLimiters 多个限流注解 * @return 返回目标方法的执行结果 * @throws Throwable 异常 */ - @Around("rateLimiterBatchPointCut()") - public Object aroundBatch(ProceedingJoinPoint joinPoint) throws Throwable { - //未开启限流功能,直接执行目标方法 + @Around("@annotation(rateLimiters)") + public Object aroundBatch(ProceedingJoinPoint joinPoint, RateLimiters rateLimiters) throws Throwable { + // 未开启限流功能,直接执行目标方法 if (!rateLimiterProperties.isEnabled()) { - joinPoint.proceed(); + return joinPoint.proceed(); } - // 获取目标方法上的 RateLimiters 注解 - MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); - Method method = methodSignature.getMethod(); - RateLimiters rateLimiters = method.getAnnotation(RateLimiters.class); - RateLimiter[] rateLimiterList = rateLimiters.value(); - for (RateLimiter rateLimiter : rateLimiterList) { - isRateLimiter(joinPoint, rateLimiter); + for (RateLimiter rateLimiter : rateLimiters.value()) { + checkRateLimiter(joinPoint, rateLimiter); } return joinPoint.proceed(); } @@ -125,68 +126,75 @@ public class RateLimiterAspect { * @param rateLimiter 限流注解 * @throws Throwable 异常 */ - public void isRateLimiter(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable { - // 生成限流 Key - String key = rateLimiterKeyConverter.getKey(joinPoint, rateLimiter); - // 对 Key 进行 MD5 加密 控制key的长度 - String redisKey = rateLimiterProperties.getLimiterKey() + SecureUtil.md5(key); - // 获取 Redisson RateLimiter 实例 - RRateLimiter rRateLimiter = getRateLimiter(redisKey); - - // 判断是否需要更新限流器配置 - if (shouldUpdateRateLimiter(rRateLimiter, rateLimiter)) { - // 更新限流配置 - rRateLimiter.setRate(rateLimiter.mode(), rateLimiter.rate(), rateLimiter.rateInterval(), rateLimiter - .timeUnit()); - } - - // 尝试获取令牌 - boolean tryAcquire = rRateLimiter.tryAcquire(1L); - // 如果获取不到令牌,则抛出限流异常 - if (!tryAcquire) { - log.error("限流Key:{},限流模式:{}; 限流数量:{}; 限流时间间隔:{}", key, rateLimiter.mode().toString(), rateLimiter - .rate(), rateLimiter.rateInterval()); - throw new RateLimiterException(rateLimiter.message()); + private void checkRateLimiter(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable { + try { + // 生成限流 Key + String redisKey = generateRedisKey(rateLimiter, joinPoint); + // 确定限流类型 + RateType rateType = rateLimiter.limitType() == LimitType.CLUSTER ? RateType.PER_CLIENT : RateType.OVERALL; + + // 剩余令牌数 + long rateCount = RedisUtils.rateLimiter(redisKey, rateType, rateLimiter.rate(), rateLimiter + .rateInterval(), rateLimiter.timeUnit()); + if (rateCount == -1L) { + throw new RateLimiterException(rateLimiter.message()); + } + } catch (RateLimiterException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException("服务器限流异常,请稍候再试", e); } } /** - * 判断是否需要更新限流器配置 + * 获取限流Key * - * @param rRateLimiter 现有的限流器 - * @param rateLimiter 注解中的限流配置 - * @return 是否需要更新配置 + * @param rateLimiter RateLimiter实例 + * @param point 切点 + * @return 限流Key */ - private boolean shouldUpdateRateLimiter(RRateLimiter rRateLimiter, RateLimiter rateLimiter) { - RateType rateType = rateLimiter.mode(); - long rate = rateLimiter.rate(); - long rateInterval = rateLimiter.rateInterval(); - RateIntervalUnit unit = rateLimiter.timeUnit(); - RateLimiterConfig config = rRateLimiter.getConfig(); - //比较各个配置是否一致 - return config.getRateType() != rateType || config.getRate() != rate || config.getRateInterval() != unit - .toMillis(rateInterval); - } + private String generateRedisKey(RateLimiter rateLimiter, JoinPoint point) { + // 获取限流器配置的 key + String key = rateLimiter.key(); + + // 如果 key 不为空,则解析表达式并获取最终的 key + if (StringUtils.hasText(key)) { + // 获取方法签名 + 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 = StringUtils.startsWithIgnoreCase(key, parserContext + .getExpressionPrefix()) && StringUtils.endsWithIgnoreCase(key, parserContext.getExpressionSuffix()) + ? parser.parseExpression(key, parserContext) + : parser.parseExpression(key); + // 获取表达式结果 + key = expression.getValue(context, String.class); + } - /** - * 获取 Redisson RateLimiter 实例 - * - * @param key 限流器的 Key - * @return RateLimiter 实例 - */ - private RRateLimiter getRateLimiter(String key) { - RRateLimiter rRateLimiter = rateLimiterCache.get(key); - if (rRateLimiter == null) { - synchronized (this) { - rRateLimiter = rateLimiterCache.get(key); // double check - if (rRateLimiter == null) { - // 创建 RateLimiter 实例 - rRateLimiter = CLIENT.getRateLimiter(key); - rateLimiterCache.put(key, rRateLimiter); - } - } + // 拼接最终的 key + StringBuilder redisKey = new StringBuilder(rateLimiterProperties.getLimiterKey()).append(ServletUtils + .getRequest() + .getRequestURI()).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; } - return rRateLimiter; + // 添加解析后的 key + return redisKey.append(key).toString(); } - -} \ No newline at end of file +} diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java index fe25931e..42cfc16d 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java @@ -16,23 +16,22 @@ 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.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Bean; +import top.continew.starter.security.limiter.aop.RateLimiterAspect; -@ComponentScan(basePackages = {"top.continew.starter.security.limiter.aop", - "top.continew.starter.security.limiter.converter"}) @AutoConfiguration @EnableConfigurationProperties(RateLimiterProperties.class) public class RateLimiterAutoConfiguration { private static final Logger log = LoggerFactory.getLogger(RateLimiterAutoConfiguration.class); - @PostConstruct - public void init() { + @Bean + public RateLimiterAspect rateLimiterAspect() { log.info("[ContiNew Starter] - Auto Configuration 'RateLimiterAspect' completed initialization."); + return new RateLimiterAspect(); } } diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java deleted file mode 100644 index acd17ed6..00000000 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/converter/RateLimiterKeyConverter.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - *

- * 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 - *

- * http://www.gnu.org/licenses/lgpl.html - *

- * 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.converter; - -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.reflect.MethodSignature; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.expression.MethodBasedEvaluationContext; -import org.springframework.core.DefaultParameterNameDiscoverer; -import org.springframework.core.ParameterNameDiscoverer; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.ParseException; -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 java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -/** - * 限流Key转换器 - * - * @author KAI - * @date 2024-5-28 21:19:14 - */ -@Component -public class RateLimiterKeyConverter { - - private static final Logger log = LoggerFactory.getLogger(RateLimiterKeyConverter.class); - - private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); - - private final ExpressionParser parser = new SpelExpressionParser(); - - /** - * 生成限流 Key - * - * @param joinPoint 切点 - * @param rateLimiter 限流注解 - * @return 限流 Key - */ - public String getKey(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) { - // 获取目标方法 - Method method = getMethod(joinPoint); - // 创建限流key存储对象 - List keys = new ArrayList<>(); - // 添加限流名称 - keys.add(Optional.ofNullable(rateLimiter.name()) - .orElse(method.getDeclaringClass().getName() + "." + method.getName())); - - // 解析Spel表达式 生成限流条件 - getSpelDefinitionKey(rateLimiter.condition(), method, joinPoint.getArgs(), keys); - // 拼接限流 Key - return StringUtils.collectionToDelimitedString(keys, ".", "", ""); - } - - /** - * 解析 SpEL 表达式,生成动态限流 Key - * - * @param spelKey SpEL 表达式 - * @param method 目标方法 - * @param parameterValues 方法参数值 - * @param keys 限流 Key 存储对象 - */ - private void getSpelDefinitionKey(String spelKey, Method method, Object[] parameterValues, List keys) { - - if (StringUtils.hasText(spelKey)) { - try { - // 创建 SpEL 表达式上下文 - EvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, discoverer); - // 解析 SpEL 表达式并获取值,使用 Optional 的 orElseThrow 方法处理空值 - String key = parser.parseExpression(spelKey).getValue(context, String.class); - if (StringUtils.hasText(key)) { - keys.add(key); - } else { - throw new IllegalArgumentException("SpEL 表达式解析结果为空: " + spelKey); - } - } catch (ParseException e) { - log.error("解析 SpEL 表达式错误: {}", spelKey, e); - throw e; - } - } - - } - - /** - * 获取目标方法 - * - * @param joinPoint 切点 - * @return 目标方法 - */ - public Method getMethod(ProceedingJoinPoint joinPoint) { - MethodSignature signature = (MethodSignature) joinPoint.getSignature(); - Method method = signature.getMethod(); - - // 如果目标方法是接口方法,则获取其实现类的方法 - if (method.getDeclaringClass().isInterface()) { - try { - method = joinPoint.getTarget() - .getClass() - .getDeclaredMethod(signature.getName(), method.getParameterTypes()); - } catch (Exception e) { - log.error(e.getMessage()); - throw new IllegalArgumentException(e.getMessage()); - } - } - return method; - } -} diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/enums/LimitType.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/enums/LimitType.java new file mode 100644 index 00000000..9bf16804 --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/enums/LimitType.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.enums; + +public enum LimitType { + + /** + * 全局限流 + */ + DEFAULT, + /** + * 根据IP限流 + */ + IP, + /** + * 根据实例限流(支持集群多实例) + */ + CLUSTER +} diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/resources/spel-extension.json b/continew-starter-security/continew-starter-security-limiter/src/main/resources/spel-extension.json new file mode 100644 index 00000000..9088db73 --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/resources/spel-extension.json @@ -0,0 +1,7 @@ +{ + "top.continew.starter.security.limiter.annotation.RateLimiter@key":{ + "method":{ + "parameters": true + } + } +} \ No newline at end of file diff --git a/continew-starter-security/pom.xml b/continew-starter-security/pom.xml index 1a0f4f54..7b599f5d 100644 --- a/continew-starter-security/pom.xml +++ b/continew-starter-security/pom.xml @@ -26,5 +26,17 @@ top.continew continew-starter-core + + + + top.continew + continew-starter-web + + + + + top.continew + continew-starter-cache-redisson + \ No newline at end of file -- Gitee From 09b4168dd9e32d82a970bb4161c143986521fcab Mon Sep 17 00:00:00 2001 From: KAI <1373639299@qq.com> Date: Fri, 14 Jun 2024 16:53:24 +0800 Subject: [PATCH 10/14] =?UTF-8?q?feat:=20=E9=80=9A=E8=BF=87=E6=B3=A8?= =?UTF-8?q?=E8=A7=A3=E5=AE=9E=E7=8E=B0=E9=99=90=E6=B5=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- continew-starter-dependencies/pom.xml | 6 + .../continew-starter-security-limiter/pom.xml | 45 +++ .../limiter/annotation/RateLimiter.java | 61 +++++ .../limiter/annotation/RateLimiters.java | 29 ++ .../limiter/aop/RateLimiterAspect.java | 257 ++++++++++++++++++ .../RateLimiterAutoConfiguration.java | 37 +++ .../autoconfigure/RateLimiterProperties.java | 54 ++++ .../security/limiter/enums/LimitType.java | 33 +++ .../exception/RateLimiterException.java | 25 ++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../src/main/resources/spel-extension.json | 7 + continew-starter-security/pom.xml | 1 + 12 files changed, 556 insertions(+) create mode 100644 continew-starter-security/continew-starter-security-limiter/pom.xml create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiters.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterProperties.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/enums/LimitType.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/exception/RateLimiterException.java create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 continew-starter-security/continew-starter-security-limiter/src/main/resources/spel-extension.json diff --git a/continew-starter-dependencies/pom.xml b/continew-starter-dependencies/pom.xml index 9003d930..22dfcb0b 100644 --- a/continew-starter-dependencies/pom.xml +++ b/continew-starter-dependencies/pom.xml @@ -473,6 +473,12 @@ ${revision} + + top.continew + continew-starter-security-limiter + ${revision} + + top.continew diff --git a/continew-starter-security/continew-starter-security-limiter/pom.xml b/continew-starter-security/continew-starter-security-limiter/pom.xml new file mode 100644 index 00000000..2e079cef --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/pom.xml @@ -0,0 +1,45 @@ + + 4.0.0 + + top.continew + continew-starter-security + ${revision} + + + continew-starter-security-limiter + ContiNew Starter 安全模块 - 限流模块 + + + + + org.redisson + redisson-spring-boot-starter + + + + org.aspectj + aspectjrt + 1.9.4 + + + org.aspectj + aspectjweaver + 1.9.4 + + + + cn.hutool + hutool-all + + + + top.continew + continew-starter-cache-redisson + + + top.continew + continew-starter-web + + + diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java new file mode 100644 index 00000000..19fae1ba --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.annotation; + +import org.redisson.api.RateIntervalUnit; +import top.continew.starter.security.limiter.enums.LimitType; + +import java.lang.annotation.*; + +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface RateLimiter { + + /** + * LimitType 限流模式 + * DEFAULT 全局限流 + * IP IP限流 + * CLUSTER 实例限流 + */ + LimitType limitType() default LimitType.DEFAULT; + + /** + * 限流key 支持 Spring EL 表达式 + */ + String key() default ""; + + /** + * 单位时间产生的令牌数 + */ + int rate() default Integer.MAX_VALUE; + + /** + * 限流时间 + */ + int rateInterval() default 0; + + /** + * 时间单位,默认毫秒 + */ + RateIntervalUnit timeUnit() default RateIntervalUnit.MILLISECONDS; + + /** + * 拒绝请求时的提示信息 + */ + String message() default "您操作过于频繁,请稍后再试!"; +} \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiters.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiters.java new file mode 100644 index 00000000..08da165b --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiters.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.annotation; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface RateLimiters { + /** + * 用于管理多个 RateLimiter + */ + RateLimiter[] value(); +} diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java new file mode 100644 index 00000000..12567fab --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 + * @date 2024-5-28 10:19:42 + */ +@Aspect +@Component +public class RateLimiterAspect { + + private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); + + private static final ConcurrentHashMap 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(":"); + // 根据限流类型添加不同的信息 + 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(); + } +} diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java new file mode 100644 index 00000000..42cfc16d --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.autoconfigure; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import top.continew.starter.security.limiter.aop.RateLimiterAspect; + +@AutoConfiguration +@EnableConfigurationProperties(RateLimiterProperties.class) +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(); + } +} diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterProperties.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterProperties.java new file mode 100644 index 00000000..e978f6e4 --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterProperties.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.autoconfigure; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.StringUtils; + +/** + * @author KAI + * @date 2024/05/28 23:28> + */ +@ConfigurationProperties(prefix = "continew-starter.security.limiter") +public class RateLimiterProperties { + private boolean enabled = false; + + private String limiterKey = "RateLimiter:"; + + public boolean isEnabled() { + return enabled; + } + + 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 + ":"; + } + } + this.limiterKey = limiterKey; + } + +} diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/enums/LimitType.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/enums/LimitType.java new file mode 100644 index 00000000..9bf16804 --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/enums/LimitType.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.enums; + +public enum LimitType { + + /** + * 全局限流 + */ + DEFAULT, + /** + * 根据IP限流 + */ + IP, + /** + * 根据实例限流(支持集群多实例) + */ + CLUSTER +} diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/exception/RateLimiterException.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/exception/RateLimiterException.java new file mode 100644 index 00000000..49467e0a --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/exception/RateLimiterException.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.exception; + +import top.continew.starter.core.exception.BaseException; + +public class RateLimiterException extends BaseException { + public RateLimiterException(String message) { + super(message); + } +} diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/continew-starter-security/continew-starter-security-limiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..d365e008 --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +top.continew.starter.security.limiter.autoconfigure.RateLimiterAutoConfiguration \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/resources/spel-extension.json b/continew-starter-security/continew-starter-security-limiter/src/main/resources/spel-extension.json new file mode 100644 index 00000000..9088db73 --- /dev/null +++ b/continew-starter-security/continew-starter-security-limiter/src/main/resources/spel-extension.json @@ -0,0 +1,7 @@ +{ + "top.continew.starter.security.limiter.annotation.RateLimiter@key":{ + "method":{ + "parameters": true + } + } +} \ No newline at end of file diff --git a/continew-starter-security/pom.xml b/continew-starter-security/pom.xml index dd87ac2f..1a0f4f54 100644 --- a/continew-starter-security/pom.xml +++ b/continew-starter-security/pom.xml @@ -17,6 +17,7 @@ continew-starter-security-password continew-starter-security-mask continew-starter-security-crypto + continew-starter-security-limiter -- Gitee From da4b87df2992cacef917ee44386064d86d06d0b6 Mon Sep 17 00:00:00 2001 From: KAI <1373639299@qq.com> Date: Fri, 14 Jun 2024 16:58:31 +0800 Subject: [PATCH 11/14] =?UTF-8?q?feat:=20=E9=80=9A=E8=BF=87=E6=B3=A8?= =?UTF-8?q?=E8=A7=A3=E5=AE=9E=E7=8E=B0=E9=99=90=E6=B5=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../continew-starter-security-limiter/pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/continew-starter-security/continew-starter-security-limiter/pom.xml b/continew-starter-security/continew-starter-security-limiter/pom.xml index 2e079cef..6baca6ea 100644 --- a/continew-starter-security/continew-starter-security-limiter/pom.xml +++ b/continew-starter-security/continew-starter-security-limiter/pom.xml @@ -33,10 +33,6 @@ hutool-all - - top.continew - continew-starter-cache-redisson - top.continew continew-starter-web -- Gitee From b3884caa6984b6802573165c504ff43c86e49e8f Mon Sep 17 00:00:00 2001 From: KAI <1373639299@qq.com> Date: Fri, 14 Jun 2024 16:59:16 +0800 Subject: [PATCH 12/14] =?UTF-8?q?feat:=20=E9=80=9A=E8=BF=87=E6=B3=A8?= =?UTF-8?q?=E8=A7=A3=E5=AE=9E=E7=8E=B0=E9=99=90=E6=B5=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../continew-starter-security-limiter/pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/continew-starter-security/continew-starter-security-limiter/pom.xml b/continew-starter-security/continew-starter-security-limiter/pom.xml index 6baca6ea..edf94c6f 100644 --- a/continew-starter-security/continew-starter-security-limiter/pom.xml +++ b/continew-starter-security/continew-starter-security-limiter/pom.xml @@ -33,9 +33,5 @@ hutool-all - - top.continew - continew-starter-web - -- Gitee From 3798836330941c1d1155681ddd1ae4bbba0a8c52 Mon Sep 17 00:00:00 2001 From: KAI <1373639299@qq.com> Date: Wed, 19 Jun 2024 19:45:07 +0800 Subject: [PATCH 13/14] Add `name` field to `RateLimiter` annotation and use it in `RateLimiterAspect` to build Redis key. --- .../starter/security/limiter/annotation/RateLimiter.java | 5 +++++ .../starter/security/limiter/aop/RateLimiterAspect.java | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java index 19fae1ba..e680c7d6 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java @@ -34,6 +34,11 @@ public @interface RateLimiter { */ LimitType limitType() default LimitType.DEFAULT; + /** + * 缓存实例名称 + */ + String name() default ""; + /** * 限流key 支持 Spring EL 表达式 */ diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java index 12567fab..a60e9aca 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java @@ -238,6 +238,14 @@ public class RateLimiterAspect { 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: -- Gitee From d51c7ed280466ff39a17de53fbd902ac506fe547 Mon Sep 17 00:00:00 2001 From: KAI <1373639299@qq.com> Date: Mon, 24 Jun 2024 19:22:23 +0800 Subject: [PATCH 14/14] =?UTF-8?q?feat:=20=E4=BB=A3=E7=A0=81=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- continew-starter-dependencies/pom.xml | 1 + .../continew-starter-security-limiter/pom.xml | 5 +++++ .../starter/security/limiter/annotation/RateLimiter.java | 5 +++++ .../starter/security/limiter/annotation/RateLimiters.java | 6 +++++- .../starter/security/limiter/aop/RateLimiterAspect.java | 2 +- .../limiter/autoconfigure/RateLimiterAutoConfiguration.java | 6 ++++++ .../limiter/autoconfigure/RateLimiterProperties.java | 3 ++- .../continew/starter/security/limiter/enums/LimitType.java | 5 +++++ .../security/limiter/exception/RateLimiterException.java | 5 +++++ continew-starter-security/pom.xml | 5 ----- .../web/autoconfigure/exception/GlobalExceptionHandler.java | 5 ----- 11 files changed, 35 insertions(+), 13 deletions(-) diff --git a/continew-starter-dependencies/pom.xml b/continew-starter-dependencies/pom.xml index 22dfcb0b..8db6f89e 100644 --- a/continew-starter-dependencies/pom.xml +++ b/continew-starter-dependencies/pom.xml @@ -473,6 +473,7 @@ ${revision} + top.continew continew-starter-security-limiter diff --git a/continew-starter-security/continew-starter-security-limiter/pom.xml b/continew-starter-security/continew-starter-security-limiter/pom.xml index edf94c6f..68c3c5ff 100644 --- a/continew-starter-security/continew-starter-security-limiter/pom.xml +++ b/continew-starter-security/continew-starter-security-limiter/pom.xml @@ -33,5 +33,10 @@ hutool-all + + + top.continew + continew-starter-cache-redisson + diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java index e680c7d6..bceed306 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiter.java @@ -21,6 +21,11 @@ import top.continew.starter.security.limiter.enums.LimitType; import java.lang.annotation.*; +/** + * 限流注解 + * @author KAI + * @since 2.2.0 + */ @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiters.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiters.java index 08da165b..b669e9bf 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiters.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/annotation/RateLimiters.java @@ -17,7 +17,11 @@ package top.continew.starter.security.limiter.annotation; import java.lang.annotation.*; - +/** + * 限流组 + * @author KAI + * @since 2.2.0 + */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java index a60e9aca..aa9b87fc 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/aop/RateLimiterAspect.java @@ -55,7 +55,7 @@ import java.util.concurrent.ConcurrentHashMap; * 限流 AOP 拦截器 * * @author KAI - * @date 2024-5-28 10:19:42 + * @since 2.2.0 */ @Aspect @Component diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java index 42cfc16d..9c5d1f75 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterAutoConfiguration.java @@ -23,6 +23,12 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean; import top.continew.starter.security.limiter.aop.RateLimiterAspect; +/** + * 限流配置注入器 + * @author KAI + * @since 2.2.0 + */ + @AutoConfiguration @EnableConfigurationProperties(RateLimiterProperties.class) public class RateLimiterAutoConfiguration { diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterProperties.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterProperties.java index e978f6e4..7cdc059f 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterProperties.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/autoconfigure/RateLimiterProperties.java @@ -20,8 +20,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.util.StringUtils; /** + * 限流器配置属性 * @author KAI - * @date 2024/05/28 23:28> + * @since 2.2.0 */ @ConfigurationProperties(prefix = "continew-starter.security.limiter") public class RateLimiterProperties { diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/enums/LimitType.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/enums/LimitType.java index 9bf16804..edfb65b6 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/enums/LimitType.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/enums/LimitType.java @@ -16,6 +16,11 @@ package top.continew.starter.security.limiter.enums; +/** + * 限流类型 + * @author KAI + * @since 2.2.0 + */ public enum LimitType { /** diff --git a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/exception/RateLimiterException.java b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/exception/RateLimiterException.java index 49467e0a..20331b2a 100644 --- a/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/exception/RateLimiterException.java +++ b/continew-starter-security/continew-starter-security-limiter/src/main/java/top/continew/starter/security/limiter/exception/RateLimiterException.java @@ -18,6 +18,11 @@ package top.continew.starter.security.limiter.exception; import top.continew.starter.core.exception.BaseException; +/** + * 限流异常 + * @author KAI + * @since 2.2.0 + */ public class RateLimiterException extends BaseException { public RateLimiterException(String message) { super(message); diff --git a/continew-starter-security/pom.xml b/continew-starter-security/pom.xml index 7b599f5d..84df7437 100644 --- a/continew-starter-security/pom.xml +++ b/continew-starter-security/pom.xml @@ -33,10 +33,5 @@ continew-starter-web - - - top.continew - continew-starter-cache-redisson - \ No newline at end of file diff --git a/continew-starter-web/src/main/java/top/continew/starter/web/autoconfigure/exception/GlobalExceptionHandler.java b/continew-starter-web/src/main/java/top/continew/starter/web/autoconfigure/exception/GlobalExceptionHandler.java index 2e01bb09..57e07c52 100644 --- a/continew-starter-web/src/main/java/top/continew/starter/web/autoconfigure/exception/GlobalExceptionHandler.java +++ b/continew-starter-web/src/main/java/top/continew/starter/web/autoconfigure/exception/GlobalExceptionHandler.java @@ -155,9 +155,4 @@ public class GlobalExceptionHandler { return R.fail(e.getMessage()); } - @ExceptionHandler(RasterFormatException.class) - public R handleRasterFormatException(RasterFormatException e, HttpServletRequest request) { - log.error("请求地址 [{}],限流拦截。", request.getRequestURI(), e); - return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); - } } \ No newline at end of file -- Gitee