修bug修bug

This commit is contained in:
BRanulf 2025-06-09 17:34:05 +08:00
parent eef2bbebad
commit 0d9e2db5ee
4 changed files with 387 additions and 126 deletions

View File

@ -6,7 +6,7 @@ minecraft_version=1.21.4
yarn_mappings=1.21.4+build.8
loader_version=0.16.10
# Mod Properties
mod_version=1.14.514.132
mod_version=1.14.514.133
maven_group=org.example1
archives_base_name=ServerPlayerOnlineTracker
# Dependencies

View File

@ -6,6 +6,8 @@ import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.Version;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
@ -16,9 +18,18 @@ import net.minecraft.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
public class PlayerTimeMod implements ModInitializer {
@ -28,6 +39,12 @@ public class PlayerTimeMod implements ModInitializer {
private static ModConfig config;
public static LocalizationManager localizationManager;
// RSS Feed URL for releases
private static final String RSS_FEED_URL = "https://git.branulf.top/Branulf/ServerPlayerOnlineTracker/releases.rss";
// Executor for update check to avoid blocking server startup
private static final ExecutorService updateCheckExecutor = Executors.newSingleThreadExecutor();
@Override
public void onInitialize() {
config = new ModConfig(FabricLoader.getInstance().getConfigDir());
@ -36,8 +53,12 @@ public class PlayerTimeMod implements ModInitializer {
try {
LOGGER.info("[在线时间] 初始化 玩家在线时长视奸Mod");
// Check for updates asynchronously on mod initialization
checkForUpdates();
// SERVER_STARTING 阶段创建 Tracker WebServer 实例
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
timeTracker = new PlayerTimeTracker(server);
timeTracker = new PlayerTimeTracker(server); // Tracker 构造函数不再加载数据
try {
webServer = new WebServer(timeTracker, config.getWebPort(), server);
webServer.start();
@ -47,6 +68,16 @@ public class PlayerTimeMod implements ModInitializer {
}
});
// SERVER_STARTED 阶段加载数据 (此时 UserCache 应该已可用)
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
if (timeTracker != null) {
timeTracker.loadData(); // 在服务器启动完成后加载数据
} else {
LOGGER.error("[在线时间] PlayerTimeTracker 未在 SERVER_STARTING 阶段成功初始化!");
}
});
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
if (timeTracker != null) {
timeTracker.onPlayerJoin(handler.player);
@ -66,11 +97,16 @@ public class PlayerTimeMod implements ModInitializer {
webServer.stop();
}
if (timeTracker != null) {
// 在服务器停止前确保所有在线玩家的会话时间被记录
for (ServerPlayerEntity player : server.getPlayerManager().getPlayerList()) {
timeTracker.onPlayerLeave(player);
timeTracker.onPlayerLeave(player); // onPlayerLeave 会自动保存该玩家数据
}
// timeTracker.saveAll(); // onPlayerLeave 已经异步保存这里可以考虑是否还需要 saveAll
// 简单的处理是直接调用 saveAll它会覆盖旧数据
timeTracker.saveAll();
}
// Shutdown the update check executor
updateCheckExecutor.shutdownNow();
});
} catch (Exception e) {
@ -108,10 +144,12 @@ public class PlayerTimeMod implements ModInitializer {
}
private static int showOnlineTime(ServerCommandSource source, int requestedPage) {
// Commands are typically executed after SERVER_STARTED, so UserCache should be available here.
ServerPlayerEntity player = source.getPlayer();
if (player == null) return 0;
CompletableFuture.runAsync(() -> {
// Use the server's main worker executor for command processing
Util.getMainWorkerExecutor().execute(() -> {
PlayerTimeTracker tracker = getTimeTracker();
if (tracker != null) {
Map<String, String> stats = tracker.getWhitelistedPlayerStats();
@ -122,40 +160,79 @@ public class PlayerTimeMod implements ModInitializer {
.collect(Collectors.toList());
sendPaginatedMessage(player, sorted, requestedPage);
} else {
player.sendMessage(Text.literal(localizationManager.getString("playertime.command.error.not_initialized")), false);
}
}, Util.getMainWorkerExecutor());
});
return 1;
}
private static int comparePlayTime(String a, String b) {
String timeA = a.substring(a.indexOf(':') + 1).trim().split(" \\| ")[0];
String timeB = b.substring(b.indexOf(':') + 1).trim().split(" \\| ")[0];
// This parsing logic is specific to the format generated by getWhitelistedPlayerStats
// Example: "PlayerName: 10h 30m | 7d: 5h 15m | 30d: 20h 45m"
// We need to extract the total time part (e.g., "10h 30m")
try {
// Find the first colon and the first pipe
int firstColon = a.indexOf(':');
int firstPipe = a.indexOf('|');
String timeA;
if (firstColon > 0) {
timeA = (firstPipe > firstColon && firstPipe != -1) ? a.substring(firstColon + 1, firstPipe).trim() : a.substring(firstColon + 1).trim();
} else {
timeA = a.trim(); // Fallback if format changes unexpectedly
}
return Long.compare(parseTimeToSeconds(timeB), parseTimeToSeconds(timeA));
int firstColonB = b.indexOf(':');
int firstPipeB = b.indexOf('|');
String timeB;
if (firstColonB > 0) {
timeB = (firstPipeB > firstColonB && firstPipeB != -1) ? b.substring(firstColonB + 1, firstPipeB).trim() : b.substring(firstColonB + 1).trim();
} else {
timeB = b.trim(); // Fallback if format changes unexpectedly
}
return Long.compare(parseTimeToSeconds(timeB), parseTimeToSeconds(timeA)); // Descending order
} catch (Exception e) {
LOGGER.error("Error comparing play times: {} vs {}", a, b, e);
return 0; // Fallback to equal if parsing fails
}
}
private static long parseTimeToSeconds(String timeStr) {
long seconds = 0;
String[] parts = timeStr.split(" ");
// Handle potential empty or null strings
if (timeStr == null || timeStr.trim().isEmpty()) {
return 0;
}
String[] parts = timeStr.trim().split("\\s+"); // Split by one or more spaces
for (String part : parts) {
if (part.endsWith("h")) {
try {
seconds += Integer.parseInt(part.replace("h", "")) * 3600;
seconds += Integer.parseInt(part.substring(0, part.length() - 1)) * 3600;
} catch (NumberFormatException ignored) {}
} else if (part.endsWith("m")) {
try {
seconds += Integer.parseInt(part.replace("m", "")) * 60;
seconds += Integer.parseInt(part.substring(0, part.length() - 1)) * 60;
} catch (NumberFormatException ignored) {}
}
// Ignore other parts like "d:" or numbers without units
}
return seconds;
}
private static String formatPlayerTime(String name, String timeStr) {
// timeStr is already formatted like "Total: Xh Ym | 7d: ... | 30d: ..."
// We just need to prepend the player name
return String.format("§e%s§r: %s", name, timeStr);
}
private static void sendPaginatedMessage(ServerPlayerEntity player, List<String> lines, int page) {
int pageSize = 10;
int totalPages = (lines.size() + pageSize - 1) / pageSize;
@ -166,6 +243,11 @@ public class PlayerTimeMod implements ModInitializer {
player.sendMessage(Text.literal(localizationManager.getString("playertime.command.title", page, totalPages)), false);
if (lines.isEmpty()) {
player.sendMessage(Text.literal(localizationManager.getString("playertime.command.empty_stats")), false);
return;
}
for (int i = from; i < to; i++) {
player.sendMessage(Text.literal(lines.get(i)), false);
}
@ -194,4 +276,112 @@ public class PlayerTimeMod implements ModInitializer {
player.sendMessage(footer, false);
}
/**
* Checks the RSS feed for new releases asynchronously.
*/
private void checkForUpdates() {
updateCheckExecutor.submit(() -> {
try {
// Get current mod version
Optional<ModContainer> modContainer = FabricLoader.getInstance().getModContainer("playertime");
if (modContainer.isEmpty()) {
LOGGER.warn("[在线时间] 无法获取 Mod 容器,跳过更新检查。");
return;
}
Version currentVersion = modContainer.get().getMetadata().getVersion();
String currentVersionString = currentVersion.getFriendlyString();
LOGGER.info("[在线时间] 正在检查更新...");
// Fetch and parse RSS feed
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new URL(RSS_FEED_URL).openStream());
doc.getDocumentElement().normalize();
NodeList itemList = doc.getElementsByTagName("item");
if (itemList.getLength() == 0) {
LOGGER.warn("[在线时间] RSS Feed 中没有找到任何发布项。");
return;
}
// Get the latest item (first one in the feed)
Element latestItem = (Element) itemList.item(0);
String latestVersionString = latestItem.getElementsByTagName("title").item(0).getTextContent();
String latestVersionLink = latestItem.getElementsByTagName("link").item(0).getTextContent();
LOGGER.info("[在线时间] 当前版本: {}, 最新版本 (RSS): {}", currentVersionString, latestVersionString);
// Compare versions
if (isNewerVersion(latestVersionString, currentVersionString)) {
LOGGER.warn("==================================================");
LOGGER.warn("[在线时间] 发现新版本!");
LOGGER.warn("[在线时间] 当前版本: {}", currentVersionString);
LOGGER.warn("[在线时间] 最新版本: {}", latestVersionString);
LOGGER.warn("[在线时间] 下载链接: {}", latestVersionLink);
LOGGER.warn("==================================================");
} else {
LOGGER.info("[在线时间] 当前已是最新版本(也有可能检查失败)。");
}
} catch (javax.xml.parsers.ParserConfigurationException e) {
LOGGER.error("[在线时间] 更新检查失败: XML 解析器配置错误", e);
} catch (org.xml.sax.SAXException e) {
LOGGER.error("[在线时间] 更新检查失败: XML 解析错误", e);
} catch (java.io.IOException e) {
LOGGER.error("[在线时间] 更新检查失败: 网络或文件读取错误", e);
} catch (Exception e) {
LOGGER.error("[在线时间] 更新检查时发生未知错误", e);
}
});
}
/**
* Compares two version strings (e.g., "1.2.3" vs "1.2.4").
* Returns true if newVersion is newer than currentVersion.
* Assumes versions are dot-separated integers.
*/
private boolean isNewerVersion(String newVersion, String currentVersion) {
if (newVersion == null || currentVersion == null || newVersion.isEmpty() || currentVersion.isEmpty()) {
return false; // Cannot compare
}
// Clean up version strings - remove potential prefixes like "v"
String cleanNewVersion = newVersion.toLowerCase().startsWith("v") ? newVersion.substring(1) : newVersion;
String cleanCurrentVersion = currentVersion.toLowerCase().startsWith("v") ? currentVersion.substring(1) : currentVersion;
String[] newParts = cleanNewVersion.split("\\.");
String[] currentParts = cleanCurrentVersion.split("\\.");
int maxLength = Math.max(newParts.length, currentParts.length);
for (int i = 0; i < maxLength; i++) {
int newPart = (i < newParts.length) ? parseIntOrZero(newParts[i]) : 0;
int currentPart = (i < currentParts.length) ? parseIntOrZero(currentParts[i]) : 0;
if (newPart > currentPart) {
return true; // New version is newer
}
if (newPart < currentPart) {
return false; // New version is older
}
// If parts are equal, continue to the next part
}
return false; // Versions are equal
}
/**
* Parses a string part of a version into an integer, returning 0 if parsing fails.
*/
private int parseIntOrZero(String s) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return 0; // Treat non-numeric parts as 0 for comparison
}
}
}

