Lazybot applies rate limiting at three independent layers. The first is a global HTTP interceptor that guards every incoming HTTP request based on the caller’s IP address, protecting the Spring Boot application itself from request floods. The second is a per-command token-bucket enforced inside the command chain, configurable per command class via a Java annotation. Both of these layers use the Bucket4j library. The third is the Shiro/OneBot WebSocket adapter’s own built-in event limiter, which throttles raw OneBot events before they ever reach the command router.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Apeuriox/lazybot-renewal/llms.txt
Use this file to discover all available pages before exploring further.
Layer 1 — Global HTTP Rate Limiter
RateLimitInterceptor implements Spring’s HandlerInterceptor and runs as a preHandle check on all HTTP requests. It maintains a ConcurrentHashMap<String, Bucket> keyed by the caller’s remote IP address, creating a new bucket on first contact.
Each per-IP bucket is configured with:
- Capacity — the maximum token count and burst ceiling.
- Refill — tokens added greedily every 1 minute.
429 with the body:
rate-limit.enabled is false.
Configuration
RateLimitInterceptor via @Value. There is no hot-reload; a restart is required after changing them.
Layer 2 — Per-Command Rate Limiter
RateLimitHandler is the third link in the command chain (@Order(2)). It reads the @LazybotRateLimit annotation from the command class at runtime. If the annotation is absent, the handler is a no-op and passes control through immediately.
When the annotation is present, the handler constructs a bucket key from the configured scope and calls LazybotCommandRateLimitManager.tryConsume(key, rateLimit). The manager keeps its own ConcurrentHashMap<String, Bucket> and uses Bucket.refillIntervally (not greedy) so tokens are added in discrete pulses rather than continuously.
Scope and Bucket Keys
| Scope | Key format | Effect |
|---|---|---|
USER | user:<userId>:cmd:<commandType> | Each user has their own independent bucket for this command |
CHANNEL | channel:<groupId>:cmd:<commandType> | All users in a group share one bucket for this command |
GLOBAL | global:cmd:<commandType> | One shared bucket across all users and groups |
@LazybotRateLimit Fields
| Field | Type | Default | Description |
|---|---|---|---|
capacity | long | (required) | Maximum tokens in the bucket (burst size) |
refillTokens | long | (required) | Tokens added per refill interval |
refillPeriod | long | (required) | Duration of the refill interval |
unit | TimeUnit | SECONDS | Time unit for refillPeriod |
scope | Scope enum | GLOBAL | USER, CHANNEL, or GLOBAL |
Annotating a Custom Command
tryConsume returns false on QQ, RateLimitHandler sends the group channel the message [Lazybot] 达到速率限制,请等待50秒 and stops the chain — the command body is not executed.
Layer 3 — Shiro Built-in Limiter
The Shiro/OneBot WebSocket adapter has its own separate limiter that throttles the number of incoming OneBot events before they even reach the command router. It is configured under theshiro.limiter key: