diff --git a/pom.xml b/pom.xml
index 28c7775..9899a9c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -129,7 +129,12 @@
com.google.guava
guava
- 29.0-jre
+ 31.1-jre
+
+
+ org.redisson
+ redisson
+ 3.21.3
diff --git a/src/main/java/top/peng/answerbi/annotation/RateLimiterTag.java b/src/main/java/top/peng/answerbi/annotation/GuavaRateLimiter.java
similarity index 86%
rename from src/main/java/top/peng/answerbi/annotation/RateLimiterTag.java
rename to src/main/java/top/peng/answerbi/annotation/GuavaRateLimiter.java
index 4ac6d62..1599f4d 100644
--- a/src/main/java/top/peng/answerbi/annotation/RateLimiterTag.java
+++ b/src/main/java/top/peng/answerbi/annotation/GuavaRateLimiter.java
@@ -13,7 +13,7 @@ import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
- * RateLimiter 限流注解
+ * GuavaRateLimiter 单机限流注解 与 RedissonRateLimiter 二选一使用即可
*
* @author yunpeng
* @version 1.0 2023/7/20
@@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
-public @interface RateLimiterTag {
+public @interface GuavaRateLimiter {
int NOT_LIMITED = 0;
/**
* 用户qps, 每个用户每秒的请求限制
diff --git a/src/main/java/top/peng/answerbi/annotation/RedissonRateLimiter.java b/src/main/java/top/peng/answerbi/annotation/RedissonRateLimiter.java
new file mode 100644
index 0000000..320ef62
--- /dev/null
+++ b/src/main/java/top/peng/answerbi/annotation/RedissonRateLimiter.java
@@ -0,0 +1,30 @@
+/*
+ * @(#)RateLimiter.java
+ *
+ * Copyright © 2023 YunPeng Corporation.
+ */
+package top.peng.answerbi.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * RedissonRateLimiter 分布式限流注解 与 GuavaRateLimiter 二选一使用即可
+ *
+ * @author yunpeng
+ * @version 1.0 2023/7/20
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RedissonRateLimiter {
+ int NOT_LIMITED = 0;
+ /**
+ * 用户qps, 每个用户每秒的请求限制
+ */
+ long qps() default NOT_LIMITED;
+}
diff --git a/src/main/java/top/peng/answerbi/aop/RateLimiterInterceptor.java b/src/main/java/top/peng/answerbi/aop/GuavaRateLimiterInterceptor.java
similarity index 80%
rename from src/main/java/top/peng/answerbi/aop/RateLimiterInterceptor.java
rename to src/main/java/top/peng/answerbi/aop/GuavaRateLimiterInterceptor.java
index c52e96a..8960afa 100644
--- a/src/main/java/top/peng/answerbi/aop/RateLimiterInterceptor.java
+++ b/src/main/java/top/peng/answerbi/aop/GuavaRateLimiterInterceptor.java
@@ -20,14 +20,14 @@ import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
-import top.peng.answerbi.annotation.RateLimiterTag;
+import top.peng.answerbi.annotation.GuavaRateLimiter;
import top.peng.answerbi.common.ErrorCode;
import top.peng.answerbi.exception.ThrowUtils;
import top.peng.answerbi.model.entity.User;
import top.peng.answerbi.service.UserService;
/**
- * RateLimiterInterceptor 限流切面
+ * GuavaRateLimiterInterceptor 单机限流切面
*
* @author yunpeng
* @version 1.0 2023/7/20
@@ -35,14 +35,14 @@ import top.peng.answerbi.service.UserService;
@Slf4j
@Aspect
@Component
-public class RateLimiterInterceptor {
+public class GuavaRateLimiterInterceptor {
@Resource
private UserService userService;
private static final ConcurrentMap RATE_LIMITER_CACHE = new ConcurrentHashMap<>();
- @Around("@annotation(rateLimiterTag)")
- public Object doInterceptor(ProceedingJoinPoint point, RateLimiterTag rateLimiterTag) throws Throwable {
+ @Around("@annotation(guavaRateLimiter)")
+ public Object doInterceptor(ProceedingJoinPoint point, GuavaRateLimiter guavaRateLimiter) throws Throwable {
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
@@ -51,8 +51,8 @@ public class RateLimiterInterceptor {
// 当前请求方法
Method method = ((MethodSignature) point.getSignature()).getMethod();
String key = loginUser.getId() + "#" + method.getName();
- if (rateLimiterTag != null && rateLimiterTag.qps() > RateLimiterTag.NOT_LIMITED) {
- double qps = rateLimiterTag.qps();
+ if (guavaRateLimiter != null && guavaRateLimiter.qps() > GuavaRateLimiter.NOT_LIMITED) {
+ double qps = guavaRateLimiter.qps();
if (RATE_LIMITER_CACHE.get(key) == null) {
// 初始化 QPS
@@ -64,7 +64,7 @@ public class RateLimiterInterceptor {
if (RATE_LIMITER_CACHE.get(key) != null){
RateLimiter limiter = RATE_LIMITER_CACHE.get(key);
ThrowUtils.throwIf(
- !limiter.tryAcquire(rateLimiterTag.timeout(), rateLimiterTag.timeUnit()),
+ !limiter.tryAcquire(guavaRateLimiter.timeout(), guavaRateLimiter.timeUnit()),
ErrorCode.TOO_MANY_REQUEST);
}
}
diff --git a/src/main/java/top/peng/answerbi/aop/RedissonRateLimiterInterceptor.java b/src/main/java/top/peng/answerbi/aop/RedissonRateLimiterInterceptor.java
new file mode 100644
index 0000000..0d9497a
--- /dev/null
+++ b/src/main/java/top/peng/answerbi/aop/RedissonRateLimiterInterceptor.java
@@ -0,0 +1,65 @@
+/*
+ * @(#)RateLimiterInterceptor.java
+ *
+ * Copyright © 2023 YunPeng Corporation.
+ */
+package top.peng.answerbi.aop;
+
+import com.google.common.util.concurrent.RateLimiter;
+import java.lang.reflect.Method;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import top.peng.answerbi.annotation.GuavaRateLimiter;
+import top.peng.answerbi.annotation.RedissonRateLimiter;
+import top.peng.answerbi.common.ErrorCode;
+import top.peng.answerbi.exception.ThrowUtils;
+import top.peng.answerbi.manager.RedisLimiterManager;
+import top.peng.answerbi.model.entity.User;
+import top.peng.answerbi.service.UserService;
+
+/**
+ * RedissonRateLimiterInterceptor 分布式限流切面
+ *
+ * @author yunpeng
+ * @version 1.0 2023/7/20
+ */
+@Slf4j
+@Aspect
+@Component
+public class RedissonRateLimiterInterceptor {
+ @Resource
+ private UserService userService;
+
+ @Resource
+ private RedisLimiterManager redisLimiterManager;
+
+ private static final ConcurrentMap RATE_LIMITER_CACHE = new ConcurrentHashMap<>();
+
+ @Around("@annotation(rRateLimiter)")
+ public Object doInterceptor(ProceedingJoinPoint point, RedissonRateLimiter rRateLimiter) throws Throwable {
+
+ RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
+ HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
+ // 当前登录用户
+ User loginUser = userService.getLoginUser(request);
+ // 当前请求方法
+ Method method = ((MethodSignature) point.getSignature()).getMethod();
+ String key = loginUser.getId() + "#" + method.getName();
+ if (rRateLimiter != null && rRateLimiter.qps() > RedissonRateLimiter.NOT_LIMITED) {
+ redisLimiterManager.doRateLimit(key, rRateLimiter.qps());
+ }
+ return point.proceed();
+ }
+
+}
diff --git a/src/main/java/top/peng/answerbi/config/RedissonConfig.java b/src/main/java/top/peng/answerbi/config/RedissonConfig.java
new file mode 100644
index 0000000..bd4156e
--- /dev/null
+++ b/src/main/java/top/peng/answerbi/config/RedissonConfig.java
@@ -0,0 +1,45 @@
+/*
+ * @(#)RedissonConfig.java
+ *
+ * Copyright © 2023 YunPeng Corporation.
+ */
+package top.peng.answerbi.config;
+
+import lombok.Data;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * RedissonConfig
+ *
+ * @author yunpeng
+ * @version 1.0 2023/7/20
+ */
+@Configuration
+@ConfigurationProperties(prefix = "spring.redis")
+@Data
+public class RedissonConfig {
+
+ private Integer database;
+
+ private String host;
+
+ private Integer port;
+
+ private String password;
+
+ @Bean
+ public RedissonClient getRedissonClient(){
+ Config config = new Config();
+ //添加单机redisson配置
+ config.useSingleServer()
+ .setDatabase(database)
+ .setAddress("redis://" + host + ":" + port)
+ .setPassword(password);
+ return Redisson.create(config);
+ }
+}
diff --git a/src/main/java/top/peng/answerbi/controller/ChartController.java b/src/main/java/top/peng/answerbi/controller/ChartController.java
index aceef12..8c54dfb 100644
--- a/src/main/java/top/peng/answerbi/controller/ChartController.java
+++ b/src/main/java/top/peng/answerbi/controller/ChartController.java
@@ -2,22 +2,18 @@ package top.peng.answerbi.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.gson.Gson;
-import java.io.File;
import java.util.Arrays;
-import java.util.List;
-import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
-import org.apache.poi.util.StringUtil;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import top.peng.answerbi.annotation.AuthCheck;
-import top.peng.answerbi.annotation.RateLimiterTag;
+import top.peng.answerbi.annotation.GuavaRateLimiter;
+import top.peng.answerbi.annotation.RedissonRateLimiter;
import top.peng.answerbi.common.CommonResponse;
import top.peng.answerbi.common.DeleteRequest;
import top.peng.answerbi.common.ErrorCode;
import top.peng.answerbi.common.ResultUtils;
import top.peng.answerbi.constant.BiConstant;
-import top.peng.answerbi.constant.FileConstant;
import top.peng.answerbi.constant.UserConstant;
import top.peng.answerbi.exception.BusinessException;
import top.peng.answerbi.exception.ThrowUtils;
@@ -27,10 +23,8 @@ import top.peng.answerbi.model.dto.chart.ChartEditRequest;
import top.peng.answerbi.model.dto.chart.ChartQueryRequest;
import top.peng.answerbi.model.dto.chart.ChartUpdateRequest;
import top.peng.answerbi.model.dto.chart.GenChartByAiRequest;
-import top.peng.answerbi.model.dto.file.UploadFileRequest;
import top.peng.answerbi.model.entity.Chart;
import top.peng.answerbi.model.entity.User;
-import top.peng.answerbi.model.enums.FileUploadBizEnum;
import top.peng.answerbi.model.vo.BiResponse;
import top.peng.answerbi.service.ChartService;
import top.peng.answerbi.service.UserService;
@@ -238,7 +232,7 @@ public class ChartController {
* @return
*/
@PostMapping("/gen")
- @RateLimiterTag(qps = 1.0, timeout = 100)
+ @RedissonRateLimiter(qps = 1)
public CommonResponse genChartByAi(@RequestPart("file") MultipartFile multipartFile,
GenChartByAiRequest genChartByAiRequest, HttpServletRequest request) {
String chartName = genChartByAiRequest.getChartName();
@@ -269,7 +263,6 @@ public class ChartController {
//压缩后的数据
String csvData = ExcelUtils.excelToCsv(multipartFile);
userInput.append(csvData).append("\n");
-
String aiResult = aiManager.doChat(BiConstant.BI_MODEL_ID, userInput.toString());
BiResponse biResponse = aiManager.aiAnsToBiResp(aiResult);
diff --git a/src/main/java/top/peng/answerbi/manager/RedisLimiterManager.java b/src/main/java/top/peng/answerbi/manager/RedisLimiterManager.java
new file mode 100644
index 0000000..545d4c9
--- /dev/null
+++ b/src/main/java/top/peng/answerbi/manager/RedisLimiterManager.java
@@ -0,0 +1,44 @@
+/*
+ * @(#)RedisLimiterManager.java
+ *
+ * Copyright © 2023 YunPeng Corporation.
+ */
+package top.peng.answerbi.manager;
+
+import javax.annotation.Resource;
+import org.redisson.api.RRateLimiter;
+import org.redisson.api.RateIntervalUnit;
+import org.redisson.api.RateType;
+import org.redisson.api.RedissonClient;
+import org.springframework.stereotype.Service;
+import top.peng.answerbi.common.ErrorCode;
+import top.peng.answerbi.config.RedissonConfig;
+import top.peng.answerbi.exception.ThrowUtils;
+
+/**
+ * RedisLimiterManager 提供 RedisLimiter 限流基础服务的
+ *
+ * @author yunpeng
+ * @version 1.0 2023/7/21
+ */
+@Service
+public class RedisLimiterManager {
+ @Resource
+ private RedissonClient redissonClient;
+
+ /**
+ * 限流操作
+ * @param key 区分不同限流器,比如不同的用户应该分别统计
+ */
+ public void doRateLimit(String key, long qps){
+ //创建一个每秒最低访问两次的限流器 key为限流器名称
+ RRateLimiter rRateLimiter = redissonClient.getRateLimiter(key);
+ //限流器的规则(每秒 qps 个请求(1秒中生产qps个令牌); 连续的请求,最多只能有1个请求被允许通过)
+ //RateType.OVERALL 表示速率限制作用于整个令牌桶, 即所有实例共享
+ rRateLimiter.trySetRate(RateType.OVERALL, qps, 1, RateIntervalUnit.SECONDS);
+ //请求一个令牌
+ boolean canOp = rRateLimiter.tryAcquire(1);
+ //没有令牌可用,抛异常
+ ThrowUtils.throwIf(!canOp, ErrorCode.TOO_MANY_REQUEST);
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 6c32cd0..c542d45 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -22,13 +22,12 @@ spring:
username: root
password: 123456
# Redis 配置
- # todo 需替换配置,然后取消注释
-# redis:
-# database: 1
-# host: localhost
-# port: 6379
-# timeout: 5000
-# password: 123456
+ redis:
+ database: 1
+ host: localhost
+ port: 6379
+ timeout: 5000
+ password: 123456
# Elasticsearch 配置
# todo 需替换配置,然后取消注释
# elasticsearch: