结构目录

相关代码
<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;
@Bean
public 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 {
@Autowired
private RedissonClient redissonClient;
@Autowired
private 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 {
@Override
public 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 {
@Override
public 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 {
@Override
public 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 {
@Override
public 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 {
@Override
public 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;
}
}
}