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