结构目录

相关代码
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.17.0</version>
</dependency>
package org.example.redission.config;import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {@Value("${spring.data.redis.host:localhost}")private String redisHost;@Value("${spring.data.redis.port:6379}")private int redisPort;@Value("${spring.data.redis.password:}")private String redisPassword;@Value("${spring.data.redis.database:0}")private int redisDatabase;@Beanpublic RedissonClient redissonClient() {Config config = new Config();String redisUrl = String.format("redis://%s:%d", redisHost, redisPort);config.useSingleServer().setAddress(redisUrl).setDatabase(redisDatabase).setConnectionMinimumIdleSize(10).setConnectionPoolSize(64).setConnectTimeout(3000).setTimeout(3000).setRetryAttempts(3).setRetryInterval(1500);if (redisPassword != null && !redisPassword.isEmpty()) {config.useSingleServer().setPassword(redisPassword);}return Redisson.create(config);}
}
package org.example.redission.annotation;import org.example.redission.handler.ExceptionRejectHandler;
import org.example.redission.handler.RejectHandler;import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter {String key() default "";long rate() default 10;long rateInterval() default 1;TimeUnit rateIntervalUnit() default TimeUnit.SECONDS;boolean waitForPermission() default true;long timeout() default 3;TimeUnit timeoutUnit() default TimeUnit.SECONDS;Class<? extends RejectHandler> rejectHandler() default ExceptionRejectHandler.class;String rejectMessage() default "请求过于频繁,请稍后再试";
}
package org.example.redission.aspect;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.example.redission.annotation.RateLimiter;
import org.example.redission.handler.RejectHandler;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
@Slf4j
public class RateLimiterAspect {@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate ApplicationContext applicationContext;private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();@Around("@annotation(rateLimiter)")public Object around(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable {String key = generateKey(joinPoint, rateLimiter);RRateLimiter limiter = redissonClient.getRateLimiter(key);RateIntervalUnit rateUnit = convertToRedissonUnit(rateLimiter.rateIntervalUnit());limiter.trySetRate(RateType.OVERALL, rateLimiter.rate(), rateLimiter.rateInterval(), rateUnit);boolean acquired = false;if (rateLimiter.waitForPermission()) {acquired = limiter.tryAcquire(1, rateLimiter.timeout(), rateLimiter.timeoutUnit());} else {acquired = limiter.tryAcquire(1);}if (acquired) {return joinPoint.proceed();} else {return handleRejection(joinPoint, rateLimiter);}}private String generateKey(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) {String key = rateLimiter.key();if (StringUtils.hasText(key)) {if (key.contains("#") || key.contains("${")) {Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();EvaluationContext context = new MethodBasedEvaluationContext(joinPoint.getTarget(), method, joinPoint.getArgs(), parameterNameDiscoverer);Expression expression = spelExpressionParser.parseExpression(key);key = expression.getValue(context, String.class);}} else {MethodSignature signature = (MethodSignature) joinPoint.getSignature();key = signature.getDeclaringTypeName() + "." + signature.getName();}return "rate_limiter:" + key;}private Object handleRejection(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable {String message = rateLimiter.rejectMessage();Class<? extends RejectHandler> handlerClass = rateLimiter.rejectHandler();RejectHandler handler;try {handler = applicationContext.getBean(handlerClass);} catch (Exception e) {handler = handlerClass.getDeclaredConstructor().newInstance();}return handler.handleReject(joinPoint, message);}private RateIntervalUnit convertToRedissonUnit(TimeUnit timeUnit) {switch (timeUnit) {case SECONDS:return RateIntervalUnit.SECONDS;case MINUTES:return RateIntervalUnit.MINUTES;case HOURS:return RateIntervalUnit.HOURS;case DAYS:return RateIntervalUnit.DAYS;default:return RateIntervalUnit.SECONDS;}}
}
package org.example.redission.exception;
public class RateLimitException extends RuntimeException {public RateLimitException(String message) {super(message);}public RateLimitException(String message, Throwable cause) {super(message, cause);}
}
handler 处理各种异常
package org.example.redission.handler;import org.aspectj.lang.ProceedingJoinPoint;
public interface RejectHandler {Object handleReject(ProceedingJoinPoint joinPoint, String message) throws Throwable;
}
package org.example.redission.handler;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.util.HashMap;
import java.util.Map;
@Component
@Slf4j
public class CustomRejectHandler implements RejectHandler {@Overridepublic Object handleReject(ProceedingJoinPoint joinPoint, String message) throws Throwable {MethodSignature signature = (MethodSignature) joinPoint.getSignature();String methodName = signature.getName();log.warn("自定义拒绝处理器被触发: 方法={}, 消息={}", methodName, message);if (methodName.contains("user")) {return handleUserRelatedMethod(joinPoint, message);} else if (methodName.contains("data")) {return handleDataRelatedMethod(joinPoint, message);} else {return handleDefaultMethod(joinPoint, message);}}private Object handleUserRelatedMethod(ProceedingJoinPoint joinPoint, String message) {Class<?> returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();if (returnType.equals(String.class)) {return String.format("用户访问受限: %s", message);}return null;}private Object handleDataRelatedMethod(ProceedingJoinPoint joinPoint, String message) {Class<?> returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();if (returnType.equals(String.class)) {return "数据服务暂时不可用,请稍后再试";} else if (returnType.equals(Map.class)) {Map<String, Object> errorResponse = new HashMap<>();errorResponse.put("error", true);errorResponse.put("message", message);errorResponse.put("timestamp", System.currentTimeMillis());return errorResponse;}return null;}private Object handleDefaultMethod(ProceedingJoinPoint joinPoint, String message) {Class<?> returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();if (returnType.equals(String.class)) {return "服务繁忙,请稍后再试";}return null;}
}
package org.example.redission.handler;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
@Component
public class DefaultValueRejectHandler implements RejectHandler {@Overridepublic Object handleReject(ProceedingJoinPoint joinPoint, String message) throws Throwable {return getDefaultValue(joinPoint);}private Object getDefaultValue(ProceedingJoinPoint joinPoint) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Class<?> returnType = signature.getReturnType();if (returnType.equals(Void.TYPE)) {return null;} else if (returnType.equals(Boolean.TYPE) || returnType.equals(Boolean.class)) {return false;} else if (returnType.equals(Integer.TYPE) || returnType.equals(Integer.class)) {return 0;} else if (returnType.equals(Long.TYPE) || returnType.equals(Long.class)) {return 0L;} else if (returnType.equals(Double.TYPE) || returnType.equals(Double.class)) {return 0.0;} else if (returnType.equals(Float.TYPE) || returnType.equals(Float.class)) {return 0.0f;} else if (returnType.equals(String.class)) {return "";} else {return null;}}
}
package org.example.redission.handler;import org.aspectj.lang.ProceedingJoinPoint;
import org.example.redission.exception.RateLimitException;
import org.springframework.stereotype.Component;
@Component
public class ExceptionRejectHandler implements RejectHandler {@Overridepublic Object handleReject(ProceedingJoinPoint joinPoint, String message) throws Throwable {throw new RateLimitException(message);}
}
package org.example.redission.handler;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class FallbackRejectHandler implements RejectHandler {@Overridepublic Object handleReject(ProceedingJoinPoint joinPoint, String message) throws Throwable {log.warn("Rate limit exceeded, executing fallback for method: {}, message: {}", joinPoint.getSignature().toShortString(), message);return getFallbackValue(joinPoint);}private Object getFallbackValue(ProceedingJoinPoint joinPoint) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Class<?> returnType = signature.getReturnType();if (returnType.equals(Void.TYPE)) {return null;} else if (returnType.equals(Boolean.TYPE) || returnType.equals(Boolean.class)) {return false;} else if (returnType.equals(Integer.TYPE) || returnType.equals(Integer.class)) {return -1; } else if (returnType.equals(Long.TYPE) || returnType.equals(Long.class)) {return -1L;} else if (returnType.equals(Double.TYPE) || returnType.equals(Double.class)) {return -1.0;} else if (returnType.equals(Float.TYPE) || returnType.equals(Float.class)) {return -1.0f;} else if (returnType.equals(String.class)) {return "服务繁忙,请稍后再试"; } else {return null;}}
}
package org.example.redission.handler;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class LogAndReturnNullRejectHandler implements RejectHandler {@Overridepublic Object handleReject(ProceedingJoinPoint joinPoint, String message) throws Throwable {log.warn("Rate limit exceeded for method: {}, message: {}", joinPoint.getSignature().toShortString(), message);return null;}
}
测试类
package org.example.redission.controller;import org.example.redission.annotation.RateLimiter;
import org.example.redission.handler.DefaultValueRejectHandler;
import org.example.redission.handler.FallbackRejectHandler;
import org.example.redission.handler.LogAndReturnNullRejectHandler;
import org.example.redission.handler.CustomRejectHandler;
import org.springframework.web.bind.annotation.*;import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/test")
public class TestController {@GetMapping("/basic")@RateLimiter(rate = 1, rateInterval = 1, rateIntervalUnit = TimeUnit.SECONDS)public String basicRateLimit() {return "Basic rate limit test success at " + LocalDateTime.now();}@GetMapping("/user/{userId}")@RateLimiter(key = "'user:' + #userId", rate = 1,rateInterval = 1, rateIntervalUnit = TimeUnit.SECONDS,rejectMessage = "用户请求过于频繁,请稍后再试")public String userRateLimit(@PathVariable String userId) {return "User " + userId + " rate limit test success at " + LocalDateTime.now();}@GetMapping("/nowait")@RateLimiter(rate = 1,rateInterval = 10,rateIntervalUnit = TimeUnit.SECONDS,waitForPermission = false,rejectHandler = DefaultValueRejectHandler.class)public String noWaitRateLimit() {return "No wait rate limit test success at " + LocalDateTime.now();}@GetMapping("/null")@RateLimiter(rate = 1, rateInterval = 3,waitForPermission = false,rateIntervalUnit = TimeUnit.SECONDS,rejectHandler = LogAndReturnNullRejectHandler.class)public String nullRateLimit() {System.out.println(1);return "Null rate limit test success at " + LocalDateTime.now();}@PostMapping("/param")@RateLimiter(key = "'param:' + #request.type + '_' + #request.category",rate = 1,rateInterval = 1,rateIntervalUnit = TimeUnit.MINUTES)public String paramRateLimit(@RequestBody TestRequest request) {return "Param rate limit test success for " + request.getType() + " and " + request.getCategory() + " at " + LocalDateTime.now();}@GetMapping("/custom")@RateLimiter(rate = 1,rateInterval = 5,rateIntervalUnit = TimeUnit.SECONDS,waitForPermission = false,rejectHandler = CustomRejectHandler.class,rejectMessage = "自定义限流处理")public String customRateLimit() {return "Custom rate limit test success at " + LocalDateTime.now();}@GetMapping("/fallback")@RateLimiter(rate = 1,rateInterval = 5,rateIntervalUnit = TimeUnit.SECONDS,waitForPermission = false,rejectHandler = FallbackRejectHandler.class,rejectMessage = "系统繁忙,执行降级")public String fallbackRateLimit() {return "Fallback rate limit test success at " + LocalDateTime.now();}public static class TestRequest {private String type;private String category;public String getType() {return type;}public void setType(String type) {this.type = type;}public String getCategory() {return category;}public void setCategory(String category) {this.category = category;}}
}