elunez/eladmin · warning · BadRequestException
访问次数受限制
Error message
访问次数受限制
What it means
Thrown by the @Limit rate-limiting aspect (LimitAspect) when a Redis-backed Lua counter shows the caller has already hit an annotated interface more times than allowed within limit.period() seconds. The aspect builds a key from the limit prefix, the caller's IP/key, and the request URI, then increments/reads it in Redis. Exceeding the quota aborts the join point with BadRequestException instead of proceeding.
Source
Thrown at eladmin-common/src/main/java/me/zhengjie/aspect/LimitAspect.java:81
String key = limit.key();
if (StringUtils.isEmpty(key)) {
if (limitType == LimitType.IP) {
key = StringUtils.getIp(request);
} else {
key = signatureMethod.getName();
}
}
ImmutableList<Object> keys = ImmutableList.of(StringUtils.join(limit.prefix(), "_", key, "_", request.getRequestURI().replace("/","_")));
String luaScript = buildLuaScript();
RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
Long count = redisTemplate.execute(redisScript, keys, limit.count(), limit.period());
if (ObjUtil.isNotNull(count) && count.intValue() <= limit.count()) {
logger.info("第{}次访问key为 {},描述为 [{}] 的接口", count, keys, limit.name());
return joinPoint.proceed();
} else {
throw new BadRequestException("访问次数受限制");
}
}
/**
* 限流脚本
*/
private String buildLuaScript() {
return "local c" +
"\nc = redis.call('get',KEYS[1])" +
"\nif c and tonumber(c) > tonumber(ARGV[1]) then" +
"\nreturn c;" +
"\nend" +
"\nc = redis.call('incr',KEYS[1])" +
"\nif tonumber(c) == 1 then" +
"\nredis.call('expire',KEYS[1],ARGV[2])" +
"\nend" +
"\nreturn c;";
}View on GitHub (pinned to 55fbf70595)
Solutions
- Wait for limit.period() seconds so the Redis key expires, then retry the request.
- If the endpoint legitimately needs more throughput, raise count/period on its @Limit annotation (e.g. @Limit(key = "login", count = 20, period = 60)).
- Verify the Redis connection (spring.redis.host/port) is correct and not shared with another environment that inflates the counter key.
- For load/performance tests, call the endpoint with a different EL-TOKEN header key or bypass the aspect in the test profile.
Example fix
// before
@Limit(key = "login", period = 60, count = 5, name = "登录接口限流")
@PostMapping(value = "/login")
public ResponseEntity<Object> login(...) { ... }
// after (raise quota for legitimate traffic)
@Limit(key = "login", period = 60, count = 20, name = "登录接口限流")
@PostMapping(value = "/login")
public ResponseEntity<Object> login(...) { ... } Defensive patterns
Strategy: retry
Validate before calling
// Before a burst of calls, check remaining quota is plausible: simply space calls // so at most limit.count() requests per limit.period() seconds hit the endpoint. // e.g. for @Limit(count=5, period=60): at most 5 calls / 60s per key. long minIntervalMs = (period * 1000L) / count; Thread.sleep(minIntervalMs); // naive client-side pacing for scripts
Try / catch
// HttpClient-ish pseudo: catch 400 with the rate-limit message, honor Retry-After-like delay
try {
return post("/auth/login", body);
} catch (BadRequestException e) {
if (e.getMessage().contains("访问次数受限制")) {
return scheduleRetry(Duration.ofSeconds(limitPeriod)); // wait one window, then retry once
}
throw e;
} Prevention
- Read the @Limit annotation (count/period/LimitType) on any endpoint you integrate with and pace clients below it.
- For load tests, use distinct EL-TOKEN header keys per virtual user when LimitType is TOKEN-based.
- Do not auto-retry instantly on 400 rate-limit responses — back off for at least limit.period() seconds.
- Keep per-environment Redis instances separate so counters do not collide.
When it happens
Trigger: Calling a REST endpoint annotated with @Limit (e.g. POST /auth/login with the default limit) more than limit.count() times within limit.period() seconds from the same key (IP or EL-TOKEN header per LimitType). Also thrown if Redis returns a count already greater than the configured threshold (the Lua script returns early when c > ARGV[1]).
Common situations: Automated login attempts or brute-force password guessing triggering the login rate limit; frontend retry storms hammering a limited endpoint; load tests without raising the limit; a misconfigured or shared Redis where counters from several instances accumulate on the same key.
Related errors
AI-assisted analysis of elunez/eladmin@55fbf70595 (2026-08-14).
Data as JSON: /api/errors/4a3efb190e535a06.
Report an issue: GitHub.