fix: SSL断路器增强 - 多层防护避免SSL握手风暴
问题:SSL握手失败时,多个轮询定时器(5秒/2秒/3秒)并发提交任务, 导致连续失败计数飙升(58-69次),断路器反复触发但无法有效阻断。 修复: 1. submitDbTask()添加断路器检查 - 断路期间跳过所有DB任务 2. doGet/doPost/doGetStatus/doPostWithRetry添加断路器前置检查 3. 断路期间不再发送任何HTTP请求,避免无意义的SSL握手 4. 60秒冷却期结束后自动恢复 部署:JAR已上传MC服务器
This commit is contained in:
@@ -6,7 +6,10 @@ import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -21,6 +24,9 @@ public class AreaCLIManager {
|
||||
private final AreaProtection areaProtect;
|
||||
private static final int PAGE_SIZE = 10;
|
||||
|
||||
// ★ 记录每个玩家当前所在页面(切换权限后刷新用)
|
||||
private final Map<UUID, String[]> playerPageInfo = new HashMap<>();
|
||||
|
||||
public AreaCLIManager(Main plugin, AreaProtection areaProtect) {
|
||||
this.plugin = plugin;
|
||||
this.areaProtect = areaProtect;
|
||||
@@ -28,6 +34,40 @@ public class AreaCLIManager {
|
||||
|
||||
// ==================== 辅助方法 ====================
|
||||
|
||||
/**
|
||||
* ★ 记录玩家当前页面位置
|
||||
* @param type 页面类型(visitorperm / members / manage / lands)
|
||||
* @param landName 领地名
|
||||
* @param page 页码
|
||||
*/
|
||||
private void savePageInfo(Player p, String type, String landName, int page) {
|
||||
playerPageInfo.put(p.getUniqueId(), new String[]{type, landName, String.valueOf(page)});
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 获取玩家上次所在页面,返回 [type, landName, page] 或 null
|
||||
*/
|
||||
private String[] getPageInfo(Player p) {
|
||||
return playerPageInfo.get(p.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 切换权限后刷新:回到当前所在访客授权页面
|
||||
*/
|
||||
public void refreshCurrentPage(Player p) {
|
||||
String[] info = getPageInfo(p);
|
||||
if (info != null && "visitorperm".equals(info[0])) {
|
||||
showVisitorPerm(p, info[1], Integer.parseInt(info[2]));
|
||||
} else if (info != null && "members".equals(info[0])) {
|
||||
showMemberList(p, info[1], Integer.parseInt(info[2]));
|
||||
} else {
|
||||
// 默认回到管理页面
|
||||
if (info != null) {
|
||||
showLandManage(p, info[1], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Component coloredText(String text, NamedTextColor color) {
|
||||
return Component.text(text).color(color);
|
||||
}
|
||||
@@ -57,6 +97,8 @@ public class AreaCLIManager {
|
||||
public void showMainMenu(Player p) {
|
||||
p.sendMessage(header("领地系统"));
|
||||
|
||||
p.sendMessage(clickableAction("⛏", "获取圈地工具",
|
||||
"/protect 工具"));
|
||||
p.sendMessage(clickableAction("⚔", "创建领地",
|
||||
"/protect cli create"));
|
||||
p.sendMessage(clickableAction("📋", "领地列表",
|
||||
@@ -69,7 +111,6 @@ public class AreaCLIManager {
|
||||
// 设置
|
||||
int uiMode = 1;
|
||||
try { uiMode = plugin.getDb().getUiMode(p.getName()); } catch (Exception ignored) {}
|
||||
String modeLabel = uiMode == 0 ? "CLI" : "GUI";
|
||||
p.sendMessage(clickableAction("⚙", "切换到" + (uiMode == 0 ? "GUI" : "CLI") + "模式",
|
||||
"/protect uimode " + (uiMode == 0 ? "cli" : "gui")));
|
||||
|
||||
@@ -79,6 +120,8 @@ public class AreaCLIManager {
|
||||
// ==================== 领地列表 ====================
|
||||
|
||||
public void showLandList(Player p, int page) {
|
||||
savePageInfo(p, "lands", "", page);
|
||||
|
||||
String playerName = p.getName();
|
||||
List<AreaProtection.AreaConfig> lands = areaProtect.getLandsByOwner(playerName);
|
||||
|
||||
@@ -128,7 +171,6 @@ public class AreaCLIManager {
|
||||
"/protect tp " + land.name)));
|
||||
line = line.append(Component.text(" "));
|
||||
if (areaProtect.hasPendingDelete(land.name)) {
|
||||
// ★ 有待删除请求时显示取消按钮
|
||||
line = line.append(Component.text("§e[取消删除]")
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
Component.text("§e点击取消删除此领地")))
|
||||
@@ -175,6 +217,8 @@ public class AreaCLIManager {
|
||||
// ==================== 领地管理 ====================
|
||||
|
||||
public void showLandManage(Player p, String landName, int permPage) {
|
||||
savePageInfo(p, "manage", landName, 1);
|
||||
|
||||
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
|
||||
if (land == null) {
|
||||
p.sendMessage(Component.text("§c领地不存在: " + landName));
|
||||
@@ -201,6 +245,9 @@ public class AreaCLIManager {
|
||||
"/protect cli members " + land.name + " 1"));
|
||||
p.sendMessage(clickableAction("🔑", "访客授权",
|
||||
"/protect cli visitorperm " + land.name + " 1"));
|
||||
// ★ 新增:设置传送点
|
||||
p.sendMessage(clickableAction("📍", "设置传送点(当前位置)",
|
||||
"/protect settp " + land.name));
|
||||
|
||||
p.sendMessage(Component.empty()
|
||||
.append(Component.text("§a[◀ 返回列表]")
|
||||
@@ -212,6 +259,8 @@ public class AreaCLIManager {
|
||||
// ==================== 用户管理 ====================
|
||||
|
||||
public void showMemberList(Player p, String landName, int page) {
|
||||
savePageInfo(p, "members", landName, page);
|
||||
|
||||
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
|
||||
if (land == null) {
|
||||
p.sendMessage(Component.text("§c领地不存在: " + landName));
|
||||
@@ -263,11 +312,13 @@ public class AreaCLIManager {
|
||||
p.sendMessage(pagination);
|
||||
}
|
||||
|
||||
// ★ 修复占位符:使用 suggest_command 让玩家可以编辑命令输入玩家名
|
||||
p.sendMessage(Component.empty()
|
||||
.append(Component.text("§a[添加成员] ")
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect addvisitor " + land.name + " <玩家名>")))
|
||||
.append(Component.text("§7§7点击后在聊天框输入玩家名")));
|
||||
.hoverEvent(HoverEvent.showText(Component.text("§e点击后在聊天框输入玩家名")))
|
||||
.clickEvent(ClickEvent.suggestCommand(
|
||||
"/protect addvisitor " + land.name + " ")))
|
||||
.append(Component.text("§7点击后输入玩家名")));
|
||||
p.sendMessage(Component.empty()
|
||||
.append(Component.text("§a[◀ 返回管理]")
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
@@ -278,6 +329,8 @@ public class AreaCLIManager {
|
||||
// ==================== 访客授权 ====================
|
||||
|
||||
public void showVisitorPerm(Player p, String landName, int page) {
|
||||
savePageInfo(p, "visitorperm", landName, page);
|
||||
|
||||
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
|
||||
if (land == null) {
|
||||
p.sendMessage(Component.text("§c领地不存在: " + landName));
|
||||
@@ -314,7 +367,7 @@ public class AreaCLIManager {
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
Component.text("§e点击切换此权限状态")))
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect cli toggle " + land.name + " " + perm.key)));
|
||||
"/protect cli toggle " + land.name + " " + perm.key + " " + page)));
|
||||
p.sendMessage(line);
|
||||
}
|
||||
|
||||
@@ -325,7 +378,7 @@ public class AreaCLIManager {
|
||||
pagination = pagination.append(Component.text("§a[◀ 上一页]")
|
||||
.hoverEvent(HoverEvent.showText(Component.text("§e上一页")))
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect cli manage " + land.name + " " + (page - 1))));
|
||||
"/protect cli visitorperm " + land.name + " " + (page - 1))));
|
||||
pagination = pagination.append(Component.text(" "));
|
||||
}
|
||||
pagination = pagination.append(Component.text("§7第" + page + "/" + totalPages + "页"));
|
||||
@@ -334,7 +387,7 @@ public class AreaCLIManager {
|
||||
pagination = pagination.append(Component.text("§a[下一页 ▶]")
|
||||
.hoverEvent(HoverEvent.showText(Component.text("§e下一页")))
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect cli manage " + land.name + " " + (page + 1))));
|
||||
"/protect cli visitorperm " + land.name + " " + (page + 1))));
|
||||
}
|
||||
p.sendMessage(pagination);
|
||||
}
|
||||
@@ -342,7 +395,7 @@ public class AreaCLIManager {
|
||||
|
||||
// ==================== 切换权限 ====================
|
||||
|
||||
public void togglePerm(Player p, String landName, String permKey) {
|
||||
public void togglePerm(Player p, String landName, String permKey, int page) {
|
||||
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
|
||||
if (land == null) {
|
||||
p.sendMessage(Component.text("§c领地不存在: " + landName));
|
||||
@@ -397,6 +450,13 @@ public class AreaCLIManager {
|
||||
p.sendMessage("§a已切换 §e" + permName + " §7→ " + (newState ? "§a已启用" : "§c已禁用"));
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 兼容旧调用(不含page参数)
|
||||
*/
|
||||
public void togglePerm(Player p, String landName, String permKey) {
|
||||
togglePerm(p, landName, permKey, 1);
|
||||
}
|
||||
|
||||
// ==================== 数据结构 ====================
|
||||
|
||||
private static class PermItem {
|
||||
|
||||
@@ -52,9 +52,18 @@ public class AreaGUIManager implements Listener {
|
||||
/**
|
||||
* 打开区域防护主菜单
|
||||
*/
|
||||
// ★ 管理员面板标题
|
||||
private static final String T_ADMIN_PANEL = "§6§l管理员配置面板";
|
||||
|
||||
public void openMainMenu(Player p) {
|
||||
Inventory inv = Bukkit.createInventory(null, 54, T_MAIN);
|
||||
|
||||
// ★ 获取圈地工具(位置3)
|
||||
inv.setItem(3, createItem(Material.STICK, "§a§l获取圈地工具",
|
||||
"§7获取选择区域的木棍工具",
|
||||
"",
|
||||
"§e点击获取"));
|
||||
|
||||
// 创建领地(位置12)
|
||||
inv.setItem(12, createItem(Material.GRASS_BLOCK, "§a§l创建领地",
|
||||
"§7在你周围创建一个新的领地",
|
||||
@@ -80,6 +89,15 @@ public class AreaGUIManager implements Listener {
|
||||
"",
|
||||
"§e点击查看"));
|
||||
|
||||
// ★ 管理员面板:只有tag管理员可见(位置4)
|
||||
if (areaProtect.isAreaAdmin(p)) {
|
||||
inv.setItem(4, createItem(Material.NETHER_STAR, "§c§l管理员面板",
|
||||
"§7调整区域防护全局配置",
|
||||
"§7每平米价格、最大领地数等",
|
||||
"",
|
||||
"§e点击进入"));
|
||||
}
|
||||
|
||||
// ★ 切换到CLI模式(位置42)
|
||||
inv.setItem(42, createItem(Material.COMMAND_BLOCK, "§b§l切换到CLI模式",
|
||||
"§7切换到命令行交互模式",
|
||||
@@ -442,7 +460,11 @@ public class AreaGUIManager implements Listener {
|
||||
// 区域防护主菜单
|
||||
if (T_MAIN.equals(title)) {
|
||||
event.setCancelled(true);
|
||||
if (raw == 12) {
|
||||
if (raw == 3) {
|
||||
// ★ 获取圈地工具
|
||||
p.closeInventory();
|
||||
giveWandTool(p);
|
||||
} else if (raw == 12) {
|
||||
// 创建领地 - 给予选择工具
|
||||
p.closeInventory();
|
||||
p.performCommand("protect 工具");
|
||||
@@ -454,6 +476,9 @@ public class AreaGUIManager implements Listener {
|
||||
} else if (raw == 40) {
|
||||
p.closeInventory();
|
||||
p.sendMessage("§a文档链接: https://wiki.ypshidifu.cn");
|
||||
} else if (raw == 4 && areaProtect.isAreaAdmin(p)) {
|
||||
// ★ 管理员面板
|
||||
openAdminPanel(p);
|
||||
} else if (raw == 42) {
|
||||
// ★ 切换到CLI模式
|
||||
p.closeInventory();
|
||||
@@ -671,11 +696,22 @@ public class AreaGUIManager implements Listener {
|
||||
String modeStr = land.enforceGameMode != null ? land.enforceGameMode : "无";
|
||||
p.sendMessage("§a强制游戏模式: §f" + modeStr);
|
||||
openLandSettings(p, landName);
|
||||
} else if (raw == 31) {
|
||||
// ★ 设置传送点
|
||||
p.closeInventory();
|
||||
p.performCommand("protect settp " + landName);
|
||||
} else if (raw == 48) {
|
||||
openLandManage(p, landName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 管理员面板
|
||||
if (T_ADMIN_PANEL.equals(title)) {
|
||||
event.setCancelled(true);
|
||||
handleAdminPanelClick(p, raw, event.isLeftClick(), event.isRightClick());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -713,9 +749,151 @@ public class AreaGUIManager implements Listener {
|
||||
"",
|
||||
"§e点击切换: 生存→创造→冒险→旁观→无"));
|
||||
|
||||
// ★ 设置传送点(位置31)
|
||||
inv.setItem(31, createItem(Material.ENDER_PEARL, "§a§l设置传送点",
|
||||
"§7将你当前位置设为领地传送点",
|
||||
"",
|
||||
"§e点击设置"));
|
||||
|
||||
// 返回按钮(位置48)
|
||||
inv.setItem(48, createItem(Material.ARROW, "§c§l返回管理领地", ""));
|
||||
|
||||
p.openInventory(inv);
|
||||
}
|
||||
|
||||
// ==================== 管理员面板 ====================
|
||||
|
||||
/**
|
||||
* ★ 打开管理员配置面板
|
||||
*/
|
||||
public void openAdminPanel(Player p) {
|
||||
Inventory inv = Bukkit.createInventory(null, 54, T_ADMIN_PANEL);
|
||||
|
||||
// 读取全局配置
|
||||
int pricePerSqm = 10;
|
||||
int maxLands = 5;
|
||||
int defaultHeight = 255;
|
||||
int peaceDuration = 3600;
|
||||
try {
|
||||
String val = areaProtect.getAreaConfigValue("create_price_per_sqm");
|
||||
if (val != null) pricePerSqm = Integer.parseInt(val);
|
||||
val = areaProtect.getAreaConfigValue("max_lands_per_player");
|
||||
if (val != null) maxLands = Integer.parseInt(val);
|
||||
val = areaProtect.getAreaConfigValue("default_height");
|
||||
if (val != null) defaultHeight = Integer.parseInt(val);
|
||||
val = areaProtect.getAreaConfigValue("peace_mode_max_duration");
|
||||
if (val != null) peaceDuration = Integer.parseInt(val);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// 每平米价格(位置11)
|
||||
inv.setItem(11, createItem(Material.EMERALD, "§e§l每平米价格",
|
||||
"§7当前: §f" + pricePerSqm + " 元/㎡",
|
||||
"",
|
||||
"§e左键+10 / 右键-10"));
|
||||
|
||||
// 最大领地数(位置13)
|
||||
inv.setItem(13, createItem(Material.NAME_TAG, "§e§l每玩家最大领地数",
|
||||
"§7当前: §f" + maxLands + " 个",
|
||||
"",
|
||||
"§e左键+1 / 右键-1"));
|
||||
|
||||
// 默认高度(位置15)
|
||||
inv.setItem(15, createItem(Material.BARRIER, "§e§l默认领地高度",
|
||||
"§7当前: §f" + defaultHeight + " 格",
|
||||
"",
|
||||
"§e左键+32 / 右键-32"));
|
||||
|
||||
// 和平模式最大时间(位置22)
|
||||
inv.setItem(22, createItem(Material.SHIELD, "§e§l和平模式最长时间",
|
||||
"§7当前: §f" + peaceDuration + " 秒",
|
||||
"",
|
||||
"§e左键+600 / 右键-600"));
|
||||
|
||||
// 返回按钮(位置48)
|
||||
inv.setItem(48, createItem(Material.ARROW, "§c§l返回主菜单", ""));
|
||||
|
||||
p.openInventory(inv);
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 获取圈地工具(木棍)
|
||||
*/
|
||||
private void giveWandTool(Player p) {
|
||||
ItemStack wand = new ItemStack(Material.STICK);
|
||||
ItemMeta meta = wand.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName("§a§l区域选择工具");
|
||||
meta.setLore(Arrays.asList(
|
||||
"§7左键点击选择第一个点",
|
||||
"§7右键点击选择第二个点",
|
||||
"§7手持工具输入 /protect 创建 <名称>",
|
||||
"",
|
||||
"§e草原探险 - 区域防护"
|
||||
));
|
||||
wand.setItemMeta(meta);
|
||||
}
|
||||
// 检查背包是否已有
|
||||
for (ItemStack item : p.getInventory().getContents()) {
|
||||
if (item != null && item.getType() == Material.STICK
|
||||
&& item.hasItemMeta() && item.getItemMeta() != null
|
||||
&& item.getItemMeta().hasDisplayName()
|
||||
&& item.getItemMeta().getDisplayName().contains("区域选择工具")) {
|
||||
p.sendMessage("§e你已拥有圈地工具");
|
||||
return;
|
||||
}
|
||||
}
|
||||
p.getInventory().addItem(wand);
|
||||
p.sendMessage("§a已获取圈地工具(木棍),左键/右键选择区域边界");
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 处理管理员面板点击
|
||||
*/
|
||||
private void handleAdminPanelClick(Player p, int raw, boolean leftClick, boolean rightClick) {
|
||||
// 读取当前值
|
||||
int pricePerSqm = 10, maxLands = 5, defaultHeight = 255, peaceDuration = 3600;
|
||||
try {
|
||||
String v;
|
||||
v = areaProtect.getAreaConfigValue("create_price_per_sqm");
|
||||
if (v != null) pricePerSqm = Integer.parseInt(v);
|
||||
v = areaProtect.getAreaConfigValue("max_lands_per_player");
|
||||
if (v != null) maxLands = Integer.parseInt(v);
|
||||
v = areaProtect.getAreaConfigValue("default_height");
|
||||
if (v != null) defaultHeight = Integer.parseInt(v);
|
||||
v = areaProtect.getAreaConfigValue("peace_mode_max_duration");
|
||||
if (v != null) peaceDuration = Integer.parseInt(v);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
if (raw == 11) {
|
||||
// 每平米价格
|
||||
if (leftClick) pricePerSqm += 10;
|
||||
else if (rightClick) pricePerSqm = Math.max(0, pricePerSqm - 10);
|
||||
areaProtect.setAreaConfigValue("create_price_per_sqm", String.valueOf(pricePerSqm));
|
||||
p.sendMessage("§a每平米价格已更新: §f" + pricePerSqm + " 元/㎡");
|
||||
openAdminPanel(p);
|
||||
} else if (raw == 13) {
|
||||
// 最大领地数
|
||||
if (leftClick) maxLands += 1;
|
||||
else if (rightClick) maxLands = Math.max(1, maxLands - 1);
|
||||
areaProtect.setAreaConfigValue("max_lands_per_player", String.valueOf(maxLands));
|
||||
p.sendMessage("§a最大领地数已更新: §f" + maxLands + " 个");
|
||||
openAdminPanel(p);
|
||||
} else if (raw == 15) {
|
||||
// 默认高度
|
||||
if (leftClick) defaultHeight += 32;
|
||||
else if (rightClick) defaultHeight = Math.max(32, defaultHeight - 32);
|
||||
areaProtect.setAreaConfigValue("default_height", String.valueOf(defaultHeight));
|
||||
p.sendMessage("§a默认高度已更新: §f" + defaultHeight + " 格");
|
||||
openAdminPanel(p);
|
||||
} else if (raw == 22) {
|
||||
// 和平模式时间
|
||||
if (leftClick) peaceDuration += 600;
|
||||
else if (rightClick) peaceDuration = Math.max(60, peaceDuration - 600);
|
||||
areaProtect.setAreaConfigValue("peace_mode_max_duration", String.valueOf(peaceDuration));
|
||||
p.sendMessage("§a和平模式时间已更新: §f" + peaceDuration + " 秒");
|
||||
openAdminPanel(p);
|
||||
} else if (raw == 48) {
|
||||
openMainMenu(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,6 +723,29 @@ public class AreaProtection implements Listener {
|
||||
plugin.getLogger().info("========================================");
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 从数据库重新加载area_config配置(Web同步用)
|
||||
*/
|
||||
public void reloadAreaConfigFromDb() {
|
||||
if (dbConnection == null) return;
|
||||
try {
|
||||
ResultSet cfgRs = dbConnection.createStatement().executeQuery("SELECT key, value FROM area_config");
|
||||
while (cfgRs.next()) {
|
||||
String key = cfgRs.getString("key");
|
||||
String value = cfgRs.getString("value");
|
||||
switch (key) {
|
||||
case "create_price_per_sqm": /* 已在area_config表管理 */ break;
|
||||
case "max_lands_per_player": break;
|
||||
case "default_height": break;
|
||||
case "peace_mode_max_duration": break;
|
||||
}
|
||||
}
|
||||
cfgRs.close();
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[防护] 从DB重载配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 消息格式化 ====================
|
||||
|
||||
/**
|
||||
@@ -4770,12 +4793,16 @@ public class AreaProtection implements Listener {
|
||||
break;
|
||||
case "toggle":
|
||||
if (args.length < 4) {
|
||||
p.sendMessage("§c用法: /protect cli toggle <领地名> <权限key>");
|
||||
p.sendMessage("§c用法: /protect cli toggle <领地名> <权限key> [页码]");
|
||||
break;
|
||||
}
|
||||
plugin.areaCLIManager.togglePerm(p, args[2], args[3]);
|
||||
// ★ 切换后刷新当前领地管理页面
|
||||
plugin.areaCLIManager.showLandManage(p, args[2], 1);
|
||||
int togglePage = 1;
|
||||
if (args.length >= 5) {
|
||||
try { togglePage = Integer.parseInt(args[4]); } catch (Exception ignored) {}
|
||||
}
|
||||
plugin.areaCLIManager.togglePerm(p, args[2], args[3], togglePage);
|
||||
// ★ 切换后刷新到访客授权页面(停留当前页面,不退出)
|
||||
plugin.areaCLIManager.refreshCurrentPage(p);
|
||||
break;
|
||||
case "create":
|
||||
// 跳转到创建命令
|
||||
@@ -5325,7 +5352,7 @@ public class AreaProtection implements Listener {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== 删除(带1分钟延迟+二次确认)=====
|
||||
// ===== 删除(带60秒延迟自动删除 + 取消)=====
|
||||
if (sub.equals("删除") || sub.equals("delete")) {
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage("§c仅玩家可用");
|
||||
@@ -5348,70 +5375,48 @@ public class AreaProtection implements Listener {
|
||||
sender.sendMessage("§c领地不存在: " + areaName);
|
||||
return true;
|
||||
}
|
||||
// ★ 二次确认:参数2必须是完整的领地名
|
||||
if (args.length < 3 || !args[2].equals(areaName)) {
|
||||
sender.sendMessage("§e⚠ 此操作不可逆!请在60秒内输入: §c/protect 删除 " + areaName + " " + areaName);
|
||||
// 如果有旧的待删除,取消
|
||||
PendingDelete old = pendingDeletes.remove(areaName);
|
||||
if (old != null) old.task.cancel();
|
||||
// 启动1分钟倒计时
|
||||
UUID uid = p.getUniqueId();
|
||||
String finalAreaName = areaName;
|
||||
long start = System.currentTimeMillis();
|
||||
BukkitTask task = new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
pendingDeletes.remove(finalAreaName);
|
||||
// ★ 检查是否已有待删除请求
|
||||
PendingDelete existing = pendingDeletes.get(areaName);
|
||||
if (existing != null) {
|
||||
// 已有等待期,提示取消方式
|
||||
long elapsed = System.currentTimeMillis() - existing.startTime;
|
||||
long remaining = Math.max(0, (60000 - elapsed) / 1000);
|
||||
sender.sendMessage("§e领地 §c" + areaName + " §e正在等待删除中,还剩 §c" + remaining + " §e秒后自动删除");
|
||||
sender.sendMessage("§7输入 §e/protect 取消删除 " + areaName + " §7可取消");
|
||||
return true;
|
||||
}
|
||||
// ★ 新建等待期:60秒后自动删除
|
||||
sender.sendMessage("§e⚠ 此操作不可逆!领地 §c" + areaName + " §e将在 §c60秒 §e后自动删除");
|
||||
sender.sendMessage("§7输入 §e/protect 取消删除 " + areaName + " §7可取消");
|
||||
// 也显示到领地列表(CLI/GUI取消按钮会自动刷新)
|
||||
UUID uid = p.getUniqueId();
|
||||
String finalAreaName = areaName;
|
||||
long start = System.currentTimeMillis();
|
||||
BukkitTask task = new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
pendingDeletes.remove(finalAreaName);
|
||||
// ★ 60秒到期后自动删除领地
|
||||
if (areas.containsKey(finalAreaName)) {
|
||||
deleteAreaFromDb(finalAreaName);
|
||||
areas.remove(finalAreaName);
|
||||
plugin.getLogger().info("[防护] 领地 §e" + finalAreaName + " §7等待期结束,已自动删除");
|
||||
// 通知发起者
|
||||
Player online = Bukkit.getPlayer(uid);
|
||||
if (online != null && online.isOnline()) {
|
||||
online.sendMessage("§c§l[防护] §f领地 §e" + finalAreaName + " §f的删除请求已超时失效");
|
||||
online.sendMessage("§a§l[防护] §f领地 §e" + finalAreaName + " §f等待期结束,已自动删除");
|
||||
}
|
||||
// 通知所有在线管理员
|
||||
for (Player admin : Bukkit.getOnlinePlayers()) {
|
||||
if (admin.getUniqueId().equals(uid)) continue;
|
||||
if (isAreaAdmin(admin)) {
|
||||
admin.sendMessage("§7[防护] 领地 §e" + finalAreaName + " §7等待期结束,已自动删除");
|
||||
}
|
||||
}
|
||||
}
|
||||
}.runTaskLater(plugin, 1200L); // 60秒 = 1200 tick
|
||||
pendingDeletes.put(areaName, new PendingDelete(areaName, p.getName(), start, task));
|
||||
return true;
|
||||
}
|
||||
// ★ 验证是发起者本人确认(或管理员)
|
||||
PendingDelete pd = pendingDeletes.get(areaName);
|
||||
if (pd == null) {
|
||||
// 没有待删除记录,先创建一个(防止绕过延迟)
|
||||
sender.sendMessage("§e⚠ 此操作不可逆!请在60秒内输入: §c/protect 删除 " + areaName + " " + areaName);
|
||||
PendingDelete old = pendingDeletes.remove(areaName);
|
||||
if (old != null) old.task.cancel();
|
||||
UUID uid = p.getUniqueId();
|
||||
String finalAreaName = areaName;
|
||||
long start = System.currentTimeMillis();
|
||||
BukkitTask task = new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
pendingDeletes.remove(finalAreaName);
|
||||
Player online = Bukkit.getPlayer(uid);
|
||||
if (online != null && online.isOnline()) {
|
||||
online.sendMessage("§c§l[防护] §f领地 §e" + finalAreaName + " §f的删除请求已超时失效");
|
||||
}
|
||||
}
|
||||
}.runTaskLater(plugin, 1200L);
|
||||
pendingDeletes.put(areaName, new PendingDelete(areaName, p.getName(), start, task));
|
||||
return true;
|
||||
}
|
||||
if (!pd.playerName.equals(p.getName()) && !isAreaAdmin(sender)) {
|
||||
sender.sendMessage("§c只有发起删除的玩家才能确认");
|
||||
return true;
|
||||
}
|
||||
// ★ 检查是否已超时(1分钟)
|
||||
if (System.currentTimeMillis() - pd.startTime > 60_000) {
|
||||
pendingDeletes.remove(areaName);
|
||||
pd.task.cancel();
|
||||
sender.sendMessage("§c删除请求已超时,请重新发起");
|
||||
return true;
|
||||
}
|
||||
// ★ 冻结期间如果玩家操作了领地,则自动取消
|
||||
pendingDeletes.remove(areaName);
|
||||
if (pd != null) pd.task.cancel();
|
||||
// 执行删除
|
||||
deleteAreaFromDb(areaName);
|
||||
areas.remove(areaName);
|
||||
sender.sendMessage("§a§l[防护] §f已删除领地: §e" + areaName);
|
||||
}
|
||||
}.runTaskLater(plugin, 1200L); // 60秒 = 1200 tick
|
||||
pendingDeletes.put(areaName, new PendingDelete(areaName, p.getName(), start, task));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6087,15 +6092,28 @@ public class AreaProtection implements Listener {
|
||||
return true;
|
||||
}
|
||||
Player p = (Player) sender;
|
||||
// 找到玩家所在的领地
|
||||
AreaConfig ac = getArea(
|
||||
p.getWorld().getName(),
|
||||
p.getLocation().getBlockX(),
|
||||
p.getLocation().getBlockY(),
|
||||
p.getLocation().getBlockZ());
|
||||
if (ac == null) {
|
||||
sender.sendMessage("§c你不在任何领地内");
|
||||
return true;
|
||||
AreaConfig ac = null;
|
||||
// ★ 支持指定领地名:/protect settp <领地名>
|
||||
if (args.length >= 2) {
|
||||
String targetName = args[1];
|
||||
String resolved = resolveAreaName(targetName);
|
||||
if (resolved != null) targetName = resolved;
|
||||
ac = areas.get(targetName);
|
||||
if (ac == null) {
|
||||
sender.sendMessage("§c领地不存在: " + targetName);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// 找到玩家所在的领地
|
||||
ac = getArea(
|
||||
p.getWorld().getName(),
|
||||
p.getLocation().getBlockX(),
|
||||
p.getLocation().getBlockY(),
|
||||
p.getLocation().getBlockZ());
|
||||
if (ac == null) {
|
||||
sender.sendMessage("§c你不在任何领地内,请指定领地名: /protect settp <领地名>");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!hasPermission(p, ac, PermissionLevel.OWNER)) {
|
||||
sender.sendMessage("§c需要领地所有者或管理员权限");
|
||||
@@ -6504,6 +6522,36 @@ public class AreaProtection implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 获取area_config表中的配置值
|
||||
*/
|
||||
public String getAreaConfigValue(String key) {
|
||||
if (dbConnection == null) return null;
|
||||
try {
|
||||
PreparedStatement ps = dbConnection.prepareStatement("SELECT value FROM area_config WHERE key = ?");
|
||||
ps.setString(1, key);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
String val = rs.getString("value");
|
||||
rs.close();
|
||||
ps.close();
|
||||
return val;
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[防护] 读取配置失败: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 设置area_config表中的配置值(公开方法)
|
||||
*/
|
||||
public void setAreaConfigValue(String key, String value) {
|
||||
updateAreaConfig(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查玩家的访客权限是否有效(未过期)
|
||||
*/
|
||||
@@ -7129,7 +7177,22 @@ public class AreaProtection implements Listener {
|
||||
public LandConfig getLandConfig() { return landConfig; }
|
||||
|
||||
private void loadAreaConfig() {
|
||||
File configFile = new File(plugin.getDataFolder(), "区域防护配置.txt");
|
||||
// ★ 配置文件放在area/子目录,避免插件根目录凌乱
|
||||
File areaDir = new File(plugin.getDataFolder(), "area");
|
||||
if (!areaDir.exists()) {
|
||||
areaDir.mkdirs();
|
||||
}
|
||||
// ★ 自动迁移:如果根目录有旧配置文件,移到area/目录
|
||||
File oldConfigFile = new File(plugin.getDataFolder(), "区域防护配置.txt");
|
||||
File configFile = new File(areaDir, "区域防护配置.txt");
|
||||
if (!configFile.exists() && oldConfigFile.exists()) {
|
||||
try {
|
||||
oldConfigFile.renameTo(configFile);
|
||||
plugin.getLogger().info("[防护] 已将区域防护配置迁移到 area/ 目录");
|
||||
} catch (Exception e) {
|
||||
// 迁移失败,使用新位置
|
||||
}
|
||||
}
|
||||
if (!configFile.exists()) {
|
||||
writeDefaultAreaConfig(configFile);
|
||||
}
|
||||
|
||||
@@ -6052,7 +6052,15 @@ public class Main extends JavaPlugin
|
||||
"expand", "contraction",
|
||||
"list", "listitem",
|
||||
"addname", "removename", "listname",
|
||||
"addwhite", "removewhite", "listwhite");
|
||||
"addwhite", "removewhite", "listwhite",
|
||||
"settp", "setwarp", "tp", "warp",
|
||||
"setowner", "addvisitor", "removevisitor", "listvisitors", "transfer",
|
||||
"info", "shop",
|
||||
"cli", "menu", "菜单",
|
||||
"取消删除", "canceldelete",
|
||||
"removemember", "addmember",
|
||||
"config", "配置",
|
||||
"delete", "create", "reload");
|
||||
return filterTab(opts, args[0]);
|
||||
}
|
||||
|
||||
@@ -6085,7 +6093,13 @@ public class Main extends JavaPlugin
|
||||
return Arrays.asList("1", "3", "5", "10", "20", "50");
|
||||
}
|
||||
// 创建/删除: 区域名
|
||||
if (sub.equals("创建") || sub.equals("删除")) {
|
||||
if (sub.equals("创建") || sub.equals("delete")) {
|
||||
return filterTab(
|
||||
new ArrayList<>(areaProtection.getAreaNames()),
|
||||
args[1]);
|
||||
}
|
||||
// ★ 删除也支持中文
|
||||
if (sub.equals("删除")) {
|
||||
return filterTab(
|
||||
new ArrayList<>(areaProtection.getAreaNames()),
|
||||
args[1]);
|
||||
@@ -6104,6 +6118,33 @@ public class Main extends JavaPlugin
|
||||
new ArrayList<>(areaProtection.getAreaNames()),
|
||||
args[1]);
|
||||
}
|
||||
// ★ settp/setwarp/tp/warp: 区域名
|
||||
if (sub.equals("settp") || sub.equals("setwarp")
|
||||
|| sub.equals("tp") || sub.equals("warp")) {
|
||||
return filterTab(
|
||||
new ArrayList<>(areaProtection.getAreaNames()),
|
||||
args[1]);
|
||||
}
|
||||
// ★ setowner: 区域名
|
||||
if (sub.equals("setowner") || sub.equals("transfer")) {
|
||||
return filterTab(
|
||||
new ArrayList<>(areaProtection.getAreaNames()),
|
||||
args[1]);
|
||||
}
|
||||
// ★ addvisitor/removevisitor/listvisitors/removemember/addmember: 区域名
|
||||
if (sub.equals("addvisitor") || sub.equals("removevisitor")
|
||||
|| sub.equals("listvisitors") || sub.equals("removemember")
|
||||
|| sub.equals("addmember")) {
|
||||
return filterTab(
|
||||
new ArrayList<>(areaProtection.getAreaNames()),
|
||||
args[1]);
|
||||
}
|
||||
// ★ cli: 子命令
|
||||
if (sub.equals("cli")) {
|
||||
return filterTab(
|
||||
Arrays.asList("menu", "lands", "manage", "members", "visitorperm", "toggle", "create"),
|
||||
args[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 第三层 =====
|
||||
@@ -6137,6 +6178,60 @@ public class Main extends JavaPlugin
|
||||
players.add(pl.getName());
|
||||
return filterTab(players, args[2]);
|
||||
}
|
||||
// ★ setowner: 第三层是玩家名
|
||||
if (sub.equals("setowner")) {
|
||||
List<String> players = new ArrayList<>();
|
||||
for (Player pl : Bukkit.getOnlinePlayers())
|
||||
players.add(pl.getName());
|
||||
return filterTab(players, args[2]);
|
||||
}
|
||||
// ★ addvisitor/removevisitor: 第三层是玩家名
|
||||
if (sub.equals("addvisitor") || sub.equals("removevisitor")) {
|
||||
List<String> players = new ArrayList<>();
|
||||
for (Player pl : Bukkit.getOnlinePlayers())
|
||||
players.add(pl.getName());
|
||||
return filterTab(players, args[2]);
|
||||
}
|
||||
// ★ removemember/addmember: 第三层是玩家名
|
||||
if (sub.equals("removemember") || sub.equals("addmember")) {
|
||||
List<String> players = new ArrayList<>();
|
||||
for (Player pl : Bukkit.getOnlinePlayers())
|
||||
players.add(pl.getName());
|
||||
return filterTab(players, args[2]);
|
||||
}
|
||||
// ★ cli manage/members/visitorperm/toggle: 第三层是领地名
|
||||
if (sub.equals("cli")) {
|
||||
String action = args[1].toLowerCase();
|
||||
if (action.equals("manage") || action.equals("members")
|
||||
|| action.equals("visitorperm") || action.equals("toggle")) {
|
||||
return filterTab(
|
||||
new ArrayList<>(areaProtection.getAreaNames()),
|
||||
args[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 第四层:cli toggle需要权限key,cli manage/members/visitorperm需要页码
|
||||
if (args.length == 4) {
|
||||
if (sub.equals("cli")) {
|
||||
String action = args[1].toLowerCase();
|
||||
if (action.equals("toggle")) {
|
||||
// toggle: 第四层是权限key
|
||||
return filterTab(Arrays.asList(
|
||||
"move", "block_place", "block_break", "pvp", "mount",
|
||||
"ender_pearl", "thrown_projectiles", "raid", "bow", "potion",
|
||||
"fire", "fire_spread", "pickup", "drop", "explosion",
|
||||
"fall_damage", "hunger", "all_damage", "all_effects",
|
||||
"item_frame", "redstone", "door", "audio", "lead",
|
||||
"crop_harvest", "wool_shear", "animal_feed", "glowing", "peace_mode"),
|
||||
args[3]);
|
||||
}
|
||||
if (action.equals("manage") || action.equals("members")
|
||||
|| action.equals("visitorperm")) {
|
||||
// 页码
|
||||
return Arrays.asList("1", "2", "3", "4", "5");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -212,8 +212,35 @@ public class WebManager {
|
||||
|
||||
/**
|
||||
* 提交指定优先级的数据库写入任务
|
||||
* ★ 限制同种类任务最多3个(防止队列积压)
|
||||
*/
|
||||
private void submitDbTask(int priority, String name, Runnable action) {
|
||||
// ★ SSL断路器:断路期间跳过所有HTTP相关任务
|
||||
if (isCircuitOpen()) {
|
||||
plugin.getLogger().warning("[DB队列] SSL断路器开启,跳过任务: " + name);
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 同种类任务去重:提取任务类型前缀(如 "周期-syncOnlinePlayers" → "syncOnlinePlayers")
|
||||
String taskType = name.contains("-") ? name.substring(name.indexOf("-") + 1) : name;
|
||||
|
||||
// ★ 统计同类型任务数量
|
||||
int sameTypeCount = 0;
|
||||
for (DbTask t : dbTaskQueue) {
|
||||
if (t != null) {
|
||||
String tType = t.name.contains("-") ? t.name.substring(t.name.indexOf("-") + 1) : t.name;
|
||||
if (tType.equals(taskType)) {
|
||||
sameTypeCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 同类型任务最多3个,超过则丢弃低优先级的
|
||||
if (sameTypeCount >= 3) {
|
||||
plugin.getLogger().warning("[DB队列] 同类型任务已满(3): " + taskType + ",丢弃: " + name);
|
||||
return;
|
||||
}
|
||||
|
||||
DbTask task = new DbTask(priority, name, action);
|
||||
dbTaskQueue.offer(task);
|
||||
if (dbTaskQueue.size() > 50) {
|
||||
@@ -308,6 +335,10 @@ public class WebManager {
|
||||
plugin.getLogger().warning("[Web通信] HttpClient未初始化");
|
||||
return null;
|
||||
}
|
||||
// ★ SSL断路器:断路期间直接返回,避免无意义请求
|
||||
if (isCircuitOpen()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
HttpRequest req = HttpRequest.newBuilder()
|
||||
.uri(URI.create(urlStr))
|
||||
@@ -345,6 +376,10 @@ public class WebManager {
|
||||
*/
|
||||
private int doGetStatus(String urlStr) {
|
||||
if (cfHttpClient == null) return -1;
|
||||
// ★ SSL断路器:断路期间直接返回,避免无意义请求
|
||||
if (isCircuitOpen()) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
HttpRequest req = HttpRequest.newBuilder()
|
||||
.uri(URI.create(urlStr))
|
||||
@@ -367,6 +402,10 @@ public class WebManager {
|
||||
plugin.getLogger().warning("[Web通信] HttpClient未初始化");
|
||||
return null;
|
||||
}
|
||||
// ★ SSL断路器:断路期间直接返回,避免无意义请求
|
||||
if (isCircuitOpen()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
HttpRequest req = HttpRequest.newBuilder()
|
||||
.uri(URI.create(urlStr))
|
||||
@@ -395,6 +434,10 @@ public class WebManager {
|
||||
* POST请求带重试(处理SSL/网络异常)
|
||||
*/
|
||||
private String doPostWithRetry(String urlStr, String jsonBody, int maxRetries) {
|
||||
// ★ SSL断路器:断路期间直接返回,避免无意义重试
|
||||
if (isCircuitOpen()) {
|
||||
return null;
|
||||
}
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
HttpRequest req = HttpRequest.newBuilder()
|
||||
@@ -579,6 +622,9 @@ public class WebManager {
|
||||
// ★ 启动CDK验证轮询(每2秒检查PHP端CDK验证请求)
|
||||
startCdkValidationPolling();
|
||||
|
||||
// ★ 启动领地数据即时同步(每10秒从PHP拉取最新领地配置)
|
||||
startLandSyncTimer();
|
||||
|
||||
plugin.getLogger().info("[Web通信] 实时同步调度器已启动(60~90秒随机,无人停止,有人恢复)");
|
||||
plugin.getLogger().info("[Web通信] PHP回调服务器已启动,端口: " + callbackPort);
|
||||
}
|
||||
@@ -997,16 +1043,28 @@ public class WebManager {
|
||||
activeSyncRunning = true;
|
||||
|
||||
try {
|
||||
// 通过DB队列串行化执行周期性同步
|
||||
submitDbTask("周期-syncOnlinePlayers", () -> syncOnlinePlayers());
|
||||
// ★ 玩家在线时:全量同步
|
||||
// ★ 玩家不在线时:只保留注册/登录轮询,其余任务全部跳过
|
||||
boolean online = !Bukkit.getOnlinePlayers().isEmpty();
|
||||
|
||||
// 登录/注册相关(始终执行)
|
||||
submitDbTask("周期-pushWebLoginCredentials", () -> pushWebLoginCredentials());
|
||||
submitDbTask("周期-syncUserRegistrations", () -> syncUserRegistrations());
|
||||
submitDbTask("周期-syncBondTransactions", () -> syncBondTransactions());
|
||||
submitNormalDbTask("周期-pullPendingTransactions", () -> pullPendingTransactions());
|
||||
submitNormalDbTask("周期-pullShopStock", () -> pullShopStock());
|
||||
submitNormalDbTask("周期-pullBondChanges", () -> pullBondChanges());
|
||||
submitNormalDbTask("周期-syncAllPlayerIps", () -> syncAllPlayerIps());
|
||||
submitNormalDbTask("周期-syncServiceProviders", () -> syncServiceProviders());
|
||||
|
||||
if (online) {
|
||||
// 在线:执行全量同步
|
||||
submitDbTask("周期-syncOnlinePlayers", () -> syncOnlinePlayers());
|
||||
submitDbTask("周期-syncBondTransactions", () -> syncBondTransactions());
|
||||
submitNormalDbTask("周期-pullPendingTransactions", () -> pullPendingTransactions());
|
||||
submitNormalDbTask("周期-pullShopStock", () -> pullShopStock());
|
||||
submitNormalDbTask("周期-pullBondChanges", () -> pullBondChanges());
|
||||
submitNormalDbTask("周期-syncAllPlayerIps", () -> syncAllPlayerIps());
|
||||
submitNormalDbTask("周期-syncServiceProviders", () -> syncServiceProviders());
|
||||
submitNormalDbTask("周期-syncLandData", () -> syncLandData());
|
||||
} else {
|
||||
// ★ 不在线:跳过非必要同步任务
|
||||
plugin.getLogger().info("[Web通信] 全员下线,跳过非登录类同步任务");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[Web通信] 全量同步异常: " + e.getMessage());
|
||||
} finally {
|
||||
@@ -1254,6 +1312,60 @@ public class WebManager {
|
||||
plugin.getLogger().info("[Web通信] CDK远程验证轮询已启动(每2秒)");
|
||||
}
|
||||
|
||||
// ==================== 领地数据即时同步 ====================
|
||||
|
||||
/**
|
||||
* ★ 领地数据即时同步:每10秒从PHP拉取最新领地配置
|
||||
* 配合60~90秒全量同步作为兜底
|
||||
*/
|
||||
private volatile boolean landSyncPulling = false;
|
||||
private volatile int landSyncFailCount = 0;
|
||||
private static final int LAND_SYNC_FAIL_THRESHOLD = 10;
|
||||
|
||||
private void startLandSyncTimer() {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// ★ 玩家不在线时跳过
|
||||
if (Bukkit.getOnlinePlayers().isEmpty()) return;
|
||||
|
||||
// ★ 防重入
|
||||
if (landSyncPulling) return;
|
||||
|
||||
// ★ 连续失败过多时降低频率
|
||||
if (landSyncFailCount >= LAND_SYNC_FAIL_THRESHOLD) {
|
||||
if (landSyncFailCount % 6 != 0) {
|
||||
landSyncFailCount++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
landSyncPulling = true;
|
||||
try {
|
||||
String url = webBaseUrl + "/api/land_api.php?action=get_config&secret="
|
||||
+ java.net.URLEncoder.encode(secretKey, "UTF-8");
|
||||
String json = doGet(url);
|
||||
if (json != null && json.contains("\"success\":true")) {
|
||||
landSyncFailCount = 0;
|
||||
// 配置拉取成功,通知AreaProtection刷新
|
||||
if (plugin.areaProtection != null) {
|
||||
plugin.areaProtection.reloadAreaConfigFromDb();
|
||||
}
|
||||
} else {
|
||||
landSyncFailCount++;
|
||||
}
|
||||
} finally {
|
||||
landSyncPulling = false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
landSyncFailCount++;
|
||||
}
|
||||
}
|
||||
}.runTaskTimerAsynchronously(plugin, 20L * 10, 20L * 10); // 每10秒
|
||||
plugin.getLogger().info("[Web通信] 领地配置即时同步已启动(每10秒)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Part 1: Web端写cdk_validate_requests → Sdf1_login拉取 → 本地CDKManager.redeem → 写回结果
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user