wangyunpeng 4 дней назад
Родитель
Сommit
67041d35ac

+ 29 - 0
api-module/src/main/java/com/tzld/piaoquan/api/annotation/RateLimit.java

@@ -0,0 +1,29 @@
+package com.tzld.piaoquan.api.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * IP 限流注解
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RateLimit {
+    
+    /**
+     * 限流时间窗口,单位秒
+     */
+    long timeWindow() default 60;
+    
+    /**
+     * 时间窗口内最大请求数
+     */
+    long maxRequests() default 10;
+    
+    /**
+     * 限流提示信息
+     */
+    String message() default "请求过于频繁,请稍后再试";
+}

+ 88 - 0
api-module/src/main/java/com/tzld/piaoquan/api/aop/RateLimitAop.java

@@ -0,0 +1,88 @@
+package com.tzld.piaoquan.api.aop;
+
+import com.google.common.base.Strings;
+import com.tzld.piaoquan.api.annotation.RateLimit;
+import com.tzld.piaoquan.api.common.enums.ExceptionEnum;
+import com.tzld.piaoquan.api.common.exception.CommonException;
+import com.tzld.piaoquan.growth.common.utils.IpUtil;
+import com.tzld.piaoquan.growth.common.utils.RedisUtils;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+import java.util.Objects;
+
+/**
+ * IP 限流 AOP 切面
+ */
+@Aspect
+@Component
+public class RateLimitAop {
+    
+    private static final Logger log = LoggerFactory.getLogger(RateLimitAop.class);
+    
+    @Autowired
+    private RedisUtils redisUtils;
+    
+    @Pointcut("@annotation(com.tzld.piaoquan.api.annotation.RateLimit)")
+    public void rateLimit() {
+    }
+    
+    /**
+     * 环绕通知,执行限流逻辑
+     */
+    @Around("rateLimit()")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+        
+        // 获取注解信息
+        RateLimit rateLimit = method.getAnnotation(RateLimit.class);
+        long timeWindow = rateLimit.timeWindow();
+        long maxRequests = rateLimit.maxRequests();
+        String message = rateLimit.message();
+        
+        // 获取请求对象
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
+        
+        // 获取客户端 IP
+        String clientIp = IpUtil.getIpAddr(request);
+        
+        if (Strings.isNullOrEmpty(clientIp)) {
+            log.warn("Rate limit - client IP is empty");
+            return joinPoint.proceed();
+        }
+        
+        // 构建 Redis key: rate_limit:{method_name}:{ip}
+        String methodName = method.getName();
+        String redisKey = String.format("rate_limit:%s:%s", methodName, clientIp);
+        
+        // 获取当前请求次数
+        Long currentRequests = redisUtils.getLong(redisKey);
+        
+        if (currentRequests >= maxRequests) {
+            log.warn("Rate limit exceeded - IP: {}, Method: {}, Requests: {}, Max: {}", 
+                    clientIp, methodName, currentRequests, maxRequests);
+            throw new CommonException(ExceptionEnum.PARAM_ERROR.getCode(), message);
+        }
+        
+        // 增加请求计数
+        redisUtils.setIncrementValue(redisKey, 1, timeWindow);
+        
+        log.debug("Rate limit - IP: {}, Method: {}, Current Requests: {}, Max: {}", 
+                clientIp, methodName, currentRequests + 1, maxRequests);
+        
+        return joinPoint.proceed();
+    }
+}

+ 2 - 0
api-module/src/main/java/com/tzld/piaoquan/api/controller/contentplatform/ContentPlatformAccountController.java

@@ -1,6 +1,7 @@
 package com.tzld.piaoquan.api.controller.contentplatform;
 
 import com.tzld.piaoquan.api.annotation.JwtIgnore;
+import com.tzld.piaoquan.api.annotation.RateLimit;
 import com.tzld.piaoquan.api.model.param.contentplatform.*;
 import com.tzld.piaoquan.api.model.vo.contentplatform.AccountLoginVO;
 import com.tzld.piaoquan.api.model.vo.contentplatform.AccountVO;
@@ -29,6 +30,7 @@ public class ContentPlatformAccountController {
     @ApiOperation(value = "发送短信验证码")
     @PostMapping("/sendMessageVerifyCode")
     @JwtIgnore
+    @RateLimit(timeWindow = 3600, maxRequests = 3, message = "请求过于频繁,请稍后再试")
     public CommonResponse<Void> sendMessageVerifyCode(@RequestBody AccountSendMessageParam param) {
         accountService.sendMessageVerifyCode(param);
         return CommonResponse.success();