refactor: refactor code

This commit is contained in:
itqop 2025-11-09 15:30:56 +03:00
parent 0c70a4696d
commit be901a31d8
3 changed files with 129 additions and 35 deletions

View File

@ -15,8 +15,8 @@ public final class Config {
.define("apiKey", "your-secret-api-key");
private static final ModConfigSpec.IntValue REQUEST_TIMEOUT = BUILDER
.comment("Request timeout in seconds")
.defineInRange("requestTimeout", 30, 5, 300);
.comment("Request timeout in seconds (recommended: 3-5 to prevent server lag)")
.defineInRange("requestTimeout", 5, 1, 60);
private static final ModConfigSpec.BooleanValue ENABLE_LOGGING = BUILDER
.comment("Enable detailed API logging")

View File

@ -22,6 +22,12 @@ public class WhitelistApiClient {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Gson GSON = new GsonBuilder().create();
// Circuit breaker to prevent log spam
private volatile long lastErrorLogTime = 0;
private volatile int consecutiveErrors = 0;
private static final long ERROR_LOG_COOLDOWN_MS = 60_000; // 1 minute
private static final int ERROR_THRESHOLD = 3;
private volatile HttpClient http;
private volatile String baseUrl;
@ -66,7 +72,10 @@ public class WhitelistApiClient {
.thenApply(resp -> {
if (resp == null) return false;
int code = resp.statusCode();
if (code == 201) return true;
if (code == 201) {
resetErrorCounter();
return true;
}
logHttpError("POST /add", code, resp.body());
return false;
})
@ -99,6 +108,7 @@ public class WhitelistApiClient {
return null;
}
try {
resetErrorCounter();
JsonObject json = JsonParser.parseString(resp.body()).getAsJsonObject();
return WhitelistEntry.fromJson(json);
} catch (Exception e) {
@ -120,7 +130,10 @@ public class WhitelistApiClient {
.thenApply(resp -> {
if (resp == null) return false;
int code = resp.statusCode();
if (code == 204) return true;
if (code == 204) {
resetErrorCounter();
return true;
}
logHttpError("POST /remove", code, resp.body());
return false;
})
@ -143,16 +156,17 @@ public class WhitelistApiClient {
logHttpError("POST /check", code, response.body());
return new CheckResponse(false, false);
}
resetErrorCounter();
JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject();
boolean isWhitelisted = json.has("is_whitelisted") && json.get("is_whitelisted").getAsBoolean();
return new CheckResponse(true, isWhitelisted);
} catch (Exception e) {
LOGGER.error("Failed to parse checkPlayer response for '{}': {}", playerName, e.getMessage(), e);
LOGGER.warn("Failed to parse checkPlayer response for '{}': {}", playerName, e.getMessage());
return new CheckResponse(false, false);
}
})
.exceptionally(ex -> {
LOGGER.error("Failed to check player '{}': {}", playerName, ex.getMessage(), ex);
logApiError("Failed to check player '" + playerName + "'", ex);
return new CheckResponse(false, false);
});
}
@ -219,10 +233,43 @@ public class WhitelistApiClient {
}
private void logHttpError(String endpoint, int statusCode, String responseBody) {
if (Config.enableLogging) {
LOGGER.error("API request failed: {} returned {} - Response: {}", endpoint, statusCode, responseBody);
} else {
LOGGER.error("API request failed: {} returned {}", endpoint, statusCode);
consecutiveErrors++;
// Log only first error and then once per minute to prevent spam
long now = System.currentTimeMillis();
if (consecutiveErrors == 1 || (now - lastErrorLogTime) > ERROR_LOG_COOLDOWN_MS) {
if (Config.enableLogging) {
LOGGER.warn("API request failed: {} returned {} - Response: {}", endpoint, statusCode, responseBody);
} else {
LOGGER.warn("API request failed: {} returned {}", endpoint, statusCode);
}
lastErrorLogTime = now;
if (consecutiveErrors > ERROR_THRESHOLD) {
LOGGER.warn("API has failed {} times consecutively. Further errors will be throttled.", consecutiveErrors);
}
}
}
private void logApiError(String message, Throwable ex) {
consecutiveErrors++;
// Log only first error and then once per minute to prevent spam
long now = System.currentTimeMillis();
if (consecutiveErrors == 1 || (now - lastErrorLogTime) > ERROR_LOG_COOLDOWN_MS) {
LOGGER.warn("{}: {} (errors: {})", message, ex.getMessage(), consecutiveErrors);
lastErrorLogTime = now;
if (consecutiveErrors > ERROR_THRESHOLD) {
LOGGER.warn("API has failed {} times consecutively. Further errors will be throttled.", consecutiveErrors);
}
}
}
private void resetErrorCounter() {
if (consecutiveErrors > 0) {
LOGGER.info("API connection restored after {} consecutive errors", consecutiveErrors);
consecutiveErrors = 0;
}
}

View File

@ -1,51 +1,98 @@
package org.itqop.whitelist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import com.mojang.authlib.GameProfile;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.bus.api.EventPriority;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import org.slf4j.Logger;
import com.mojang.logging.LogUtils;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@EventBusSubscriber(modid = Whitelist.MODID)
public class WhitelistEventHandler {
private static final Logger LOGGER = LogUtils.getLogger();
@SubscribeEvent
public static void registerCommands(RegisterCommandsEvent event) {
WhitelistCommands.register(event.getDispatcher());
}
@SubscribeEvent
@SubscribeEvent(priority = EventPriority.HIGHEST)
public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
if (!Config.enableWhitelist) return;
if (!(event.getEntity() instanceof ServerPlayer sp)) return;
if (!(event.getEntity() instanceof ServerPlayer player)) return;
String name = sp.getGameProfile().getName();
WhitelistApiClient.get().checkPlayer(name).thenAccept(resp -> {
if (resp != null && resp.isSuccess() && !resp.isWhitelisted()) {
// Check if server is still available (player might have disconnected)
if (sp.server == null) return;
GameProfile profile = player.getGameProfile();
String playerName = profile.getName();
sp.server.execute(() -> {
// Double-check connection is still valid
if (sp.connection == null) return;
// Check whitelist synchronously with timeout protection
CompletableFuture<WhitelistApiClient.CheckResponse> checkFuture =
WhitelistApiClient.get().checkPlayer(playerName);
Component link = Component.literal("https://hubmc.org/shop")
.withStyle(style -> style
.withUnderlined(true)
.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://hubmc.org/shop"))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("Открыть в браузере")))
);
try {
// Block login with timeout
WhitelistApiClient.CheckResponse response = checkFuture
.completeOnTimeout(
new WhitelistApiClient.CheckResponse(false, false),
Config.requestTimeout,
TimeUnit.SECONDS
)
.join();
Component msg = Component.literal("Вас нету в белом списке сервера.\n")
.append(Component.literal("Для входа необходимо приобрести проходку на сайте "))
.append(link);
// If check failed or player not whitelisted, kick immediately
if (!response.isSuccess() || !response.isWhitelisted()) {
kickPlayer(player, createKickMessage(response.isSuccess()));
}
sp.connection.disconnect(msg);
});
} catch (Exception e) {
// On any error, deny access
LOGGER.error("Whitelist check error for '{}': {}", playerName, e.getMessage());
kickPlayer(player, Component.literal("Whitelist service error. Please contact administrator."));
}
}
private static void kickPlayer(ServerPlayer player, Component message) {
if (player.server == null || player.connection == null) return;
// Send disconnect message with delay to ensure client receives it
// Without delay, rapid reconnects may fail to show the message
player.server.execute(() -> {
if (player.connection != null) {
try {
// Small delay to ensure message packet is sent before connection close
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
player.connection.disconnect(message);
LOGGER.info("Player '{}' kicked from whitelist", player.getGameProfile().getName());
}
});
}
private static Component createKickMessage(boolean apiAvailable) {
if (!apiAvailable) {
return Component.literal("Whitelist service unavailable. Please try again later.");
}
Component link = Component.literal("https://hubmc.org/shop")
.withStyle(style -> style
.withUnderlined(true)
.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://hubmc.org/shop"))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
Component.literal("Открыть в браузере")))
);
return Component.literal("Вас нету в белом списке сервера.\n")
.append(Component.literal("Для входа необходимо приобрести проходку на сайте "))
.append(link);
}
}