xkcoding/spring-boot-demo · warning · RuntimeException
手速太快了,慢点儿吧~
Error message
手速太快了,慢点儿吧~
What it means
Thrown by the Redis-based RateLimiterAspect when a method annotated with @RateLimiter exceeds the maximum request count within the configured time window. The limiter uses a Lua script (limitRedisScript) executed via StringRedisTemplate; when executeTimes returns 0 (window full), shouldLimited returns true and a RuntimeException is thrown. The key combines the method/class name with the client IP, so limits are per-IP.
Source
Thrown at demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/aspect/RateLimiterAspect.java:68
Method method = signature.getMethod();
// 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解
RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class);
if (rateLimiter != null) {
String key = rateLimiter.key();
// 默认用类名+方法名做限流的 key 前缀
if (StrUtil.isBlank(key)) {
key = method.getDeclaringClass().getName() + StrUtil.DOT + method.getName();
}
// 最终限流的 key 为 前缀 + IP地址
// TODO: 此时需要考虑局域网多用户访问的情况,因此 key 后续需要加上方法参数更加合理
key = key + SEPARATOR + IpUtil.getIpAddr();
long max = rateLimiter.max();
long timeout = rateLimiter.timeout();
TimeUnit timeUnit = rateLimiter.timeUnit();
boolean limited = shouldLimited(key, max, timeout, timeUnit);
if (limited) {
throw new RuntimeException("手速太快了,慢点儿吧~");
}
}
return point.proceed();
}
private boolean shouldLimited(String key, long max, long timeout, TimeUnit timeUnit) {
// 最终的 key 格式为:
// limit:自定义key:IP
// limit:类名.方法名:IP
key = REDIS_LIMIT_KEY_PREFIX + key;
// 统一使用单位毫秒
long ttl = timeUnit.toMillis(timeout);
// 当前时间毫秒数
long now = Instant.now().toEpochMilli();
long expired = now - ttl;
// 注意这里必须转为 String,否则会报错 java.lang.Long cannot be cast to java.lang.String
Long executeTimes = stringRedisTemplate.execute(limitRedisScript, Collections.singletonList(key), now + "", ttl + "", expired + "", max + "");View on GitHub (pinned to 87a142f960)
Solutions
- Tune the @RateLimiter(max=N, timeout=T) values to match realistic traffic patterns.
- Catch the RuntimeException in a @ControllerAdvice and return HTTP 429 with a Retry-After header.
- Address the TODO: incorporate method parameters or a user/session ID into the key to distinguish users behind a shared NAT IP.
- Verify the Lua script (limitRedisScript bean) is correctly loaded and Redis connectivity is stable — a script error could cause unexpected limiting behavior.
Example fix
// before
throw new RuntimeException("手速太快了,慢点儿吧~");
// after — custom exception for HTTP 429 mapping
throw new RateLimitException("请求过于频繁,请稍后重试");
// TODO fix for shared-IP key collision:
// before
key = key + SEPARATOR + IpUtil.getIpAddr();
// after — add user or session identifier
key = key + SEPARATOR + IpUtil.getIpAddr() + SEPARATOR + SecurityUtil.getCurrentUsername(); Defensive patterns
Strategy: try-catch
Validate before calling
// No direct pre-check API; the limiter is enforced via AOP + Redis Lua script. // On the client, implement rate-limit awareness via a Retry-After header from the 429 response.
Try / catch
// In a @ControllerAdvice handler — map to HTTP 429
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public ResponseEntity<?> handleRateLimit(RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("手速太快了")) {
return ResponseEntity.status(429)
.header("Retry-After", "60")
.body(ApiResponse.ofMessage(429, "Too Many Requests"));
}
throw e;
} Prevention
- Tune @RateLimiter(max=N, timeout=T) values to match real traffic.
- Incorporate user/session ID into the key to avoid NAT-IP collisions (address the TODO).
- Verify the limitRedisScript Lua bean is correctly loaded.
- Create a custom RateLimitException for clean HTTP 429 mapping.
When it happens
Trigger: A single IP sends more than `max` requests (default 10) to a @RateLimiter-annotated method within the timeout window (default 1 minute). The Lua script returns 0 on the next call, and the aspect throws.
Common situations: A legitimate user sending rapid requests; a bot or script hitting the endpoint; shared NAT IP causing multiple users behind one address to share a single limit (the TODO comment acknowledges this); rate limit window too short or max too low for real traffic.
Related errors
AI-assisted analysis of xkcoding/spring-boot-demo@87a142f960 (2026-08-14).
Data as JSON: /api/errors/d261436f3ae36356.
Report an issue: GitHub.