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

  1. Wait for limit.period() seconds so the Redis key expires, then retry the request.
  2. If the endpoint legitimately needs more throughput, raise count/period on its @Limit annotation (e.g. @Limit(key = "login", count = 20, period = 60)).
  3. Verify the Redis connection (spring.redis.host/port) is correct and not shared with another environment that inflates the counter key.
  4. 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

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.