Prechádzať zdrojové kódy

feature:000.000.006:后端生成图表的接口添加限流策略;
使用Redisson的RRateLimiter令牌限流器实现;

yang yi 1 mesiac pred
rodič
commit
6341dd0467

+ 2 - 0
BI_front/README.md

@@ -3,3 +3,5 @@
 This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
 
 Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
+
+使用node版本v18.20.4

+ 10 - 11
serve/pom.xml

@@ -35,11 +35,6 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
-        <!--<dependency>-->
-        <!--    <groupId>cn.afterturn</groupId>-->
-        <!--    <artifactId>easypoi-spring-boot-starter</artifactId>-->
-        <!--    <version>4.4.0</version>-->
-        <!--</dependency>-->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
@@ -49,7 +44,10 @@
             <artifactId>knife4j-spring-boot-starter</artifactId>
             <version>2.0.8</version>
         </dependency>
-
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
         <dependency>
             <groupId>cn.hutool</groupId>
             <artifactId>hutool-all</artifactId>
@@ -77,16 +75,17 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-validation</artifactId>
         </dependency>
-        <!--<dependency>-->
-        <!--    <groupId>com.alibaba</groupId>-->
-        <!--    <artifactId>easyexcel</artifactId>-->
-        <!--    <version>4.0.3</version>-->
-        <!--</dependency>-->
         <dependency>
             <groupId>org.apache.poi</groupId>
             <artifactId>poi-ooxml</artifactId>
             <version>3.17</version>
         </dependency>
+        <!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson</artifactId>
+            <version>3.23.3</version>
+        </dependency>
 
 
     </dependencies>

+ 72 - 0
serve/src/main/java/space/anyi/BI/config/RedissonConfig.java

