瀏覽代碼

Dev ratelimit

tsurumure 8 月之前
父節點
當前提交
f8494f3bde

+ 5 - 3
src/main/java/com/backendsys/aspect/RateLimiting.java

@@ -10,8 +10,10 @@ import java.lang.annotation.Target;
 public @interface RateLimiting {
     // 缓存key
     String key() default "RateLimiting";
-    // 限流时间,单位秒
-    int duration() default 10;
-    // 限流次数
+    // 限流时间 (秒)
+    int duration() default 1;
+    // 限流次数 (默认1秒3次比较合理)
     int limit() default 3;
+    // 锁定时长 (秒)
+    int lockDuration() default 5;
 }

+ 22 - 54
src/main/java/com/backendsys/aspect/RateLimitingAspect.java

@@ -1,6 +1,8 @@
 package com.backendsys.aspect;
 
 import com.backendsys.exception.CustException;
+import com.backendsys.modules.common.config.redis.utils.RedisUtil;
+import com.backendsys.modules.common.config.security.utils.HttpRequestUtil;
 import jakarta.servlet.http.HttpServletRequest;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -8,10 +10,9 @@ import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Before;
 import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
 
 import java.lang.reflect.Method;
 import java.util.concurrent.TimeUnit;
@@ -22,68 +23,35 @@ import java.util.concurrent.TimeUnit;
 @RequiredArgsConstructor
 public class RateLimitingAspect {
 
-    private final RedisTemplate redisTemplate;
+    @Autowired
+    private RedisUtil redisUtil;
+    @Autowired
+    private HttpRequestUtil httpRequestUtil;
 
     /**
      * 带有注解的方法之前执行
      */
     @SuppressWarnings("unchecked")
     @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(rateLimiting.key() + ":", point);
-        // 未达到限流次数,自增
-        long value = redisTemplate.opsForValue().increment(key, 1);
-        if (value > limit) {
-            log.error("接口限流,key:{},count:{},currentCount:{}", key, limit, value);
-//            throw new CustExceptionSimple("访问过于频繁,请稍后再试!");
-            throw new CustException("访问过于频繁,请稍后再试!");
-        }
-        // 第一次请求设置过期时间
-        if(value == 1){
-            redisTemplate.expire(key, duration, TimeUnit.SECONDS);
-        }
-    }
-
-    /**
-     * 组装 redis 的 key
-     */
-    private String getCurrentLimitingKey(String prefixKey,JoinPoint point) {
-        StringBuilder sb = new StringBuilder(prefixKey);
-        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+    public void doBefore(RateLimiting rateLimiting) {
 
-        HttpServletRequest request = attributes.getRequest();
-        if (request != null) sb.append(getIpAddress(request));
+        String key = "ratelimit-" + rateLimiting.key() + "-" + httpRequestUtil.getIpAddr() + '-' + httpRequestUtil.getUserId();
+        String lockKey = "lock-" + key;
 
-        MethodSignature signature = (MethodSignature) point.getSignature();
-        Method method = signature.getMethod();
-        Class<?> targetClass = method.getDeclaringClass();
-        return sb.append("-")
-            .append( targetClass.getName())
-            .append(".")
-            .append(method.getName()).toString();
-    }
+        // 已锁定
+        Integer isLocked = redisUtil.getCacheObject(lockKey);
+        if (isLocked != null) throw new CustException("访问过于频繁,请稍后再试!");
 
-    /**
-     * 获取ip地址
-     * @param request
-     * @return
-     */
-    private String getIpAddress(HttpServletRequest request) {
-        // 从请求头或代理头中获取真实IP地址
-        String ipAddress = request.getHeader("X-Forwarded-For");
-        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
-            ipAddress = request.getHeader("Proxy-Client-IP");
-        }
-        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
-            ipAddress = request.getHeader("WL-Proxy-Client-IP");
-        }
-        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
-            ipAddress = request.getRemoteAddr();
+        // 未达到限流次数,自增
+        long value = redisUtil.increment(key, 1);
+        if (value > rateLimiting.limit()) {
+            System.out.println("接口限流: " + key);
+            redisUtil.setCacheObject(lockKey, 1, rateLimiting.lockDuration(), TimeUnit.SECONDS);
+            throw new CustException("访问过于频繁,请稍后再试!");
         }
-        return ipAddress;
+
+        // 第一次请求设置过期时间
+        if (value == 1) redisUtil.expire(key, rateLimiting.duration(), TimeUnit.SECONDS);
     }
 
 }

+ 10 - 0
src/main/java/com/backendsys/modules/common/config/redis/utils/RedisUtil.java

@@ -17,6 +17,16 @@ public class RedisUtil {
 
     private final RedisTemplate redisTemplate;
 
+    // 增量
+    public Long increment(final String key, final long delta) {
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+    // 减量
+    public Long decrement(final String key, final long delta) {
+        return redisTemplate.opsForValue().decrement(key, delta);
+    }
+
+
     /**
      * 缓存基本的对象,Integer、String、实体类等
      *

+ 1 - 0
src/main/java/com/backendsys/modules/common/config/security/utils/HttpRequestUtil.java

@@ -92,6 +92,7 @@ public class HttpRequestUtil {
             }
         } catch (Exception e) {
             ipAddress = "";
+            System.out.println(e.getMessage());
         }
         return ipAddress;
     }

+ 3 - 1
src/main/java/com/backendsys/modules/common/config/security/utils/TokenUtil.java

@@ -77,7 +77,9 @@ public class TokenUtil {
      */
     public Claims getTokenInfo(HttpServletRequest request) {
         String token = getToken(request);
-        if (token != null && !token.isEmpty()) {
+        System.out.println("token = " + token);
+        if (token != null && !token.isEmpty() && !"undefined".equals(token)) {
+            System.out.println("token in!");
             Claims tokenInfo = jwtUtil.extractAllClaims(token);
             return tokenInfo;
         }

+ 1 - 0
src/main/java/com/backendsys/modules/system/controller/SysAuthV2Controller.java

@@ -35,6 +35,7 @@ public class SysAuthV2Controller {
     }
 
     @Operation(summary = "获取手机号地区列表")
+    @RateLimiting(key = "getMobileAreaCode")
     @GetMapping("/api/public/system/getMobileAreaCode")
     public Result getMobileAreaCode(@Validated SysMobileArea sysMobileArea) {
         return Result.success().put("data", sysAuthV2Service.getMobileAreaList(sysMobileArea));

+ 8 - 8
src/main/resources/application-local.yml

@@ -118,14 +118,14 @@ tencent:
     # ------------------------------------------------------
 
     # 香港
-#    region: ap-hongkong
-#    bucket-name: storage-1320301544
-#    accessible-domain: http://cos.daoguyujiamcn.com
-
-    # 广州
-    region: ap-guangzhou
-    bucket-name: duanju3-1320301544
-    accessible-domain: https://duanju3-1320301544.cos.ap-hongkong.myqcloud.com
+    region: ap-hongkong
+    bucket-name: storage-1320301544
+    accessible-domain: http://cos.daoguyujiamcn.com
+
+#    # 广州
+#    region: ap-guangzhou
+#    bucket-name: duanju3-1320301544
+#    accessible-domain: https://duanju3-1320301544.cos.ap-hongkong.myqcloud.com
 
   ivh:
     empty-app-key: 283ca6dc9d4147debc60bf9fc3fbbe03         # 空数据账号 (我自己的子账号)