领地系统8项修复: SSL断路器/计费/GUI-CLI切换/删除取消/管理细分/配置释放/txt迁移
This commit is contained in:
@@ -127,11 +127,20 @@ public class AreaCLIManager {
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect tp " + land.name)));
|
||||
line = line.append(Component.text(" "));
|
||||
line = line.append(Component.text("§c[删除]")
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
Component.text("§c点击删除此领地")))
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect 删除 " + land.name)));
|
||||
if (areaProtect.hasPendingDelete(land.name)) {
|
||||
// ★ 有待删除请求时显示取消按钮
|
||||
line = line.append(Component.text("§e[取消删除]")
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
Component.text("§e点击取消删除此领地")))
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect 取消删除 " + land.name)));
|
||||
} else {
|
||||
line = line.append(Component.text("§c[删除]")
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
Component.text("§c点击删除此领地")))
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect 删除 " + land.name)));
|
||||
}
|
||||
}
|
||||
|
||||
p.sendMessage(line);
|
||||
@@ -187,10 +196,11 @@ public class AreaCLIManager {
|
||||
p.sendMessage(Component.text("§7规则数: §f" + land.ruleCount()));
|
||||
p.sendMessage(Component.text("§7§l───────────────────────────────"));
|
||||
|
||||
// 显示权限列表(分页)
|
||||
List<AreaProtection.AreaConfig> tempLandList = new ArrayList<>();
|
||||
tempLandList.add(land);
|
||||
showPermPage(p, land, permPage);
|
||||
// ★ 细分管理选项
|
||||
p.sendMessage(clickableAction("👤", "用户管理",
|
||||
"/protect cli members " + land.name + " 1"));
|
||||
p.sendMessage(clickableAction("🔑", "访客授权",
|
||||
"/protect cli visitorperm " + land.name + " 1"));
|
||||
|
||||
p.sendMessage(Component.empty()
|
||||
.append(Component.text("§a[◀ 返回列表]")
|
||||
@@ -199,6 +209,91 @@ public class AreaCLIManager {
|
||||
p.sendMessage(Component.text("§7§l───────────────────────────────"));
|
||||
}
|
||||
|
||||
// ==================== 用户管理 ====================
|
||||
|
||||
public void showMemberList(Player p, String landName, int page) {
|
||||
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
|
||||
if (land == null) {
|
||||
p.sendMessage(Component.text("§c领地不存在: " + landName));
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> members = areaProtect.getLandMembers(landName);
|
||||
List<String> memberList = new ArrayList<>(members);
|
||||
int totalPages = Math.max(1, (int) Math.ceil((double) memberList.size() / PAGE_SIZE));
|
||||
page = Math.max(1, Math.min(page, totalPages));
|
||||
|
||||
p.sendMessage(header("用户管理: " + landName));
|
||||
|
||||
if (memberList.isEmpty()) {
|
||||
p.sendMessage(Component.text("§7暂无成员"));
|
||||
} else {
|
||||
int start = (page - 1) * PAGE_SIZE;
|
||||
int end = Math.min(start + PAGE_SIZE, memberList.size());
|
||||
for (int i = start; i < end; i++) {
|
||||
String member = memberList.get(i);
|
||||
Component line = Component.empty();
|
||||
line = line.append(Component.text("§f" + member));
|
||||
line = line.append(Component.text(" "));
|
||||
line = line.append(Component.text("§c[移除]")
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
Component.text("§c点击移除此成员")))
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect removemember " + land.name + " " + member)));
|
||||
p.sendMessage(line);
|
||||
}
|
||||
}
|
||||
|
||||
// 分页
|
||||
if (totalPages > 1) {
|
||||
Component pagination = Component.empty();
|
||||
if (page > 1) {
|
||||
pagination = pagination.append(Component.text("§a[◀ 上一页]")
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect cli members " + land.name + " " + (page - 1))));
|
||||
pagination = pagination.append(Component.text(" "));
|
||||
}
|
||||
pagination = pagination.append(Component.text("§7第" + page + "/" + totalPages + "页"));
|
||||
if (page < totalPages) {
|
||||
pagination = pagination.append(Component.text(" "));
|
||||
pagination = pagination.append(Component.text("§a[下一页 ▶]")
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect cli members " + land.name + " " + (page + 1))));
|
||||
}
|
||||
p.sendMessage(pagination);
|
||||
}
|
||||
|
||||
p.sendMessage(Component.empty()
|
||||
.append(Component.text("§a[添加成员] ")
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect addvisitor " + land.name + " <玩家名>")))
|
||||
.append(Component.text("§7§7点击后在聊天框输入玩家名")));
|
||||
p.sendMessage(Component.empty()
|
||||
.append(Component.text("§a[◀ 返回管理]")
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect cli manage " + land.name + " 1"))));
|
||||
p.sendMessage(Component.text("§7§l───────────────────────────────"));
|
||||
}
|
||||
|
||||
// ==================== 访客授权 ====================
|
||||
|
||||
public void showVisitorPerm(Player p, String landName, int page) {
|
||||
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
|
||||
if (land == null) {
|
||||
p.sendMessage(Component.text("§c领地不存在: " + landName));
|
||||
return;
|
||||
}
|
||||
|
||||
p.sendMessage(header("访客授权: " + landName));
|
||||
showPermPage(p, land, page);
|
||||
|
||||
p.sendMessage(Component.empty()
|
||||
.append(Component.text("§a[◀ 返回管理]")
|
||||
.clickEvent(ClickEvent.runCommand(
|
||||
"/protect cli manage " + land.name + " 1"))));
|
||||
p.sendMessage(Component.text("§7§l───────────────────────────────"));
|
||||
}
|
||||
|
||||
private void showPermPage(Player p, AreaProtection.AreaConfig land, int page) {
|
||||
List<PermItem> perms = getPermList(land);
|
||||
int totalPages = Math.max(1, (int) Math.ceil((double) perms.size() / PAGE_SIZE));
|
||||
|
||||
@@ -80,6 +80,13 @@ public class AreaGUIManager implements Listener {
|
||||
"",
|
||||
"§e点击查看"));
|
||||
|
||||
// ★ 切换到CLI模式(位置42)
|
||||
inv.setItem(42, createItem(Material.COMMAND_BLOCK, "§b§l切换到CLI模式",
|
||||
"§7切换到命令行交互模式",
|
||||
"§7权限管理更便捷,支持批量操作",
|
||||
"",
|
||||
"§e点击切换"));
|
||||
|
||||
// 返回按钮(位置49)
|
||||
inv.setItem(49, createItem(Material.ARROW, "§c§l返回主菜单", ""));
|
||||
|
||||
@@ -447,6 +454,17 @@ public class AreaGUIManager implements Listener {
|
||||
} else if (raw == 40) {
|
||||
p.closeInventory();
|
||||
p.sendMessage("§a文档链接: https://wiki.ypshidifu.cn");
|
||||
} else if (raw == 42) {
|
||||
// ★ 切换到CLI模式
|
||||
p.closeInventory();
|
||||
try {
|
||||
plugin.getDb().setUiMode(p.getName(), 1);
|
||||
} catch (Exception ignored) {}
|
||||
if (plugin.areaCLIManager != null) {
|
||||
plugin.areaCLIManager.showMainMenu(p);
|
||||
} else {
|
||||
p.sendMessage("§cCLI管理器未初始化");
|
||||
}
|
||||
} else if (raw == 49) {
|
||||
plugin.getGui().openMain(p);
|
||||
}
|
||||
@@ -470,10 +488,9 @@ public class AreaGUIManager implements Listener {
|
||||
if (event.isLeftClick() && !event.isShiftClick()) {
|
||||
openLandManage(p, land.name);
|
||||
} else if (event.isRightClick()) {
|
||||
// 删除领地
|
||||
areaProtect.deleteLand(land.name);
|
||||
p.sendMessage("§a已删除领地: " + land.name);
|
||||
openLandList(p, page);
|
||||
// ★ 删除领地:走延迟确认流程
|
||||
p.closeInventory();
|
||||
p.performCommand("protect 删除 " + land.name);
|
||||
} else if (event.isShiftClick() && event.isLeftClick()) {
|
||||
// 传送
|
||||
p.closeInventory();
|
||||
|
||||
@@ -396,7 +396,6 @@ public class AreaProtection implements Listener {
|
||||
migrateTxtToDb(); // ★ 迁移txt文件到数据库
|
||||
loadAllAreas();
|
||||
loadWhitelists();
|
||||
loadAreaConfig();
|
||||
recoverPendingEffects();
|
||||
}
|
||||
public void recoverPendingEffects() {
|
||||
@@ -841,7 +840,12 @@ public class AreaProtection implements Listener {
|
||||
// ★ 优先从数据库加载
|
||||
loadAreasFromDb();
|
||||
|
||||
// ★ 兼容:如果没有从DB加载到数据,回退到txt(未迁移场景)
|
||||
// ★ 自动迁移:如果DB有数据但txt文件仍存在,迁移txt中的白名单数据到DB后删除txt
|
||||
if (!areas.isEmpty()) {
|
||||
migrateTxtFilesToDb();
|
||||
}
|
||||
|
||||
// ★ 兼容:如果没有从DB加载到数据,回退到txt(未迁移场景)并自动导入DB
|
||||
if (areas.isEmpty()) {
|
||||
File[] files = rootDir.listFiles(
|
||||
(File d, String n) -> n.endsWith(".txt"));
|
||||
@@ -851,14 +855,78 @@ public class AreaProtection implements Listener {
|
||||
String name = f.getName().replace(".txt", "");
|
||||
AreaConfig ac = parseArea(name, f);
|
||||
areas.put(name, ac);
|
||||
// ★ 自动写入数据库
|
||||
saveAreaToDb(ac);
|
||||
plugin.getLogger().info("[防护] 自动迁移txt→DB: " + name);
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[防护] 加载失败: " + f.getName());
|
||||
}
|
||||
}
|
||||
// ★ 迁移完成后删除所有txt文件
|
||||
if (!areas.isEmpty()) {
|
||||
for (File f : files) {
|
||||
if (f.delete()) {
|
||||
plugin.getLogger().info("[防护] 已删除迁移后的txt: " + f.getName());
|
||||
}
|
||||
}
|
||||
// 也删除白名单目录
|
||||
File wlDir = new File(rootDir, "whitelists");
|
||||
if (wlDir.exists() && wlDir.isDirectory()) {
|
||||
File[] wlFiles = wlDir.listFiles();
|
||||
if (wlFiles != null) {
|
||||
for (File wf : wlFiles) {
|
||||
wf.delete();
|
||||
}
|
||||
}
|
||||
wlDir.delete();
|
||||
}
|
||||
plugin.getLogger().info("[防护] txt迁移完成,共导入 " + areas.size() + " 个领地");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移txt文件到数据库(如果txt存在但DB已有数据,清理txt)
|
||||
*/
|
||||
private void migrateTxtFilesToDb() {
|
||||
File[] files = rootDir.listFiles(
|
||||
(File d, String n) -> n.endsWith(".txt"));
|
||||
if (files == null || files.length == 0) return;
|
||||
|
||||
plugin.getLogger().info("[防护] 检测到 " + files.length + " 个残留txt文件,开始清理...");
|
||||
for (File f : files) {
|
||||
String name = f.getName().replace(".txt", "");
|
||||
// 如果txt对应的领地不在DB中,先导入
|
||||
if (!areas.containsKey(name)) {
|
||||
try {
|
||||
AreaConfig ac = parseArea(name, f);
|
||||
areas.put(name, ac);
|
||||
saveAreaToDb(ac);
|
||||
plugin.getLogger().info("[防护] 迁移txt→DB: " + name);
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[防护] 迁移失败: " + f.getName());
|
||||
}
|
||||
}
|
||||
// 删除txt
|
||||
if (f.delete()) {
|
||||
plugin.getLogger().info("[防护] 已删除: " + f.getName());
|
||||
}
|
||||
}
|
||||
// 清理白名单目录
|
||||
File wlDir = new File(rootDir, "whitelists");
|
||||
if (wlDir.exists() && wlDir.isDirectory()) {
|
||||
File[] wlFiles = wlDir.listFiles();
|
||||
if (wlFiles != null) {
|
||||
for (File wf : wlFiles) {
|
||||
wf.delete();
|
||||
}
|
||||
}
|
||||
wlDir.delete();
|
||||
plugin.getLogger().info("[防护] 已清理白名单目录");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库加载所有领地配置
|
||||
*/
|
||||
@@ -3595,6 +3663,13 @@ public class AreaProtection implements Listener {
|
||||
return Collections.unmodifiableMap(areas);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查领地是否有待删除请求
|
||||
*/
|
||||
public boolean hasPendingDelete(String areaName) {
|
||||
return pendingDeletes.containsKey(areaName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定玩家拥有的所有领地
|
||||
*/
|
||||
@@ -4423,7 +4498,10 @@ public class AreaProtection implements Listener {
|
||||
"setowner", "addvisitor", "removevisitor", "listvisitors", "transfer",
|
||||
"info", "setowner", "addvisitor", "removevisitor",
|
||||
"listvisitors", "transfer", "shop",
|
||||
"menu", "菜单", "sdf1debug", "testclear", "cli"
|
||||
"menu", "菜单", "sdf1debug", "testclear", "cli",
|
||||
"取消删除", "canceldelete",
|
||||
"removemember", "addmember",
|
||||
"config", "配置"
|
||||
));
|
||||
|
||||
String first = original[0].toLowerCase();
|
||||
@@ -4617,11 +4695,17 @@ public class AreaProtection implements Listener {
|
||||
if (mode.equals("gui") || mode.equals("0")) {
|
||||
plugin.getDb().setUiMode(p.getName(), 0);
|
||||
p.sendMessage("§a已切换为 §6GUI §a模式");
|
||||
p.sendMessage("§7下次输入 /protect 将打开GUI菜单");
|
||||
// ★ 立即打开GUI
|
||||
if (plugin.areaGUIManager != null) {
|
||||
plugin.areaGUIManager.openMainMenu(p);
|
||||
}
|
||||
} else if (mode.equals("cli") || mode.equals("1")) {
|
||||
plugin.getDb().setUiMode(p.getName(), 1);
|
||||
p.sendMessage("§a已切换为 §6CLI §a模式");
|
||||
p.sendMessage("§7下次输入 /protect 将显示文本帮助");
|
||||
// ★ 立即显示CLI菜单
|
||||
if (plugin.areaCLIManager != null) {
|
||||
plugin.areaCLIManager.showMainMenu(p);
|
||||
}
|
||||
} else {
|
||||
p.sendMessage("§c用法: /protect uimode <gui|cli>");
|
||||
}
|
||||
@@ -4662,12 +4746,36 @@ public class AreaProtection implements Listener {
|
||||
}
|
||||
plugin.areaCLIManager.showLandManage(p, args[2], permPage);
|
||||
break;
|
||||
case "members":
|
||||
if (args.length < 3) {
|
||||
p.sendMessage("§c用法: /protect cli members <领地名> [页码]");
|
||||
break;
|
||||
}
|
||||
int memberPage = 1;
|
||||
if (args.length >= 4) {
|
||||
try { memberPage = Integer.parseInt(args[3]); } catch (Exception ignored) {}
|
||||
}
|
||||
plugin.areaCLIManager.showMemberList(p, args[2], memberPage);
|
||||
break;
|
||||
case "visitorperm":
|
||||
if (args.length < 3) {
|
||||
p.sendMessage("§c用法: /protect cli visitorperm <领地名> [页码]");
|
||||
break;
|
||||
}
|
||||
int vPermPage = 1;
|
||||
if (args.length >= 4) {
|
||||
try { vPermPage = Integer.parseInt(args[3]); } catch (Exception ignored) {}
|
||||
}
|
||||
plugin.areaCLIManager.showVisitorPerm(p, args[2], vPermPage);
|
||||
break;
|
||||
case "toggle":
|
||||
if (args.length < 4) {
|
||||
p.sendMessage("§c用法: /protect cli toggle <领地名> <权限key>");
|
||||
break;
|
||||
}
|
||||
plugin.areaCLIManager.togglePerm(p, args[2], args[3]);
|
||||
// ★ 切换后刷新当前领地管理页面
|
||||
plugin.areaCLIManager.showLandManage(p, args[2], 1);
|
||||
break;
|
||||
case "create":
|
||||
// 跳转到创建命令
|
||||
@@ -4704,9 +4812,38 @@ public class AreaProtection implements Listener {
|
||||
p.sendMessage("§c区域已存在");
|
||||
return true;
|
||||
}
|
||||
// ★ 检查每人领地数量上限
|
||||
long playerLandCount = areas.values().stream()
|
||||
.filter(a -> p.getName().equalsIgnoreCase(a.owner))
|
||||
.count();
|
||||
if (playerLandCount >= globalMaxLandsPerPlayer) {
|
||||
p.sendMessage("§c§l[防护] §f已达领地上限!每人最多 §e" + globalMaxLandsPerPlayer + " §f个领地");
|
||||
return true;
|
||||
}
|
||||
Location l1 = pos1.get(u);
|
||||
Location l2 = pos2.get(u);
|
||||
|
||||
// ★ 领地计费:计算面积并扣除余额
|
||||
int width = Math.abs(l2.getBlockX() - l1.getBlockX()) + 1;
|
||||
int length = Math.abs(l2.getBlockZ() - l1.getBlockZ()) + 1;
|
||||
int area = width * length;
|
||||
int cost = area * globalCreatePricePerSqm;
|
||||
if (cost > 0) {
|
||||
BondManager bm = plugin.getBonds();
|
||||
if (bm != null) {
|
||||
int balance = bm.getBonds(p.getName());
|
||||
if (balance < cost) {
|
||||
p.sendMessage("§c§l[防护] §f余额不足!需要 §e" + cost + " §f债券,当前余额 §e" + balance);
|
||||
return true;
|
||||
}
|
||||
if (!bm.deductBonds(p.getName(), cost, "land_create", p.getName(), p.getName(), "创建领地: " + areaName + " (" + area + "㎡×" + globalCreatePricePerSqm + ")")) {
|
||||
p.sendMessage("§c§l[防护] §f扣费失败,请稍后重试");
|
||||
return true;
|
||||
}
|
||||
p.sendMessage("§a§l[防护] §f创建领地扣除 §e" + cost + " §f债券(" + area + "㎡×" + globalCreatePricePerSqm + "/㎡)");
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 创建AreaConfig并保存到数据库
|
||||
AreaConfig ac = new AreaConfig();
|
||||
ac.name = areaName;
|
||||
@@ -4720,31 +4857,6 @@ public class AreaProtection implements Listener {
|
||||
ac.yMax = 255;
|
||||
saveAreaToDb(ac);
|
||||
|
||||
// 兼容:同时创建txt文件
|
||||
File f = new File(rootDir, areaName + ".txt");
|
||||
try {
|
||||
PrintWriter pw = new PrintWriter(
|
||||
new OutputStreamWriter(
|
||||
new FileOutputStream(f),
|
||||
StandardCharsets.UTF_8));
|
||||
pw.println("# 区域: " + areaName);
|
||||
pw.println("起点: " + l1.getWorld().getName()
|
||||
+ "," + l1.getBlockX()
|
||||
+ "," + l1.getBlockZ());
|
||||
pw.println("终点: " + l2.getWorld().getName()
|
||||
+ "," + l2.getBlockX()
|
||||
+ "," + l2.getBlockZ());
|
||||
pw.println("高度范围: 0-255");
|
||||
pw.println();
|
||||
pw.println("# 禁止放置方块");
|
||||
pw.println("# 禁止破坏方块");
|
||||
pw.println("# 禁止PVP");
|
||||
pw.println("# 效果: 夜视 1 999");
|
||||
pw.println("# 进入提示: 欢迎来到保护区");
|
||||
pw.println("# 离开提示: 已离开保护区");
|
||||
pw.close();
|
||||
} catch (IOException ignored) {}
|
||||
|
||||
p.sendMessage("§a§l[防护] §f区域 "
|
||||
+ areaName + " 已创建 (owner: " + p.getName() + ")");
|
||||
loadAllAreas();
|
||||
@@ -4938,6 +5050,53 @@ public class AreaProtection implements Listener {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== config 全局配置 =====
|
||||
if (sub.equals("config") || sub.equals("配置")) {
|
||||
if (!isAreaAdmin(sender)) {
|
||||
sender.sendMessage("§c需要管理员权限");
|
||||
return true;
|
||||
}
|
||||
if (args.length < 3) {
|
||||
// 查看配置
|
||||
sender.sendMessage("§e§l==== 全局配置 ====");
|
||||
sender.sendMessage("§a创建价格(每㎡): §f" + globalCreatePricePerSqm);
|
||||
sender.sendMessage("§a每人最大领地数: §f" + globalMaxLandsPerPlayer);
|
||||
sender.sendMessage("§7用法: /protect config <key> <value>");
|
||||
sender.sendMessage("§7可用key: create_price, max_lands, default_height, peace_duration");
|
||||
return true;
|
||||
}
|
||||
String key = args[1];
|
||||
String value = args[2];
|
||||
try {
|
||||
switch (key) {
|
||||
case "create_price":
|
||||
globalCreatePricePerSqm = Integer.parseInt(value);
|
||||
updateAreaConfig("create_price_per_sqm", value);
|
||||
sender.sendMessage("§a创建价格已更新为: " + value + "/㎡");
|
||||
break;
|
||||
case "max_lands":
|
||||
globalMaxLandsPerPlayer = Integer.parseInt(value);
|
||||
updateAreaConfig("max_lands_per_player", value);
|
||||
sender.sendMessage("§a每人最大领地数已更新为: " + value);
|
||||
break;
|
||||
case "default_height":
|
||||
updateAreaConfig("default_height", value);
|
||||
sender.sendMessage("§a默认高度已更新为: " + value);
|
||||
break;
|
||||
case "peace_duration":
|
||||
updateAreaConfig("peace_mode_max_duration", value);
|
||||
sender.sendMessage("§a和平模式最大时长已更新为: " + value + "秒");
|
||||
break;
|
||||
default:
|
||||
sender.sendMessage("§c未知配置项: " + key);
|
||||
break;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
sender.sendMessage("§c值必须是数字");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== 重载 =====
|
||||
if (sub.equals("重载") || sub.equals("reload")) {
|
||||
reload();
|
||||
@@ -5256,6 +5415,30 @@ public class AreaProtection implements Listener {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== 取消删除 =====
|
||||
if (sub.equals("取消删除") || sub.equals("canceldelete")) {
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage("§c仅玩家可用");
|
||||
return true;
|
||||
}
|
||||
Player p = (Player) sender;
|
||||
if (args.length < 2) {
|
||||
sender.sendMessage("§e用法: /protect 取消删除 <领地名>");
|
||||
return true;
|
||||
}
|
||||
String areaName = args[1];
|
||||
String resolved = resolveAreaName(areaName);
|
||||
if (resolved != null) areaName = resolved;
|
||||
PendingDelete pd = pendingDeletes.remove(areaName);
|
||||
if (pd == null) {
|
||||
sender.sendMessage("§c没有待删除的领地: " + areaName);
|
||||
return true;
|
||||
}
|
||||
pd.task.cancel();
|
||||
sender.sendMessage("§a§l[防护] §f已取消删除领地: §e" + areaName);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== add 加白名单 =====
|
||||
if (sub.equals("add")) {
|
||||
if (args.length == 2) {
|
||||
@@ -5799,8 +5982,8 @@ public class AreaProtection implements Listener {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== removevisitor 移除访客 =====
|
||||
if (sub.equals("removevisitor")) {
|
||||
// ===== removevisitor / removemember 移除访客/成员 =====
|
||||
if (sub.equals("removevisitor") || sub.equals("removemember")) {
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage("§c仅玩家可用");
|
||||
return true;
|
||||
@@ -6302,6 +6485,25 @@ public class AreaProtection implements Listener {
|
||||
return seconds + "秒";
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新area_config表中的配置值
|
||||
*/
|
||||
private void updateAreaConfig(String key, String value) {
|
||||
if (dbConnection == null) return;
|
||||
try {
|
||||
PreparedStatement ps = dbConnection.prepareStatement(
|
||||
"INSERT INTO area_config (key, value) VALUES (?, ?) "
|
||||
+ "ON CONFLICT(key) DO UPDATE SET value = ?");
|
||||
ps.setString(1, key);
|
||||
ps.setString(2, value);
|
||||
ps.setString(3, value);
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[防护] 更新配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查玩家的访客权限是否有效(未过期)
|
||||
*/
|
||||
@@ -6353,6 +6555,7 @@ public class AreaProtection implements Listener {
|
||||
sendClickableHelp(s, "/protect tempon", "临时边框(15秒)");
|
||||
sendClickableHelp(s, "/protect list", "列出白名单(需在领地内)");
|
||||
sendClickableHelp(s, "/protect listitem", "列出物品黑名单");
|
||||
sendClickableHelp(s, "/protect config", "查看/修改全局配置");
|
||||
|
||||
s.sendMessage("§e§l---- 权限管理 ----");
|
||||
sendClickableHelp(s, "/protect setowner", "设置当前领地所有者(管理员)");
|
||||
|
||||
@@ -101,6 +101,20 @@ public class WebManager {
|
||||
// 库存高频拉取去重标志
|
||||
private final AtomicBoolean stockFastPollTaskPending = new AtomicBoolean(false);
|
||||
|
||||
// ★ Web请求专用线程池(HTTP操作不阻塞DB队列)
|
||||
private final java.util.concurrent.ExecutorService webExecutor =
|
||||
java.util.concurrent.Executors.newFixedThreadPool(3, r -> {
|
||||
Thread t = new Thread(r, "sdf1-web-worker");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
|
||||
// ★ SSL断路器:连续失败超过阈值后暂停轮询,避免DB队列积压
|
||||
private static final int SSL_CIRCUIT_THRESHOLD = 5; // 连续失败5次触发断路
|
||||
private static final long SSL_CIRCUIT_COOLDOWN_MS = 60000; // 断路冷却60秒
|
||||
private volatile int sslConsecutiveFailures = 0;
|
||||
private volatile long sslCircuitOpenUntil = 0;
|
||||
|
||||
public WebManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
this.config = plugin.getConfig2();
|
||||
@@ -217,6 +231,36 @@ public class WebManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== SSL断路器 ====================
|
||||
|
||||
/**
|
||||
* 检查SSL断路器是否开启(跳过轮询请求)
|
||||
*/
|
||||
private boolean isCircuitOpen() {
|
||||
if (sslCircuitOpenUntil == 0) return false;
|
||||
if (System.currentTimeMillis() < sslCircuitOpenUntil) return true;
|
||||
// 冷却期结束,允许重试
|
||||
sslCircuitOpenUntil = 0;
|
||||
sslConsecutiveFailures = 0;
|
||||
plugin.getLogger().info("[Web通信] SSL断路器冷却结束,恢复轮询");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交Web任务到专用线程池(HTTP操作不阻塞DB队列)
|
||||
* HTTP完成后再把DB写入提交到DB队列
|
||||
*/
|
||||
private void submitWebTask(String name, Runnable action) {
|
||||
if (isCircuitOpen()) return;
|
||||
webExecutor.submit(() -> {
|
||||
try {
|
||||
action.run();
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[Web线程] " + name + " 异常: " + e.getClass().getSimpleName() + " - " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 工具方法 ====================
|
||||
|
||||
/**
|
||||
@@ -273,6 +317,7 @@ public class WebManager {
|
||||
.build();
|
||||
HttpResponse<String> resp = cfHttpClient.send(req, HttpResponse.BodyHandlers.ofString());
|
||||
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
|
||||
sslConsecutiveFailures = 0; // 成功 → 重置断路器
|
||||
return resp.body();
|
||||
}
|
||||
// ★ 500时记录响应体(PHP错误信息在body里)
|
||||
@@ -282,6 +327,14 @@ public class WebManager {
|
||||
plugin.getLogger().warning("[Web通信] GET HTTP " + resp.statusCode() + ": " + shortUrl + " | 响应: " + shortBody);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
// ★ SSL断路器:跟踪连续失败
|
||||
if (e.getMessage() != null && e.getMessage().contains("handshake")) {
|
||||
sslConsecutiveFailures++;
|
||||
if (sslConsecutiveFailures >= SSL_CIRCUIT_THRESHOLD) {
|
||||
sslCircuitOpenUntil = System.currentTimeMillis() + SSL_CIRCUIT_COOLDOWN_MS;
|
||||
plugin.getLogger().warning("[Web通信] SSL断路器触发: 连续失败" + sslConsecutiveFailures + "次,暂停轮询60秒");
|
||||
}
|
||||
}
|
||||
plugin.getLogger().warning("[Web通信] GET异常: " + e.getClass().getSimpleName() + ": " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
@@ -533,6 +586,8 @@ public class WebManager {
|
||||
public void shutdown() {
|
||||
stop();
|
||||
stopDbWorker();
|
||||
// ★ 关闭Web线程池
|
||||
webExecutor.shutdownNow();
|
||||
// 关闭回调服务器
|
||||
if (callbackServer != null) {
|
||||
try {
|
||||
@@ -2963,7 +3018,8 @@ public class WebManager {
|
||||
// 检查是否有玩家正在登录
|
||||
if (!hasPendingLogins()) return;
|
||||
|
||||
submitDbTask("登录轮询-pollWebLoginConfirmations", () -> {
|
||||
// ★ HTTP在webExecutor执行,不阻塞DB队列
|
||||
submitWebTask("登录轮询-pollWebLoginConfirmations", () -> {
|
||||
try {
|
||||
pollWebLoginConfirmations();
|
||||
} catch (Exception e) {
|
||||
@@ -2974,7 +3030,7 @@ public class WebManager {
|
||||
}
|
||||
}
|
||||
});
|
||||
submitDbTask("登录轮询-pollWebLoginRequests", () -> {
|
||||
submitWebTask("登录轮询-pollWebLoginRequests", () -> {
|
||||
try {
|
||||
pollWebLoginRequests();
|
||||
} catch (Exception e) {
|
||||
@@ -4083,11 +4139,11 @@ public class WebManager {
|
||||
* 使用随机间隔避免与其他轮询任务同时执行
|
||||
*/
|
||||
private void scheduleRegisterRequestPoll() {
|
||||
// ★ 注册请求轮询:通过DB队列串行化,高优先级
|
||||
// ★ 注册请求轮询:HTTP在webExecutor执行,DB写入才走DB队列
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
submitDbTask("注册轮询-pollWebRegisterRequests", () -> {
|
||||
submitWebTask("注册轮询-pollWebRegisterRequests", () -> {
|
||||
try {
|
||||
pollWebRegisterRequests();
|
||||
} catch (Exception e) {
|
||||
|
||||
Reference in New Issue
Block a user