记录方便随时备用
以下两种都是限速每10秒最多请求3次
需要的依赖
<!-- redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 注解方法需引入此依赖,基于方法可以忽略 -->
<!-- aop依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
一、基于方法限流
1.限速验证的service服务
@Service
public class SpeedLimitService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public boolean isAllowed(String key, int limit, int seconds){
// 将 key 的值增加指定的delta(可以为负数)
long count = redisTemplate.opsForValue().increment(key, 1);
if (count == 1) {
// 如果是第一次访问,设置过期时间
redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
}
// 如果计数小于等于限制,允许访问
return count <= limit;
}
}
2.限速验证controller
/**
* RateLimiterService服务,它有一个isAllowed方法,
* 该方法使用Redis的INCR操作来递增特定键的计数,并设置过期时间。
* 如果累加后的计数不超过限制(limit),则允许访问,否则拒绝。
* 每次请求都会检查是否超过了限制,如果超过了,则拒绝访问。
* @return
*/
@PostMapping(name = "限流测试", path = "/testSpeedLimit")
@CrossOrigin
public String testSpeedLimit() {
String key = "user_access_key"; // 用户访问的唯一标识
final int LIMIT = 3; // 时间窗口内允许的最大请求次数
final int SECONDS = 10; // 时间窗口,这里是60秒
if (speedLimitService.isAllowed(key, LIMIT, SECONDS)) {
log.info("存入redis成功,key:{},seconds:{}",key, SECONDS);
// 如果允许访问
return "允许访问";
} else {
// 如果不允许访问
throw new RuntimeException("访问过于频繁,请稍后再试!");
}
}
3.结果验证
{
"timestamp": "2024-05-16T08:25:27.610+0000",
"status": 500,
"error": "Internal Server Error",
"message": "访问过于频繁,请稍后再试!",
"path": "/testSpeedLimit3"
}
二、基于注解限流
1.新增注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentLimiting {
/**
* 缓存key
*/
String key() default "apiKey:";
/**
* 限流时间,单位秒
*/
int time() default 10;
/**
* 限流次数
*/
int count() default 3;
}
2.新增AOP切面
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class CurrentLimitingAspect {
private final RedisTemplate redisTemplate;
/**
* 带有注解的方法之前执行
*/
@SuppressWarnings("unchecked")
@Before("@annotation(currentLimiting)")
public void doBefore(JoinPoint point, CurrentLimiting currentLimiting) throws Throwable {
int time = currentLimiting.time();
int count = currentLimiting.count();
// 将接口方法和用户IP构建Redis的key
String key = getCurrentLimitingKey(currentLimiting.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 == 1){
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
}
/**
* 组装 redis 的 key
*/
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) );
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Class<?> targetClass = method.getDeclaringClass();
return sb.append("_")
// .append( targetClass.getName() )
.append("_").append(method.getName()).toString();
}
/**
* 获取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();
}
return ipAddress;
}
}
3.验证注解@CurrentLimiting
/**
* 基于注解
* @return
*/
@PostMapping(name = "限流测试 - 基于注解", path = "/testAnnotate")
@CurrentLimiting
public void testAnnotate(){
}
4.结果验证
{
"timestamp": "2024-05-16T08:25:27.610+0000",
"status": 500,
"error": "Internal Server Error",
"message": "访问过于频繁,请稍后再试!",
"path": "/testSpeedLimit3"
}
三、基于redisson实现
偷个懒~ 这位帅哥写的还是比较全的。俺就补个依赖
<!-- Redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.2</version>
</dependency>