View File

@ -24,9 +24,63 @@ public class PlayerTimeTracker {
public PlayerTimeTracker(MinecraftServer server) {
this.server = server;
this.dataFile = server.getRunDirectory().resolve("player_time_data.json");
loadData();
// loadData() is now called later in ServerLifecycleEvents.SERVER_STARTED
// loadData(); // <-- Remove this line
}
// Make loadData public so it can be called from PlayerTimeMod
public void loadData() {
if (!Files.exists(dataFile)) {
PlayerTimeMod.LOGGER.info("[在线时间] 数据文件未找到,跳过加载");
return;
}
try (Reader reader = Files.newBufferedReader(dataFile)) {
JsonElement jsonElement = JsonParser.parseReader(reader);
if (jsonElement == null || !jsonElement.isJsonObject()) {
PlayerTimeMod.LOGGER.warn("[在线时间] 数据文件为空或格式错误,跳过加载");
return;
}
JsonObject root = jsonElement.getAsJsonObject();
int resetCount = 0;
for (Map.Entry<String, JsonElement> entry : root.entrySet()) {
try {
UUID uuid = UUID.fromString(entry.getKey());
PlayerTimeData data = GSON.fromJson(entry.getValue(), PlayerTimeData.class);
if (data.lastLogin > 0) {
PlayerTimeMod.LOGGER.warn(
// 修改日志直接使用 UUID避免在加载阶段调用 getPlayerName()
"[在线时间] 在数据加载过程中发现玩家UUID{}的最后登录时间大于0{}。将其重置为0。",
uuid, data.lastLogin
);
data.lastLogin = 0;
resetCount++;
}
playerData.put(uuid, data);
} catch (IllegalArgumentException e) {
PlayerTimeMod.LOGGER.error("[在线时间] I数据文件中的 UUID 格式无效: " + entry.getKey(), e);
} catch (JsonParseException e) {
PlayerTimeMod.LOGGER.error("[在线时间] 解析玩家数据失败(UUID: " + entry.getKey() + ")", e);
}
}
PlayerTimeMod.LOGGER.info("[在线时间] 成功加载了 {} 名玩家的数据,重置了 {} 名玩家的上次登录时间", playerData.size(), resetCount);
} catch (IOException e) {
PlayerTimeMod.LOGGER.error("[在线时间] 无法读取玩家在线时间数据文件", e);
} catch (JsonParseException e) {
PlayerTimeMod.LOGGER.error("[在线时间] 玩家在线时间数据文件格式错误", e);
} catch (Exception e) {
// 捕获更广泛的异常以防其他未知错误
PlayerTimeMod.LOGGER.error("[在线时间] 加载玩家在线时间数据时发生未知错误", e);
}
}
public void onPlayerJoin(ServerPlayerEntity player) {
PlayerTimeData data = playerData.computeIfAbsent(player.getUuid(), uuid -> new PlayerTimeData());
data.lastLogin = Instant.now().getEpochSecond();
@ -90,17 +144,24 @@ public class PlayerTimeTracker {
// 获取白名单玩家UUID集合
Set<UUID> whitelistUuids = new HashSet<>();
for (String name : server.getPlayerManager().getWhitelist().getNames()) {
server.getUserCache().findByName(name).ifPresent(profile -> {
whitelistUuids.add(profile.getId());
});
// UserCache 应该在 SERVER_STARTED 之后可用
if (server != null && server.getPlayerManager() != null && server.getUserCache() != null) {
for (String name : server.getPlayerManager().getWhitelist().getNames()) {
server.getUserCache().findByName(name).ifPresent(profile -> {
whitelistUuids.add(profile.getId());
});
}
} else {
PlayerTimeMod.LOGGER.error("[在线时间] 尝试获取白名单玩家统计时 UserCache 或 PlayerManager 不可用!");
return stats; // 返回空Map避免崩溃
}
// 遍历所有已记录玩家数据
playerData.forEach((uuid, data) -> {
// 只处理白名单玩家
if (whitelistUuids.contains(uuid)) {
String playerName = getPlayerName(uuid);
String playerName = getPlayerName(uuid); // UserCache 此时应该可用
long totalTime = data.totalTime;
// 检查玩家是否当前在线只在在线时才计算
@ -126,68 +187,26 @@ public class PlayerTimeTracker {
}
public String getPlayerName(UUID uuid) {
// 这个方法现在只会在 SERVER_STARTED 之后被调用UserCache 应该可用
ServerPlayerEntity player = server.getPlayerManager().getPlayer(uuid);
if (player != null) {
return player.getName().getString();
}
Optional<GameProfile> profile = server.getUserCache().getByUuid(uuid);
if (profile.isPresent()) {
return profile.get().getName();
// 确保 UserCache 不为 null 再使用
if (server != null && server.getUserCache() != null) {
Optional<GameProfile> profile = server.getUserCache().getByUuid(uuid);
if (profile.isPresent()) {
return profile.get().getName();
}
} else {
PlayerTimeMod.LOGGER.warn("[在线时间] 尝试通过 UUID 获取玩家名称时 UserCache 不可用: {}", uuid);
}
return "Unknown";
}
private void loadData() {
if (!Files.exists(dataFile)) {
PlayerTimeMod.LOGGER.info("[在线时间] 数据文件未找到,跳过加载");
return;
}
try (Reader reader = Files.newBufferedReader(dataFile)) {
JsonElement jsonElement = JsonParser.parseReader(reader);
if (jsonElement == null || !jsonElement.isJsonObject()) {
PlayerTimeMod.LOGGER.warn("[在线时间] 数据文件为空或格式错误,跳过加载");
return;
}
JsonObject root = jsonElement.getAsJsonObject();
int resetCount = 0;
for (Map.Entry<String, JsonElement> entry : root.entrySet()) {
try {
UUID uuid = UUID.fromString(entry.getKey());
PlayerTimeData data = GSON.fromJson(entry.getValue(), PlayerTimeData.class);
if (data.lastLogin > 0) {
PlayerTimeMod.LOGGER.warn(
"[在线时间] 在数据加载过程中发现玩家{}UUID{}的最后登录时间大于0{}。将其重置为0。",
getPlayerName(uuid), uuid, data.lastLogin
);
data.lastLogin = 0;
resetCount++;
}
playerData.put(uuid, data);
} catch (IllegalArgumentException e) {
PlayerTimeMod.LOGGER.error("[在线时间] I数据文件中的 UUID 格式无效: " + entry.getKey(), e);
} catch (JsonParseException e) {
PlayerTimeMod.LOGGER.error("[在线时间] 解析玩家数据失败(UUID: " + entry.getKey() + ")", e);
}
}
PlayerTimeMod.LOGGER.info("[在线时间] 成功加载了 {} 名玩家的数据,重置了 {} 名玩家的上次登录时间", playerData.size(), resetCount);
} catch (IOException e) {
PlayerTimeMod.LOGGER.error("[在线时间] 无法读取玩家在线时间数据文件", e);
} catch (JsonParseException e) {
PlayerTimeMod.LOGGER.error("[在线时间] 玩家在线时间数据文件格式错误", e);
} catch (Exception e) {
PlayerTimeMod.LOGGER.error("[在线时间] 加载玩家在线时间数据时发生未知错误", e);
}
}
public void saveAll() {
PlayerTimeMod.LOGGER.info("[在线时间] 开始保存所有玩家数据...");
JsonObject root = new JsonObject();
@ -195,8 +214,12 @@ public class PlayerTimeTracker {
root.add(uuid.toString(), GSON.toJsonTree(data));
});
try (Writer writer = Files.newBufferedWriter(dataFile)) {
GSON.toJson(root, writer);
try {
// 确保父目录存在
Files.createDirectories(dataFile.getParent());
try (Writer writer = Files.newBufferedWriter(dataFile)) {
GSON.toJson(root, writer);
}
PlayerTimeMod.LOGGER.info("[在线时间] 所有玩家数据已成功保存");
} catch (Exception e) {
PlayerTimeMod.LOGGER.error("[在线时间] 无法保存所有玩家在线时间数据", e);
@ -231,10 +254,33 @@ public class PlayerTimeTracker {
root.add(uuid.toString(), GSON.toJsonTree(data));
try (Writer writer = Files.newBufferedWriter(dataFile)) {
GSON.toJson(root, writer);
try {
// 确保父目录存在
Files.createDirectories(dataFile.getParent());
try (Writer writer = Files.newBufferedWriter(dataFile)) {
GSON.toJson(root, writer);
}
} catch (Exception e) {
PlayerTimeMod.LOGGER.error("[在线时间] 无法异步保存玩家的在线时间数据 " + getPlayerName(uuid) + " (UUID: " + uuid + ")", e);
// 异步保存失败时尝试获取玩家名称可能会再次触发 UserCache 问题直接使用 UUID
String playerName = "UUID: " + uuid.toString();
// 尝试获取玩家名称但要小心 UserCache 是否可用
try {
if (server != null && server.getPlayerManager() != null) {
ServerPlayerEntity player = server.getPlayerManager().getPlayer(uuid);
if (player != null) {
playerName = player.getName().getString();
} else if (server.getUserCache() != null) { // 只有 UserCache 可用时才尝试
Optional<GameProfile> profile = server.getUserCache().getByUuid(uuid);
if (profile.isPresent()) {
playerName = profile.get().getName();
}
}
}
} catch (Exception nameEx) {
// Ignore error getting name during error logging
}
PlayerTimeMod.LOGGER.error("[在线时间] 无法异步保存玩家的在线时间数据 " + playerName, e);
}
});
}
@ -280,9 +326,8 @@ public class PlayerTimeTracker {
private void cleanUp(long currentTime) {
long cutoff = currentTime - (days * 24 * 3600L);
while (!entries.isEmpty() && entries.get(0).timestamp < cutoff) {
entries.remove(0);
}
// 使用迭代器安全地移除元素
entries.removeIf(entry -> entry.timestamp < cutoff);
}
private static class TimeEntry {

View File

@ -69,11 +69,12 @@ public class WebServer {
try {
// 白名单
// timeTracker.getWhitelistedPlayerStats() 内部已处理 UserCache null 检查
Map<String, String> stats = timeTracker.getWhitelistedPlayerStats();
String response = GSON.toJson(stats);
sendResponse(exchange, 200, response.getBytes(StandardCharsets.UTF_8), "application/json");
} catch (Exception e) {
PlayerTimeMod.LOGGER.error("[PlayerTime] Failed to get stats data", e);
PlayerTimeMod.LOGGER.error("[在线时间] 获取统计数据失败", e);
sendResponse(exchange, 500, "Internal Server Error");
}
});
@ -124,11 +125,11 @@ public class WebServer {
.orElse(null);
if (is == null) {
PlayerTimeMod.LOGGER.error("[PlayerTime] Default language file (en_us.json) not found!");
PlayerTimeMod.LOGGER.error("[在线时间] 默认语言文件zh_cn.json未找到");
sendResponse(exchange, 500, "Language file not found");
return;
}
PlayerTimeMod.LOGGER.warn("[PlayerTime] Configured language file ({}.json) not found, using default (en_us.json).", PlayerTimeMod.getConfig().getLanguage());
PlayerTimeMod.LOGGER.warn("[在线时间] 配置的语言文件({}.json)未找到,正在使用默认(zh_cn.json)。", PlayerTimeMod.getConfig().getLanguage());
}
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
@ -141,13 +142,13 @@ public class WebServer {
is.close();
sendResponse(exchange, 200, buffer.toByteArray(), "application/json");
PlayerTimeMod.LOGGER.debug("[PlayerTime] Served language file: {}", resourcePath);
PlayerTimeMod.LOGGER.debug("[在线时间] 提供的语言文件: {}", resourcePath);
} catch (IOException e) {
PlayerTimeMod.LOGGER.error("[PlayerTime] Failed to read language file", e);
PlayerTimeMod.LOGGER.error("[在线时间] 无法读取语言文件", e);
sendResponse(exchange, 500, "Error reading language file");
} catch (Exception e) {
PlayerTimeMod.LOGGER.error("[PlayerTime] An unknown error occurred while processing language request", e);
PlayerTimeMod.LOGGER.error("[在线时间] 处理语言请求时发生未知错误", e);
sendResponse(exchange, 500, "Internal Server Error");
}
});
@ -185,7 +186,7 @@ public class WebServer {
if (is == null) {
PlayerTimeMod.LOGGER.warn("[PlayerTime] Static resource not found: {}", resourcePath);
PlayerTimeMod.LOGGER.warn("[在线时间] 找不到静态资源: {}", resourcePath);
sendResponse(exchange, 404, "Not Found");
return;
}
@ -209,16 +210,16 @@ public class WebServer {
is.close();
sendResponse(exchange, 200, buffer.toByteArray(), contentType);
PlayerTimeMod.LOGGER.debug("[PlayerTime] Served static file: {}", resourcePath);
PlayerTimeMod.LOGGER.debug("[在线时间] 提供静态文件: {}", resourcePath);
} catch (Exception e) {
PlayerTimeMod.LOGGER.error("[PlayerTime] Failed to serve static resource", e);
PlayerTimeMod.LOGGER.error("[在线时间] 无法提供静态资源", e);
sendResponse(exchange, 500, "Internal Server Error");
}
});
// 没啥用了
// 没啥用了 (根据注释这个API可能不再使用但保留并增加健壮性)
server.createContext("/api/widget-data", exchange -> {
handleCors(exchange);
if ("OPTIONS".equals(exchange.getRequestMethod())) {
@ -243,12 +244,18 @@ public class WebServer {
JsonArray whitelistPlayers = new JsonArray();
Set<UUID> whitelistUuids = new HashSet<>();
for (String name : playerManager.getWhitelist().getNames()) {
server.getUserCache().findByName(name).ifPresent(profile -> {
whitelistUuids.add(profile.getId());
});
// 增加 UserCache null 检查
if (server != null && server.getUserCache() != null) {
for (String name : playerManager.getWhitelist().getNames()) {
server.getUserCache().findByName(name).ifPresent(profile -> {
whitelistUuids.add(profile.getId());
});
}
} else {
PlayerTimeMod.LOGGER.warn("[在线时间] UserCache 不可用无法获取白名单UUID用于 widget-data");
}
for (ServerPlayerEntity player : playerManager.getPlayerList()) {
UUID uuid = player.getUuid();
if (whitelistUuids.contains(uuid)) {
@ -269,6 +276,7 @@ public class WebServer {
.sorted((a, b) -> Long.compare(b.getValue().totalTime, a.getValue().totalTime))
.limit(3)
.forEach(entry -> {
// timeTracker.getPlayerName() 内部已处理 UserCache null 检查
JsonObject playerJson = new JsonObject();
playerJson.addProperty("name", timeTracker.getPlayerName(entry.getKey()));
playerJson.addProperty("time", PlayerTimeTracker.formatTime(entry.getValue().totalTime)); // formatTime doesn't need localization
@ -280,7 +288,7 @@ public class WebServer {
sendResponse(exchange, 200, GSON.toJson(response).getBytes(StandardCharsets.UTF_8), "application/json");
} catch (Exception e) {
PlayerTimeMod.LOGGER.error("[PlayerTime] Failed to get widget data", e);
PlayerTimeMod.LOGGER.error("[在线时间] 获取小部件数据失败", e);
sendResponse(exchange, 500, "Internal Server Error");
}
});
@ -304,12 +312,18 @@ public class WebServer {
// 获取白名单玩家UUID集合
Set<UUID> whitelistUuids = new HashSet<>();
for (String name : playerManager.getWhitelist().getNames()) {
minecraftServer.getUserCache().findByName(name).ifPresent(profile -> {
whitelistUuids.add(profile.getId());
});
// 增加 UserCache null 检查
if (minecraftServer != null && minecraftServer.getUserCache() != null) {
for (String name : playerManager.getWhitelist().getNames()) {
minecraftServer.getUserCache().findByName(name).ifPresent(profile -> {
whitelistUuids.add(profile.getId());
});
}
} else {
PlayerTimeMod.LOGGER.warn("[在线时间] UserCache 不可用无法获取白名单UUID用于 online-players");
}
// 分类玩家
JsonArray whitelistedPlayers = new JsonArray();
JsonArray nonWhitelistedPlayers = new JsonArray();
@ -332,7 +346,7 @@ public class WebServer {
sendResponse(exchange, 200, GSON.toJson(response).getBytes(StandardCharsets.UTF_8), "application/json");
} catch (Exception e) {
PlayerTimeMod.LOGGER.error("[PlayerTime] Failed to get online players list", e);
PlayerTimeMod.LOGGER.error("[在线时间] 获取在线玩家列表失败", e);
sendResponse(exchange, 500, "Internal Server Error");
}
});
@ -356,12 +370,18 @@ public class WebServer {
// 获取白名单玩家UUID集合
Set<UUID> whitelistUuids = new HashSet<>();
for (String name : playerManager.getWhitelist().getNames()) {
minecraftServer.getUserCache().findByName(name).ifPresent(profile -> {
whitelistUuids.add(profile.getId());
});
// 增加 UserCache null 检查
if (minecraftServer != null && minecraftServer.getUserCache() != null) {
for (String name : playerManager.getWhitelist().getNames()) {
minecraftServer.getUserCache().findByName(name).ifPresent(profile -> {
whitelistUuids.add(profile.getId());
});
}
} else {
PlayerTimeMod.LOGGER.warn("[在线时间] UserCache 不可用无法获取白名单UUID用于 player-count");
}
// 分类计数
int whitelistedCount = 0;
int nonWhitelistedCount = 0;
@ -380,16 +400,16 @@ public class WebServer {
sendResponse(exchange, 200, GSON.toJson(response).getBytes(StandardCharsets.UTF_8), "application/json");
} catch (Exception e) {
PlayerTimeMod.LOGGER.error("[PlayerTime] Failed to get player count", e);
PlayerTimeMod.LOGGER.error("[在线时间] 获取玩家数量失败", e);
sendResponse(exchange, 500, "Internal Server Error");
}
});
// 白名单玩家还有用吗
// 白名单玩家
server.createContext("/api/whitelist", exchange -> {
handleCors(exchange);
if ("OPTIONS".equals(exchange.getRequestMethod())) {
exchange.sendResponseHeaders(204, -1);
exchange.sendResponseHeaders(204, -1); // 204 No Content
return;
}
@ -402,28 +422,34 @@ public class WebServer {
PlayerManager playerManager = minecraftServer.getPlayerManager();
JsonArray whitelist = new JsonArray();
for (String name : playerManager.getWhitelist().getNames()) {
JsonObject player = new JsonObject();
player.addProperty("name", name);
// 增加 UserCache null 检查
if (minecraftServer != null && minecraftServer.getUserCache() != null) {
for (String name : playerManager.getWhitelist().getNames()) {
JsonObject player = new JsonObject();
player.addProperty("name", name);
// 尝试获取UUID
Optional<GameProfile> profile = minecraftServer.getUserCache().findByName(name);
if (profile.isPresent()) {
player.addProperty("uuid", profile.get().getId().toString());
// 尝试获取UUID
Optional<GameProfile> profile = minecraftServer.getUserCache().findByName(name);
if (profile.isPresent()) {
player.addProperty("uuid", profile.get().getId().toString());
// 检查是否在线
ServerPlayerEntity onlinePlayer = playerManager.getPlayer(profile.get().getId());
player.addProperty("online", onlinePlayer != null);
} else {
player.addProperty("online", false);
// 检查是否在线
ServerPlayerEntity onlinePlayer = playerManager.getPlayer(profile.get().getId());
player.addProperty("online", onlinePlayer != null);
} else {
player.addProperty("online", false);
}
whitelist.add(player);
}
whitelist.add(player);
} else {
PlayerTimeMod.LOGGER.warn("[在线时间] UserCache 不可用,无法获取白名单列表");
}
sendResponse(exchange, 200, GSON.toJson(whitelist).getBytes(StandardCharsets.UTF_8), "application/json");
} catch (Exception e) {
PlayerTimeMod.LOGGER.error("[PlayerTime] Failed to get whitelist", e);
PlayerTimeMod.LOGGER.error("[在线时间] 获取白名单失败", e);
sendResponse(exchange, 500, "Internal Server Error");
}
});
@ -500,7 +526,7 @@ public class WebServer {
sendResponse(exchange, 200, GSON.toJson(status).getBytes(StandardCharsets.UTF_8), "application/json");
} catch (Exception e) {
PlayerTimeMod.LOGGER.error("[PlayerTime] Failed to get server status", e);
PlayerTimeMod.LOGGER.error("[在线时间] 无法获取服务器状态", e);
sendResponse(exchange, 500, "Internal Server Error");
}
});
@ -522,7 +548,7 @@ public class WebServer {
Path dataFile = timeTracker.getDataFile(); // 从PlayerTimeTracker获取文件路径
if (!Files.exists(dataFile)) {
PlayerTimeMod.LOGGER.warn("[PlayerTime] Player data file not found: {}", dataFile);
PlayerTimeMod.LOGGER.warn("[在线时间] 玩家数据文件未找到: {}", dataFile);
sendResponse(exchange, 404, "Data file not found");
return;
}
@ -530,13 +556,13 @@ public class WebServer {
byte[] fileContent = Files.readAllBytes(dataFile);
sendResponse(exchange, 200, fileContent, "application/json");
PlayerTimeMod.LOGGER.debug("[PlayerTime] Successfully served player data file {}", dataFile);
PlayerTimeMod.LOGGER.debug("[在线时间] 成功提供玩家数据文件 {}", dataFile);
} catch (IOException e) {
PlayerTimeMod.LOGGER.error("[PlayerTime] Error reading player data file", e);
PlayerTimeMod.LOGGER.error("[在线时间] 读取玩家数据文件时出错", e);
sendResponse(exchange, 500, "Error reading data file");
} catch (Exception e) {
PlayerTimeMod.LOGGER.error("[PlayerTime] An unknown error occurred while processing player data file request", e);
PlayerTimeMod.LOGGER.error("[在线时间] 处理玩家数据文件请求时发生未知错误", e);
sendResponse(exchange, 500, "Internal Server Error");
}
});