feat: persistence util, PM DRY, Discord rate limiter, missing permissions in plugin.yml
This commit is contained in:
parent
dd0fb0f10d
commit
42ebac890c
60 changed files with 418 additions and 374 deletions
|
|
@ -2,6 +2,7 @@ package com.loki.lochat.integrations;
|
|||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.loki.lochat.integrations.discord.DiscordRateLimiter;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ public class DiscordWebhook {
|
|||
private final int timeout;
|
||||
private final int retryAttempts;
|
||||
private final long retryDelay;
|
||||
private final DiscordRateLimiter rateLimiter;
|
||||
|
||||
// Выделенный пул — не засоряем ForkJoinPool.commonPool()
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor(
|
||||
|
|
@ -39,8 +41,10 @@ public class DiscordWebhook {
|
|||
}
|
||||
);
|
||||
|
||||
@SuppressWarnings("checkstyle:ParameterNumber")
|
||||
public DiscordWebhook(JavaPlugin plugin, String webhookUrl, String username, String avatarUrl,
|
||||
int timeout, int retryAttempts, long retryDelay) {
|
||||
int timeout, int retryAttempts, long retryDelay,
|
||||
double maxRequestsPerSecond, int rateLimitBurst) {
|
||||
this.plugin = plugin;
|
||||
this.webhookUrl = webhookUrl != null ? webhookUrl.trim() : null;
|
||||
this.username = username;
|
||||
|
|
@ -48,6 +52,7 @@ public class DiscordWebhook {
|
|||
this.timeout = timeout * 1000;
|
||||
this.retryAttempts = retryAttempts;
|
||||
this.retryDelay = retryDelay;
|
||||
this.rateLimiter = new DiscordRateLimiter(maxRequestsPerSecond, rateLimitBurst);
|
||||
}
|
||||
|
||||
// ── Публичные методы отправки ─────────────────────────────────────────────
|
||||
|
|
@ -114,6 +119,7 @@ public class DiscordWebhook {
|
|||
private CompletableFuture<Boolean> sendWebhook(JsonObject json) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
for (int attempt = 1; attempt <= retryAttempts; attempt++) {
|
||||
rateLimiter.waitIfNeeded();
|
||||
try {
|
||||
if (sendWebhookSync(json.toString())) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -70,6 +70,14 @@ public class DiscordConfig {
|
|||
public boolean isAsync() {
|
||||
return config.getBoolean("performance.async", true);
|
||||
}
|
||||
|
||||
public double getMaxRequestsPerSecond() {
|
||||
return config.getDouble("performance.max-requests-per-second", 5.0);
|
||||
}
|
||||
|
||||
public int getRateLimitBurst() {
|
||||
return config.getInt("performance.rate-limit-burst", 10);
|
||||
}
|
||||
|
||||
public boolean isChatEnabled() {
|
||||
return config.getBoolean("chat.enabled", true);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ public class DiscordMessageService {
|
|||
getAvatarUrl(),
|
||||
config.getTimeout(),
|
||||
config.getRetryAttempts(),
|
||||
config.getRetryDelay()
|
||||
config.getRetryDelay(),
|
||||
config.getMaxRequestsPerSecond(),
|
||||
config.getRateLimitBurst()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package com.loki.lochat.integrations.discord;
|
||||
|
||||
public class DiscordRateLimiter {
|
||||
|
||||
private final double refillRate;
|
||||
private final double capacity;
|
||||
private double tokens;
|
||||
private long lastRefillTime;
|
||||
|
||||
public DiscordRateLimiter(double maxRequestsPerSecond, double burst) {
|
||||
this.refillRate = maxRequestsPerSecond;
|
||||
this.capacity = burst;
|
||||
this.tokens = burst;
|
||||
this.lastRefillTime = System.nanoTime();
|
||||
}
|
||||
|
||||
public synchronized boolean tryAcquire() {
|
||||
refill();
|
||||
if (tokens >= 1.0) {
|
||||
tokens -= 1.0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void waitIfNeeded() {
|
||||
long sleepMs;
|
||||
while (true) {
|
||||
synchronized (this) {
|
||||
refill();
|
||||
if (tokens >= 1.0) {
|
||||
tokens -= 1.0;
|
||||
return;
|
||||
}
|
||||
double waitTimeMs = ((1.0 - tokens) / refillRate) * 1000.0;
|
||||
sleepMs = Math.min((long) Math.ceil(waitTimeMs), 100);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(Math.max(sleepMs, 1));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refill() {
|
||||
long now = System.nanoTime();
|
||||
double elapsed = (now - lastRefillTime) / 1_000_000_000.0;
|
||||
tokens = Math.min(capacity, tokens + elapsed * refillRate);
|
||||
lastRefillTime = now;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue