修复: PVP世界旧虚空世界Windows删除锁致地形失效(重命名回收站强制刷新NORMAL地形) + /weblogin命令主线程同步HTTP卡服改为异步
This commit is contained in:
@@ -221,17 +221,24 @@ public class PVPArenaManager implements Listener {
|
||||
// 世界不存在,或世界存在但已无人活动 → 重新随机生成全新地形。
|
||||
// 这样满足"每次空场进入都随机生成地形";同时,服务器若自动从磁盘加载了
|
||||
// 旧版小平台(残留世界),这里会先卸载并删除磁盘目录,用随机地形替换它。
|
||||
// ★ 关键修复:先清理历史回收站目录,避免磁盘无限增长
|
||||
cleanupOldTrashWorlds();
|
||||
|
||||
if (pvpWorld != null) {
|
||||
plugin.getLogger().info("[PVP] 检测到PVP世界当前无人,卸载旧世界以重新随机生成...");
|
||||
Bukkit.unloadWorld(pvpWorld, false); // false=不保存,直接丢弃旧地形(含旧版小平台)
|
||||
deleteWorldFolder(new File(Bukkit.getWorldContainer(), PVP_WORLD_NAME));
|
||||
// ★ 关键修复:Windows 上 unloadWorld 后 region 文件可能仍被 OS 锁定,
|
||||
// File.delete() 会静默失败导致旧虚空世界残留,并被 createWorld() 原样重新加载
|
||||
// (findSafeSpawn=null → 退化为手工小岛地形)。改用"重命名到回收站"移走,
|
||||
// rename 在文件锁定时仍可靠成功,从而让 createWorld() 生成全新 NORMAL 主世界。
|
||||
moveWorldToTrash(new File(Bukkit.getWorldContainer(), PVP_WORLD_NAME));
|
||||
}
|
||||
|
||||
// 兜底:确保磁盘残留目录被清理(含旧版小平台),再随机重新生成
|
||||
File worldDir = new File(Bukkit.getWorldContainer(), PVP_WORLD_NAME);
|
||||
if (worldDir.exists() && worldDir.isDirectory()) {
|
||||
plugin.getLogger().info("[PVP] 清理磁盘残留的PVP世界目录,准备随机重新生成...");
|
||||
deleteWorldFolder(worldDir);
|
||||
moveWorldToTrash(worldDir);
|
||||
}
|
||||
|
||||
// 创建全新随机地形世界
|
||||
@@ -442,6 +449,48 @@ public class PVPArenaManager implements Listener {
|
||||
folder.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将旧世界目录"重命名"移入回收站(而非直接删除)。
|
||||
* ★ 原因:Windows 上 unloadWorld 后 region 文件可能仍被 OS 锁定,
|
||||
* File.delete() 会静默失败导致旧虚空世界残留;而 File.renameTo() 移动目录在
|
||||
* 文件锁定时仍可靠成功。移走后 createWorld() 因目录不存在而生成全新 NORMAL 主世界地形。
|
||||
*/
|
||||
private void moveWorldToTrash(File folder) {
|
||||
if (folder == null || !folder.exists()) return;
|
||||
File parent = folder.getParentFile();
|
||||
String base = folder.getName();
|
||||
long ts = System.currentTimeMillis();
|
||||
File trash = new File(parent, base + "_trash_" + ts);
|
||||
int i = 0;
|
||||
while (trash.exists()) {
|
||||
i++;
|
||||
trash = new File(parent, base + "_trash_" + ts + "_" + i);
|
||||
}
|
||||
if (folder.renameTo(trash)) {
|
||||
plugin.getLogger().info("[PVP] 旧世界已移入回收站(避免Windows删除锁): " + trash.getName());
|
||||
} else {
|
||||
// 极少数情况下重命名也失败,退回递归删除
|
||||
plugin.getLogger().warning("[PVP] 旧世界重命名失败,退回递归删除: " + base);
|
||||
deleteWorldFolder(folder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理历史回收站目录(pvp_arena_trash_*),避免磁盘无限增长。
|
||||
* best-effort,失败不阻断主流程。
|
||||
*/
|
||||
private void cleanupOldTrashWorlds() {
|
||||
File container = Bukkit.getWorldContainer();
|
||||
if (container == null || !container.isDirectory()) return;
|
||||
File[] list = container.listFiles();
|
||||
if (list == null) return;
|
||||
for (File f : list) {
|
||||
if (f.isDirectory() && f.getName().startsWith(PVP_WORLD_NAME + "_trash_")) {
|
||||
deleteWorldFolder(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消待执行的"空场冷却删除世界"任务(有人重新进入时调用)
|
||||
*/
|
||||
@@ -489,7 +538,8 @@ public class PVPArenaManager implements Listener {
|
||||
p.sendMessage("§e[PVP] 竞技场已关闭,你被传回主世界");
|
||||
}
|
||||
Bukkit.unloadWorld(world, false); // false=不保存(即将删除,避免残留破坏的地形)
|
||||
deleteWorldFolder(new File(Bukkit.getWorldContainer(), PVP_WORLD_NAME));
|
||||
// ★ 同样用重命名移走,避免 Windows 删除锁导致旧世界残留
|
||||
moveWorldToTrash(new File(Bukkit.getWorldContainer(), PVP_WORLD_NAME));
|
||||
plugin.getLogger().info("[PVP] PVP世界已删除,下次进入将随机重新生成地形");
|
||||
}
|
||||
|
||||
|
||||
@@ -4999,45 +4999,59 @@ public class WebManager {
|
||||
|
||||
// ★ 使用push_player_login_status端点,带上online=0(玩家当前不在游戏里执行/weblogin,只是要token)
|
||||
// 实际上玩家就在游戏里,所以online=1
|
||||
// ★ 使用push_player_login_status端点,带上online=1(玩家就在游戏里,已认证)
|
||||
boolean isRegistered = plugin.getDb().userExists(playerName);
|
||||
boolean syncSuccess = false;
|
||||
try {
|
||||
// ★ 获取玩家真实IP地址
|
||||
String playerIp = "";
|
||||
java.net.InetSocketAddress addr = sender.getAddress();
|
||||
if (addr != null && addr.getAddress() != null) {
|
||||
playerIp = addr.getAddress().getHostAddress();
|
||||
}
|
||||
String urlStr = webBaseUrl + "/api/sync.php?action=push_player_login_status"
|
||||
+ "&secret=" + java.net.URLEncoder.encode(secretKey, "UTF-8")
|
||||
+ "&player=" + java.net.URLEncoder.encode(playerName, "UTF-8")
|
||||
+ "&web_token=" + java.net.URLEncoder.encode(token, "UTF-8")
|
||||
+ "&expire_seconds=" + tokenExpireSeconds
|
||||
+ "&online=1"
|
||||
+ "®istered=" + (isRegistered ? "1" : "0")
|
||||
+ "&ip=" + java.net.URLEncoder.encode(playerIp, "UTF-8")
|
||||
+ "&login_verified=1"; // ★ 玩家在游戏里已认证,直接告诉PHP放行
|
||||
final String tokenFinal = token;
|
||||
|
||||
String response = doGet(urlStr);
|
||||
if (response != null) {
|
||||
plugin.getLogger().info("[Web通信] push_player_login_status结果: " + response);
|
||||
if (response.contains("\"success\":true")) {
|
||||
syncSuccess = true;
|
||||
// ★★★ 致命修复:原实现在主线程(命令处理)同步调用 doGet() → HttpClient.send(),
|
||||
// 当 Web 后端不可达时会阻塞服务端主线程 10 秒以上,触发 "server has not responded
|
||||
// for 10 seconds" 线程转储并冻结整个服务器。现改为完全异步执行 HTTP,
|
||||
// 结果通过 runTask 回到主线程再向玩家反馈。 ★★★
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
boolean syncSuccess = false;
|
||||
try {
|
||||
// ★ 获取玩家真实IP地址
|
||||
String playerIp = "";
|
||||
java.net.InetSocketAddress addr = sender.getAddress();
|
||||
if (addr != null && addr.getAddress() != null) {
|
||||
playerIp = addr.getAddress().getHostAddress();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[Web通信] 同步Web登录Token失败: " + e.getMessage());
|
||||
}
|
||||
String urlStr = webBaseUrl + "/api/sync.php?action=push_player_login_status"
|
||||
+ "&secret=" + java.net.URLEncoder.encode(secretKey, "UTF-8")
|
||||
+ "&player=" + java.net.URLEncoder.encode(playerName, "UTF-8")
|
||||
+ "&web_token=" + java.net.URLEncoder.encode(tokenFinal, "UTF-8")
|
||||
+ "&expire_seconds=" + tokenExpireSeconds
|
||||
+ "&online=1"
|
||||
+ "®istered=" + (isRegistered ? "1" : "0")
|
||||
+ "&ip=" + java.net.URLEncoder.encode(playerIp, "UTF-8")
|
||||
+ "&login_verified=1"; // ★ 玩家在游戏里已认证,直接告诉PHP放行
|
||||
|
||||
if (syncSuccess) {
|
||||
sender.sendMessage("§a§l===== Web登录 =====");
|
||||
sender.sendMessage("§e请点击链接登录Web端:");
|
||||
sender.sendMessage("§b" + webBaseUrl + "/login.php?token=" + token);
|
||||
sender.sendMessage("§7有效期: " + tokenExpireSeconds + "秒 | 一次性使用");
|
||||
} else {
|
||||
sender.sendMessage("§c[Web] Token同步失败,请检查Web后端是否可访问");
|
||||
sender.sendMessage("§7当前后端地址: " + webBaseUrl);
|
||||
}
|
||||
String response = doGet(urlStr);
|
||||
if (response != null) {
|
||||
plugin.getLogger().info("[Web通信] push_player_login_status结果: " + response);
|
||||
if (response.contains("\"success\":true")) {
|
||||
syncSuccess = true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[Web通信] 同步Web登录Token失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
// ★ 回到主线程向玩家反馈结果(sendMessage 必须在主线程调用)
|
||||
final boolean ok = syncSuccess;
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (sender instanceof Player && !((Player) sender).isOnline()) return;
|
||||
if (ok) {
|
||||
sender.sendMessage("§a§l===== Web登录 =====");
|
||||
sender.sendMessage("§e请点击链接登录Web端:");
|
||||
sender.sendMessage("§b" + webBaseUrl + "/login.php?token=" + tokenFinal);
|
||||
sender.sendMessage("§7有效期: " + tokenExpireSeconds + "秒 | 一次性使用");
|
||||
} else {
|
||||
sender.sendMessage("§c[Web] Token同步失败,请检查Web后端是否可访问");
|
||||
sender.sendMessage("§7当前后端地址: " + webBaseUrl);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user