@@ -0,0 +1,72 @@
+package space.anyi.BI.config;
+
+import org.redisson.Redisson;
+import org.redisson.RedissonRateLimiter;
+import org.redisson.api.RRateLimiter;
+import org.redisson.api.RateIntervalUnit;
+import org.redisson.api.RateType;
+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;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @ProjectName: serve
+ * @FileName: RedissonConfig
+ * @Author: 杨逸
+ * @Data:2024/12/7 14:03
+ * @Description:
+ */
+@ConfigurationProperties("spring.redis")
+@Configuration
+public class RedissonConfig {
+    private String host;
+    private String password;
+    private Integer database;
+    private Integer port;
+    @Bean
+    public RedissonClient rateLimiter(){
+        Config config = new Config();
+        config.useSingleServer()
+                .setDatabase(database)
+                //.setPassword(password)
+                .setAddress("redis://"+host+":"+port);
+        RedissonClient redissonClient = Redisson.create(config);
+        return redissonClient;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public Integer getDatabase() {
+        return database;
+    }
+
+    public void setDatabase(Integer database) {
+        this.database = database;
+    }
+
+    public Integer getPort() {
+        return port;
+    }
+
+    public void setPort(Integer port) {
+        this.port = port;
+    }
+}

+ 11 - 5
serve/src/main/java/space/anyi/BI/controller/ChartController.java

@@ -1,27 +1,23 @@
 package space.anyi.BI.controller;
 
 import cn.hutool.core.util.IdUtil;
-import cn.hutool.poi.excel.ExcelUtil;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
-import space.anyi.BI.BIApplication;
 import space.anyi.BI.entity.Chart;
 import space.anyi.BI.entity.ResponseResult;
 import space.anyi.BI.entity.dto.ChartDTO;
 import space.anyi.BI.entity.vo.ChartVO;
 import space.anyi.BI.entity.vo.PageVO;
+import space.anyi.BI.handler.redisson.RRateLimiterHandler;
 import space.anyi.BI.service.ChartService;
-import space.anyi.BI.util.BeanCopyUtil;
 import space.anyi.BI.util.SecurityUtils;
 
 import javax.annotation.Resource;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -37,6 +33,8 @@ public class ChartController {
     private final static Logger log = LoggerFactory.getLogger(ChartController.class);
     @Resource
     private ChartService chartService;
+    @Resource
+    private RRateLimiterHandler rRateLimiterHandler;
     @PreAuthorize("@ps.hasRole('用户')")
     @GetMapping("/getChartById/{id}")
     @ResponseBody
@@ -82,6 +80,10 @@ public class ChartController {
     @ResponseBody
     public ResponseResult generateChartByAI(ChartDTO chartDTO, MultipartFile file) throws IOException {
         log.info("分析目标:{}图标名称:{}",chartDTO.getAnalysisTarget(),chartDTO.getName());
+        //限流判断
+        if (!rRateLimiterHandler.accessAble("generateChartByAI_"+SecurityUtils.getUserId())){
+            return ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.RATE_LIMIT_ERROR);
+        }
         if (file == null || file.isEmpty()) {
             return ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.FILE_NOT_NULL);
         }
@@ -89,6 +91,10 @@ public class ChartController {
         if (!file.getContentType().equals("application/vnd.ms-excel") && !file.getContentType().equals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) {
             return ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.FILE_TYPE_ERROR);
         }
+        //文件大小判断
+        if (file.getSize()>1024*1024*2L) {
+            return ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.FILE_SIZE_ERROR);
+        }
         ChartVO vo = chartService.generateChartByAI(chartDTO,file);
 
         return ResponseResult.okResult(vo);

+ 3 - 1
serve/src/main/java/space/anyi/BI/entity/ResponseResult.java

@@ -146,7 +146,9 @@ public class ResponseResult<T> implements Serializable {
         VERIFY_CODE_ERROR(517, "验证码错误"),
         USER_STATE_ERROR(518, "用户状态值不正确"),
         FILE_ERROR(528, "文件读取错误"),
-        FILE_IS_EMPTY_ERROR(529, "文件内容不能为空");
+        FILE_IS_EMPTY_ERROR(529, "文件内容不能为空"),
+        FILE_SIZE_ERROR(412, "文件过大"),
+        RATE_LIMIT_ERROR(429, "请求过于频繁"),;
 
         private int code;
         private String msg;

+ 34 - 0
serve/src/main/java/space/anyi/BI/handler/redisson/RRateLimiterHandler.java

@@ -0,0 +1,34 @@
+package space.anyi.BI.handler.redisson;
+
+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 javax.annotation.Resource;
+
+/**
+ * @ProjectName: serve
+ * @FileName: RRateLimiterHandler
+ * @Author: 杨逸
+ * @Data:2024/12/7 14:14
+ * @Description:
+ */
+@Service
+public class RRateLimiterHandler {
+    @Resource
+    private RedissonClient redissonClient;
+    public boolean accessAble(String key){
+        //获取限流器
+        RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
+        //设置限流器速率
+        //rateLimiter.setRate(RateType.OVERALL,5,1, RateIntervalUnit.SECONDS);
+        rateLimiter.trySetRate(RateType.OVERALL,5,1, RateIntervalUnit.SECONDS);
+        //获取令牌,每次获取一个令牌,如果获取不到则返回false
+        //return rateLimiter.tryAcquire(1);
+        //阻塞获取令牌,每次获取一个令牌
+        //rateLimiter.acquire();
+        return rateLimiter.tryAcquire(1);
+    }
+}

+ 40 - 0
serve/src/test/java/space/anyi/BI/handler/redisson/RRateLimiterHandlerTest.java

@@ -0,0 +1,40 @@
+package space.anyi.BI.handler.redisson;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.redisson.api.RRateLimiter;
+import org.redisson.api.RedissonClient;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import javax.annotation.Resource;
+
+
+/**
+ * @ProjectName: serve
+ * @FileName: RRateLimiterHandlerTest
+ * @Author: 杨逸
+ * @Data:2024/12/7 14:26
+ * @Description:
+ */
+@SpringBootTest
+class RRateLimiterHandlerTest {
+    @Resource
+    private RRateLimiterHandler rateLimiterHandler;
+    @Resource
+    private RedissonClient redissonClient;
+    @Test
+    void accessAble() {
+        String key = "test:user13232121321";
+        boolean flag;
+        for (int i = 0; i < 5; i++) {
+            flag = rateLimiterHandler.accessAble(key);
+            //断言
+            Assertions.assertEquals(flag,true);
+        }
+
+        flag = rateLimiterHandler.accessAble(key);
+        //断言
+        Assertions.assertEquals(flag,false);
+    }
+
+}