Jelajahi Sumber

新增限流切面

tsurumure 1 tahun lalu
induk
melakukan
c6fea64d5e

+ 19 - 7
src/main/java/com/backendsys/aspect/HttpRequestAspect.java

@@ -28,7 +28,7 @@ public class HttpRequestAspect {
     private HttpServletRequest getRequest() {
         // 获取HttpServletRequest对象
         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
-        HttpServletRequest request = attributes.getRequest();
+        HttpServletRequest request = attributes != null ? attributes.getRequest() : null;
         return request;
     }
 
@@ -37,9 +37,15 @@ public class HttpRequestAspect {
      */
     @Before("serviceMethods()")
     public Long getUserId() {
-        Claims tokenInfo = tokenService.getTokenInfo(getRequest());
-        Integer memberId = (Integer) tokenInfo.get("user_id");
-        return memberId.longValue();
+        HttpServletRequest request = getRequest();
+        if (request != null) {
+            Claims tokenInfo = tokenService.getTokenInfo(request);
+            if (tokenInfo != null) {
+                Integer memberId = (Integer) tokenInfo.get("user_id");
+                return memberId.longValue();
+            }
+        }
+        return null;
     }
 
     /**
@@ -47,8 +53,14 @@ public class HttpRequestAspect {
      */
     @Before("serviceMethods()")
     public Long getMemberId() {
-        Claims tokenInfo = tokenService.getTokenInfo(getRequest());
-        Integer memberId = (Integer) tokenInfo.get("member_id");
-        return memberId.longValue();
+        HttpServletRequest request = getRequest();
+        if (request != null) {
+            Claims tokenInfo = tokenService.getTokenInfo(request);
+            if (tokenInfo != null) {
+                Integer memberId = (Integer) tokenInfo.get("member_id");
+                return memberId.longValue();
+            }
+        }
+        return null;
     }
 }

+ 7 - 13
src/main/java/com/backendsys/aspect/CurrentLimiting.java → src/main/java/com/backendsys/aspect/RateLimiting.java

@@ -7,17 +7,11 @@ import java.lang.annotation.Target;
 
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
-public @interface CurrentLimiting {
-    /**
-     * 缓存key
-     */
-    String key() default "apiKey:";
-    /**
-     * 限流时间,单位秒
-     */
-    int time() default 10;
-    /**
-     * 限流次数
-     */
-    int count() default 3;
+public @interface RateLimiting {
+    // 缓存key
+    String key() default "RateLimiting";
+    // 限流时间,单位秒
+    int duration() default 10;
+    // 限流次数
+    int limit() default 3;
 }

+ 19 - 16
src/main/java/com/backendsys/aspect/CurrentLimitingAspect.java → src/main/java/com/backendsys/aspect/RateLimitingAspect.java

@@ -1,5 +1,7 @@
 package com.backendsys.aspect;
 
+import com.backendsys.exception.CustomException;
+import com.backendsys.exception.CustomExceptionSimple;
 import jakarta.servlet.http.HttpServletRequest;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -19,7 +21,7 @@ import java.util.concurrent.TimeUnit;
 @Aspect
 @Component
 @RequiredArgsConstructor
-public class CurrentLimitingAspect {
+public class RateLimitingAspect {
 
     private final RedisTemplate redisTemplate;
 
@@ -27,23 +29,22 @@ public class CurrentLimitingAspect {
      * 带有注解的方法之前执行
      */
     @SuppressWarnings("unchecked")
-    @Before("@annotation(currentLimiting)")
-    public void doBefore(JoinPoint point, CurrentLimiting currentLimiting) throws Throwable {
-        int time = currentLimiting.time();
-        int count = currentLimiting.count();
+    @Before("@annotation(rateLimiting)")
+    public void doBefore(JoinPoint point, RateLimiting rateLimiting) throws Throwable {
+        int duration = rateLimiting.duration();
+        int limit = rateLimiting.limit();
         // 将接口方法和用户IP构建Redis的key
-        String key = getCurrentLimitingKey(currentLimiting.key(), point);
+        String key = getCurrentLimitingKey(rateLimiting.key() + ":", point);
         // 未达到限流次数,自增
         long value = redisTemplate.opsForValue().increment(key, 1);
-
-        if (value > count) {
-            log.error("接口限流,key:{},count:{},currentCount:{}", key, count, value);
-            throw new RuntimeException("访问过于频繁,请稍后再试!");
+        if (value > limit) {
+            log.error("接口限流,key:{},count:{},currentCount:{}", key, limit, value);
+//            throw new CustomExceptionSimple("访问过于频繁,请稍后再试!");
+            throw new CustomException("访问过于频繁,请稍后再试!");
         }
-
         // 第一次请求设置过期时间
         if(value == 1){
-            redisTemplate.expire(key, time, TimeUnit.SECONDS);
+            redisTemplate.expire(key, duration, TimeUnit.SECONDS);
         }
     }
 
@@ -53,15 +54,17 @@ public class CurrentLimitingAspect {
     private String getCurrentLimitingKey(String prefixKey,JoinPoint point) {
         StringBuilder sb = new StringBuilder(prefixKey);
         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+
         HttpServletRequest request = attributes.getRequest();
-//        sb.append( Utils.getIpAddress(request) );
+        if (request != null) sb.append(getIpAddress(request));
 
         MethodSignature signature = (MethodSignature) point.getSignature();
         Method method = signature.getMethod();
         Class<?> targetClass = method.getDeclaringClass();
-        return sb.append("_")
-//                .append( targetClass.getName() )
-                .append("_").append(method.getName()).toString();
+        return sb.append("-")
+            .append( targetClass.getName())
+            .append(".")
+            .append(method.getName()).toString();
     }
 
     /**

+ 35 - 0
src/main/java/com/backendsys/config/ThreadPool/ThreadPoolConfig.java

@@ -0,0 +1,35 @@
+package com.backendsys.config.ThreadPool;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+
+/**
+ * 线程池
+ */
+@Configuration
+@EnableAsync
+public class ThreadPoolConfig {
+
+    @Bean("taskExecutor")
+    public Executor asyncServiceExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        // 设置核心线程数
+        executor.setCorePoolSize(5);
+        // 设置最大线程数
+        executor.setMaxPoolSize(20);
+        // 配置队列大小
+        executor.setQueueCapacity(Integer.MAX_VALUE);
+        // 设置线程活跃时间 (秒)
+        executor.setKeepAliveSeconds(60);
+        // 等待所有任务结束后再关闭线程池
+        executor.setWaitForTasksToCompleteOnShutdown(true);
+        // 执行初始化
+        executor.initialize();
+        return executor;
+    }
+
+}

+ 4 - 0
src/main/java/com/backendsys/controller/api/Systems/SysAuthController.java

@@ -1,5 +1,6 @@
 package com.backendsys.controller.api.Systems;
 
+import com.backendsys.aspect.RateLimiting;
 import com.backendsys.config.Kaptcha.KaptchaUtil;
 import com.backendsys.config.Redis.RedisCache;
 import com.backendsys.config.Security.service.TokenService;
@@ -57,6 +58,7 @@ public class SysAuthController {
      * 登录 (用户名)
      */
     @PostMapping(value = "/api/system/auth/login")
+    @RateLimiting(key = "systemLogin", limit = 5)
     public Result systemLogin(HttpServletRequest request, @Validated(SysUserDTO.Login.class) @RequestBody SysUserDTO sysUserDTO) {
         return Result.success(sysAuthService.login(request, sysUserDTO));
     }
@@ -64,6 +66,7 @@ public class SysAuthController {
      * 登录 (手机号码)
      */
     @PostMapping(value = "/api/system/auth/loginWithPhone")
+    @RateLimiting(key = "systemLoginWithPhone", limit = 5)
     public Result systemLoginWithPhone(HttpServletRequest request, @Validated(SysUserDTO.LoginWithPhone.class) @RequestBody SysUserDTO sysUserDTO) {
         return Result.success(sysAuthService.loginWithPhone(request, sysUserDTO));
     }
@@ -72,6 +75,7 @@ public class SysAuthController {
      * 注册系统用户 (用户名 和 手机号码 必填)
      */
     @PostMapping("/api/public/system/user/registerUser")
+    @RateLimiting(key = "registerUser", limit = 5)
     public Result registerUser(HttpServletRequest request, @Validated(SysUserDTO.Register.class) @RequestBody SysUserDTO sysUserDTO) {
         return Result.success(sysAuthService.registerUser(request, sysUserDTO), "注册成功");
     }

+ 52 - 30
src/main/java/com/backendsys/controller/api/TestController.java

@@ -3,21 +3,17 @@ package com.backendsys.controller.api;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 
-import com.backendsys.aspect.CurrentLimiting;
+import com.backendsys.aspect.RateLimiting;
 import com.backendsys.aspect.QueuingPoll;
-import com.backendsys.entity.Tencent.TencentCos.UploadOriginDTO;
-import com.backendsys.service.SDKService.SDKTencent.SDKTencentCOSService;
 //import com.backendsys.service.SDKService.SDKTinypng.SDKTinypngService;
-import com.backendsys.service.System.SysResourceService;
+import com.backendsys.service.TestService;
 import com.backendsys.utils.ResourceUtil;
-import com.backendsys.utils.response.Result;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 import cn.afterturn.easypoi.word.WordExportUtil;
 import com.backendsys.utils.MD5Util;
 
-import jakarta.servlet.http.HttpServletRequest;
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
 import org.redisson.api.*;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -25,18 +21,17 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.util.StreamUtils;
 import org.springframework.web.bind.annotation.*;
 
 import java.awt.*;
 import java.io.*;
-import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.security.NoSuchAlgorithmException;
 import java.util.*;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 
 
@@ -55,33 +50,60 @@ public class TestController {
 //    @Autowired
 //    private HttpTodoService httpTodoService;
 
-    @PostMapping("testWebHook")
-    public Result testWebHook(@RequestBody Map<String, Object> requestBody, HttpServletRequest request) {
-        System.out.println("------- testWebHook --------");
-//        try {
-//            String requestBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
-//            System.out.println(requestBody);
-//        } catch (IOException e) {
-//            e.printStackTrace();
-//        }
-        System.out.println("===========================================");
-        System.out.println("requestBody:");
-        System.out.println(requestBody);
-        System.out.println("----------------------------");
-        System.out.println("requestBody (JSON):");
-        System.out.println(JSONUtil.parseObj(requestBody));
-        System.out.println("----------------------------");
-        String token = request.getHeader("X-Codeup-Token");
-        System.out.println("X-Codeup-Token: " + token);
-        System.out.println("===========================================");
-        return null;
-    }
+//    @PostMapping("testWebHook")
+//    public Result testWebHook(@RequestBody Map<String, Object> requestBody, HttpServletRequest request) {
+//        System.out.println("------- testWebHook --------");
+////        try {
+////            String requestBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
+////            System.out.println(requestBody);
+////        } catch (IOException e) {
+////            e.printStackTrace();
+////        }
+//        System.out.println("===========================================");
+//        System.out.println("requestBody:");
+//        System.out.println(requestBody);
+//        System.out.println("----------------------------");
+//        System.out.println("requestBody (JSON):");
+//        System.out.println(JSONUtil.parseObj(requestBody));
+//        System.out.println("----------------------------");
+//        String token = request.getHeader("X-Codeup-Token");
+//        System.out.println("X-Codeup-Token: " + token);
+//        System.out.println("===========================================");
+//        return null;
+//    }
+//
 
+    @Autowired
+    private TestService testService;
 
+    @GetMapping("testThreadPool")
+    public String testThreadPool() throws ExecutionException, InterruptedException {
+        testService.testThreadPool();
+        return "ok";
+    }
 
-    @CurrentLimiting(count = 2, time = 10)
+
+    @RateLimiting(key = "test", limit = 5, duration = 10)
     @GetMapping("testSemaphore")
     public String testSemaphore() {
+
+//        Semaphore semaphore = new Semaphore(2);
+//        try {
+//            // 尝试获取一个许可
+//            semaphore.acquire();
+//            // 模拟服务操作
+//            System.out.println("服务被访问,当前活跃线程数:" + semaphore.availablePermits());
+//            // 假设服务操作需要一些时间
+//            Thread.sleep(5000);
+//        } catch (InterruptedException e) {
+//            Thread.currentThread().interrupt();
+//            System.out.println("线程被中断");
+//        } finally {
+//            // 释放许可
+//            semaphore.release();
+//            System.out.println("服务访问结束,释放许可,当前活跃线程数:" + semaphore.availablePermits());
+//        }
+
         return "ok";
     }
 

+ 7 - 0
src/main/java/com/backendsys/exception/CustomExceptionSimple.java

@@ -0,0 +1,7 @@
+package com.backendsys.exception;
+
+public class CustomExceptionSimple extends RuntimeException {
+    public CustomExceptionSimple(String message) {
+        super(message);
+    }
+}

+ 9 - 0
src/main/java/com/backendsys/exception/GlobalExceptionHandler.java

@@ -130,6 +130,15 @@ public class GlobalExceptionHandler implements ResponseBodyAdvice<Object> {
         System.out.println(e);
         return Result.error(e.getErrorCode() != null ? e.getErrorCode() : ResultEnum.PARAMETER_EXCEPTION.getCode(), e.getMessage(), e.getErrorObject() );
     }
+    /**
+     * 自定义异常类 (仅返回文本)
+     */
+    @ExceptionHandler(CustomExceptionSimple.class)
+    public String handleCustomExceptionSimple(CustomExceptionSimple e) {
+        System.out.println("****** CustomExceptionSimple.class: ******");
+        System.out.println(e);
+        return e.getMessage();
+    }
 
 
     /**

+ 9 - 0
src/main/java/com/backendsys/service/TestService.java

@@ -0,0 +1,9 @@
+package com.backendsys.service;
+
+import java.util.concurrent.ExecutionException;
+
+public interface TestService {
+
+    void testThreadPool() throws ExecutionException, InterruptedException;
+
+}

+ 23 - 0
src/main/java/com/backendsys/service/TestServiceImpl.java

@@ -0,0 +1,23 @@
+package com.backendsys.service;
+
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ForkJoinPool;
+
+@Service
+public class TestServiceImpl implements TestService {
+
+    @Async("taskExecutor")
+    public void testThreadPool() {
+        try {
+            Thread.sleep(5000);
+            System.out.println("更新完成 ..");
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+}

+ 3 - 0
src/main/resources/application.yml

@@ -37,6 +37,9 @@ mybatis:
 server:
   servlet:
     context-path: /
+#  tomcat:
+#    threads:
+#      max: 200
 
 # PageHelper分页插件
 pagehelper: