refactor: refactor code
This commit is contained in:
parent
0c70a4696d
commit
be901a31d8
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
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.error("API request failed: {} returned {} - Response: {}", endpoint, statusCode, responseBody);
|
||||
LOGGER.warn("API request failed: {} returned {} - Response: {}", endpoint, statusCode, responseBody);
|
||||
} else {
|
||||
LOGGER.error("API request failed: {} returned {}", endpoint, statusCode);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
try {
|
||||
// Block login with timeout
|
||||
WhitelistApiClient.CheckResponse response = checkFuture
|
||||
.completeOnTimeout(
|
||||
new WhitelistApiClient.CheckResponse(false, false),
|
||||
Config.requestTimeout,
|
||||
TimeUnit.SECONDS
|
||||
)
|
||||
.join();
|
||||
|
||||
// If check failed or player not whitelisted, kick immediately
|
||||
if (!response.isSuccess() || !response.isWhitelisted()) {
|
||||
kickPlayer(player, createKickMessage(response.isSuccess()));
|
||||
}
|
||||
|
||||
} 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("Открыть в браузере")))
|
||||
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
|
||||
Component.literal("Открыть в браузере")))
|
||||
);
|
||||
|
||||
Component msg = Component.literal("Вас нету в белом списке сервера.\n")
|
||||
return Component.literal("Вас нету в белом списке сервера.\n")
|
||||
.append(Component.literal("Для входа необходимо приобрести проходку на сайте "))
|
||||
.append(link);
|
||||
|
||||
sp.connection.disconnect(msg);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue