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");
|
.define("apiKey", "your-secret-api-key");
|
||||||
|
|
||||||
private static final ModConfigSpec.IntValue REQUEST_TIMEOUT = BUILDER
|
private static final ModConfigSpec.IntValue REQUEST_TIMEOUT = BUILDER
|
||||||
.comment("Request timeout in seconds")
|
.comment("Request timeout in seconds (recommended: 3-5 to prevent server lag)")
|
||||||
.defineInRange("requestTimeout", 30, 5, 300);
|
.defineInRange("requestTimeout", 5, 1, 60);
|
||||||
|
|
||||||
private static final ModConfigSpec.BooleanValue ENABLE_LOGGING = BUILDER
|
private static final ModConfigSpec.BooleanValue ENABLE_LOGGING = BUILDER
|
||||||
.comment("Enable detailed API logging")
|
.comment("Enable detailed API logging")
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,12 @@ public class WhitelistApiClient {
|
||||||
private static final Logger LOGGER = LogUtils.getLogger();
|
private static final Logger LOGGER = LogUtils.getLogger();
|
||||||
private static final Gson GSON = new GsonBuilder().create();
|
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 HttpClient http;
|
||||||
private volatile String baseUrl;
|
private volatile String baseUrl;
|
||||||
|
|
||||||
|
|
@ -66,7 +72,10 @@ public class WhitelistApiClient {
|
||||||
.thenApply(resp -> {
|
.thenApply(resp -> {
|
||||||
if (resp == null) return false;
|
if (resp == null) return false;
|
||||||
int code = resp.statusCode();
|
int code = resp.statusCode();
|
||||||
if (code == 201) return true;
|
if (code == 201) {
|
||||||
|
resetErrorCounter();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
logHttpError("POST /add", code, resp.body());
|
logHttpError("POST /add", code, resp.body());
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
|
|
@ -99,6 +108,7 @@ public class WhitelistApiClient {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
resetErrorCounter();
|
||||||
JsonObject json = JsonParser.parseString(resp.body()).getAsJsonObject();
|
JsonObject json = JsonParser.parseString(resp.body()).getAsJsonObject();
|
||||||
return WhitelistEntry.fromJson(json);
|
return WhitelistEntry.fromJson(json);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
@ -120,7 +130,10 @@ public class WhitelistApiClient {
|
||||||
.thenApply(resp -> {
|
.thenApply(resp -> {
|
||||||
if (resp == null) return false;
|
if (resp == null) return false;
|
||||||
int code = resp.statusCode();
|
int code = resp.statusCode();
|
||||||
if (code == 204) return true;
|
if (code == 204) {
|
||||||
|
resetErrorCounter();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
logHttpError("POST /remove", code, resp.body());
|
logHttpError("POST /remove", code, resp.body());
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
|
|
@ -143,16 +156,17 @@ public class WhitelistApiClient {
|
||||||
logHttpError("POST /check", code, response.body());
|
logHttpError("POST /check", code, response.body());
|
||||||
return new CheckResponse(false, false);
|
return new CheckResponse(false, false);
|
||||||
}
|
}
|
||||||
|
resetErrorCounter();
|
||||||
JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject();
|
JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject();
|
||||||
boolean isWhitelisted = json.has("is_whitelisted") && json.get("is_whitelisted").getAsBoolean();
|
boolean isWhitelisted = json.has("is_whitelisted") && json.get("is_whitelisted").getAsBoolean();
|
||||||
return new CheckResponse(true, isWhitelisted);
|
return new CheckResponse(true, isWhitelisted);
|
||||||
} catch (Exception e) {
|
} 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);
|
return new CheckResponse(false, false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exceptionally(ex -> {
|
.exceptionally(ex -> {
|
||||||
LOGGER.error("Failed to check player '{}': {}", playerName, ex.getMessage(), ex);
|
logApiError("Failed to check player '" + playerName + "'", ex);
|
||||||
return new CheckResponse(false, false);
|
return new CheckResponse(false, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -219,10 +233,43 @@ public class WhitelistApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logHttpError(String endpoint, int statusCode, String responseBody) {
|
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) {
|
if (Config.enableLogging) {
|
||||||
LOGGER.error("API request failed: {} returned {} - Response: {}", endpoint, statusCode, responseBody);
|
LOGGER.warn("API request failed: {} returned {} - Response: {}", endpoint, statusCode, responseBody);
|
||||||
} else {
|
} 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;
|
package org.itqop.whitelist;
|
||||||
|
|
||||||
import net.neoforged.bus.api.SubscribeEvent;
|
import com.mojang.authlib.GameProfile;
|
||||||
import net.neoforged.fml.common.EventBusSubscriber;
|
|
||||||
import net.neoforged.neoforge.event.RegisterCommandsEvent;
|
|
||||||
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
|
|
||||||
import net.minecraft.network.chat.ClickEvent;
|
import net.minecraft.network.chat.ClickEvent;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.network.chat.HoverEvent;
|
import net.minecraft.network.chat.HoverEvent;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
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)
|
@EventBusSubscriber(modid = Whitelist.MODID)
|
||||||
public class WhitelistEventHandler {
|
public class WhitelistEventHandler {
|
||||||
|
private static final Logger LOGGER = LogUtils.getLogger();
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void registerCommands(RegisterCommandsEvent event) {
|
public static void registerCommands(RegisterCommandsEvent event) {
|
||||||
WhitelistCommands.register(event.getDispatcher());
|
WhitelistCommands.register(event.getDispatcher());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent(priority = EventPriority.HIGHEST)
|
||||||
public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
|
public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
|
||||||
if (!Config.enableWhitelist) return;
|
if (!Config.enableWhitelist) return;
|
||||||
if (!(event.getEntity() instanceof ServerPlayer sp)) return;
|
if (!(event.getEntity() instanceof ServerPlayer player)) return;
|
||||||
|
|
||||||
String name = sp.getGameProfile().getName();
|
GameProfile profile = player.getGameProfile();
|
||||||
WhitelistApiClient.get().checkPlayer(name).thenAccept(resp -> {
|
String playerName = profile.getName();
|
||||||
if (resp != null && resp.isSuccess() && !resp.isWhitelisted()) {
|
|
||||||
// Check if server is still available (player might have disconnected)
|
|
||||||
if (sp.server == null) return;
|
|
||||||
|
|
||||||
sp.server.execute(() -> {
|
// Check whitelist synchronously with timeout protection
|
||||||
// Double-check connection is still valid
|
CompletableFuture<WhitelistApiClient.CheckResponse> checkFuture =
|
||||||
if (sp.connection == null) return;
|
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")
|
Component link = Component.literal("https://hubmc.org/shop")
|
||||||
.withStyle(style -> style
|
.withStyle(style -> style
|
||||||
.withUnderlined(true)
|
.withUnderlined(true)
|
||||||
.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://hubmc.org/shop"))
|
.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(Component.literal("Для входа необходимо приобрести проходку на сайте "))
|
||||||
.append(link);
|
.append(link);
|
||||||
|
|
||||||
sp.connection.disconnect(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue