From 19e03c5fb4bd08b84de5dd83a69b25f866a6c972 Mon Sep 17 00:00:00 2001 From: youpaishidifu <1@ypshidifu.cn> Date: Thu, 8 Oct 2026 17:02:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=89bug=E5=B9=B6?= =?UTF-8?q?=E5=BD=92=E4=BD=8D=E6=A0=B9=E7=9B=AE=E5=BD=955=E7=B1=BB?= =?UTF-8?q?=E6=96=87=E4=BB=B6=EF=BC=9A=E2=91=A0=E7=A6=81=E8=A8=80=E8=A7=A3?= =?UTF-8?q?=E9=99=A4=E5=90=8E=E5=BC=BA=E5=88=B6=E9=87=8D=E6=96=B0=E4=BA=BA?= =?UTF-8?q?=E6=9C=BA=E9=AA=8C=E8=AF=81(=E8=A7=A3=E7=A6=81=E2=89=A0?= =?UTF-8?q?=E8=A7=A3=E9=99=A4=E4=BA=BA=E6=9C=BA=E9=AA=8C=E8=AF=81=EF=BC=8C?= =?UTF-8?q?=E5=88=B0=E6=9C=9F/=E5=91=BD=E4=BB=A4=E8=A7=A3=E7=A6=81/?= =?UTF-8?q?=E9=87=8D=E7=BD=AE=E5=9D=87=E6=89=93reverify=E6=A0=87=E8=AE=B0?= =?UTF-8?q?=EF=BC=8C=E7=AD=94=E5=AF=B9=E6=B8=85=E9=99=A4)=20=E2=91=A1OP?= =?UTF-8?q?=E7=99=BD=E5=90=8D=E5=8D=95=E8=BF=87=E6=9C=9F=E6=9D=A1=E7=9B=AE?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E8=A1=A5=E6=8E=88(=E4=BF=AE=E5=A4=8D26-10-08?= =?UTF-8?q?=E5=88=B0=E6=9C=9F=E5=90=8E=E6=AF=8F=E5=88=86=E9=92=9F=E6=92=A4?= =?UTF-8?q?=E9=94=80=E2=86=94=E6=8E=88=E4=BA=88=E6=97=A0=E9=99=90=E6=A8=AA?= =?UTF-8?q?=E8=B7=B3)=20=E2=91=A2=E6=95=8F=E6=84=9F=E8=AF=8D=E5=91=BD?= =?UTF-8?q?=E4=B8=AD=E6=94=B9=E4=B8=BA=E6=98=8E=E7=A1=AE=E5=8F=8D=E9=A6=88?= =?UTF-8?q?(=E6=8E=A7=E5=88=B6=E5=8F=B0=E6=97=A5=E5=BF=97+=E7=8E=A9?= =?UTF-8?q?=E5=AE=B6=E6=8F=90=E7=A4=BA+=E4=B8=8B=E4=B8=80=E6=A1=A3?= =?UTF-8?q?=E5=A4=84=E7=BD=9A=E9=A2=84=E5=91=8A=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=90=AF=E7=94=A8=E8=BF=87=E6=BB=A4=E9=85=8D=E7=BD=AE=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=A4=B1=E6=95=88)=20=E2=91=A3bond.db/chat.txt/garbag?= =?UTF-8?q?e.db/maxsell=5Fconfig.txt/pvp.db=E5=90=AF=E5=8A=A8=E6=9C=80?= =?UTF-8?q?=E6=97=A9=E6=9C=9F=E8=87=AA=E5=8A=A8=E8=BF=81=E5=85=A5=E5=AF=B9?= =?UTF-8?q?=E5=BA=94=E5=AD=90=E6=96=87=E4=BB=B6=E5=A4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Sdf1_login/BondManager.java | 4 +- src/main/java/Sdf1_login/CDKManager.java | 4 +- .../java/Sdf1_login/ChatFilterManager.java | 78 ++++++++++++++++-- src/main/java/Sdf1_login/GarbageManager.java | 3 +- src/main/java/Sdf1_login/Main.java | 79 +++++++++++++++++++ .../java/Sdf1_login/OpWhiteListManager.java | 6 ++ src/main/java/Sdf1_login/PVPManager.java | 3 +- src/main/java/Sdf1_login/ShopManager.java | 4 +- 8 files changed, 170 insertions(+), 11 deletions(-) diff --git a/src/main/java/Sdf1_login/BondManager.java b/src/main/java/Sdf1_login/BondManager.java index 6ae5ee2..5228d49 100644 --- a/src/main/java/Sdf1_login/BondManager.java +++ b/src/main/java/Sdf1_login/BondManager.java @@ -59,7 +59,9 @@ public class BondManager { try { Class.forName("org.sqlite.JDBC"); File dbFile = new File( - plugin.getDataFolder(), "bond.db"); + new File(plugin.getDataFolder(), "财务债券"), + "bond.db"); + dbFile.getParentFile().mkdirs(); db = DriverManager.getConnection( "jdbc:sqlite:" + dbFile.getAbsolutePath()); Statement st = db.createStatement(); diff --git a/src/main/java/Sdf1_login/CDKManager.java b/src/main/java/Sdf1_login/CDKManager.java index 8e7b56c..0157ca6 100644 --- a/src/main/java/Sdf1_login/CDKManager.java +++ b/src/main/java/Sdf1_login/CDKManager.java @@ -25,7 +25,9 @@ public class CDKManager { try { Class.forName("org.sqlite.JDBC"); File dbFile = new File( - plugin.getDataFolder(), "bond.db"); + new File(plugin.getDataFolder(), "财务债券"), + "bond.db"); + dbFile.getParentFile().mkdirs(); db = DriverManager.getConnection( "jdbc:sqlite:" + dbFile.getAbsolutePath()); Statement st = db.createStatement(); diff --git a/src/main/java/Sdf1_login/ChatFilterManager.java b/src/main/java/Sdf1_login/ChatFilterManager.java index fc14bbb..f1b3a45 100644 --- a/src/main/java/Sdf1_login/ChatFilterManager.java +++ b/src/main/java/Sdf1_login/ChatFilterManager.java @@ -97,6 +97,9 @@ public class ChatFilterManager { /** 已验证过的玩家名单(永久,跨session持久化到内存) */ private final Set verifiedPlayers = ConcurrentHashMap.newKeySet(); + + /** ★ 解禁后强制重新人机验证名单(2026-10-08:解禁≠解除人机验证) */ + private final Set reverifyRequired = ConcurrentHashMap.newKeySet(); /** 验证码数据存储(public供Main.java访问清理) */ public final Map verificationData = new ConcurrentHashMap<>(); @@ -149,6 +152,28 @@ public class ChatFilterManager { */ public VerificationResult checkNewPlayerVerification(Player p) { String name = p.getName(); + + // ★ 解禁 ≠ 解除人机验证(2026-10-08):解禁后必须重新过人机验证才能发言。 + // 该分支优先级最高——连 verifiedPlayers 与正版OAuth豁免也压过。 + if (reverifyRequired.contains(name)) { + VerificationData vd0 = verificationData.get(name); + if (vd0 != null && vd0.completed) { + verifiedPlayers.add(name); + verificationData.remove(name); + reverifyRequired.remove(name); + return VerificationResult.VERIFIED; + } + if (vd0 != null && vd0.isExpired()) { + verificationData.remove(name); + p.sendMessage("§c§l[验证码] §c上一题超时,已为你换新题"); + generateMathVerification(name); + return VerificationResult.TIMEOUT; + } + if (vd0 != null) { + return VerificationResult.PENDING; + } + return VerificationResult.NEED_VERIFICATION; + } // ★ 永久已验证的玩家 → 直接放行 if (verifiedPlayers.contains(name)) { @@ -502,6 +527,7 @@ public class ChatFilterManager { vd.completed = true; verificationData.remove(playerName); verifiedPlayers.add(playerName); // 永久记录 - 验证通过 + reverifyRequired.remove(playerName); // 解禁重验已通过 return VerificationResult.VERIFIED; } else { // ★ 回答错误 → 不清除验证码也不加入verifiedPlayers,让玩家重新答题 @@ -551,6 +577,7 @@ public class ChatFilterManager { vd.completed = true; verificationData.remove(name); verifiedPlayers.add(name); + reverifyRequired.remove(name); // 解禁重验已通过 return VerificationResult.VERIFIED; } @@ -706,7 +733,8 @@ public class ChatFilterManager { sensitiveWords.clear(); // 重新加载前先清空 File f = new File( - plugin.getDataFolder(), "chat.txt"); + new File(plugin.getDataFolder(), "发言规则管控"), "chat.txt"); + f.getParentFile().mkdirs(); if (!f.exists()) writeDefaultFile(f); boolean hasSensitiveSection = false; // 记录文件是否已有敏感词列表节 try (BufferedReader r = @@ -749,8 +777,8 @@ public class ChatFilterManager { String[] kv = t.split(":", 2); String k = kv[0].trim(); String v = kv[1].trim(); - if (equals("启用过滤") - || equals("enabled")) { + if (k.equals("启用过滤") + || k.equals("enabled")) { enabled = parseBool(v); continue; } @@ -928,7 +956,8 @@ public class ChatFilterManager { public void saveConfig() { File f = new File( - plugin.getDataFolder(), "chat.txt"); + new File(plugin.getDataFolder(), "发言规则管控"), "chat.txt"); + f.getParentFile().mkdirs(); try (PrintWriter pw = new PrintWriter( new OutputStreamWriter( new FileOutputStream(f), @@ -1094,6 +1123,8 @@ public class ChatFilterManager { muteIssuers.remove(name); muteStarts.remove(name); clearMuteDb(name); + // ★ 禁言到期自动解禁 → 立即要求重新人机验证(解禁≠解除人机验证) + markReverify(name); return false; } return true; @@ -1276,6 +1307,19 @@ public class ChatFilterManager { muteIssuers.remove(name); muteStarts.remove(name); clearMuteDb(name); + // ★ 解禁≠解除人机验证(2026-10-08) + markReverify(name); + } + + /** + * ★ 解禁(命令解禁/禁言到期/重置)→ 吊销人机验证: + * 移出已验证名单、清掉挂起的题与缓存消息,下次发言必须重新过人机验证。 + */ + public void markReverify(String name) { + reverifyRequired.add(name); + verifiedPlayers.remove(name); + verificationData.remove(name); + pendingMessages.remove(name); } // ========== 禁言持久化(重启后仍生效) ========== @@ -1365,8 +1409,32 @@ public class ChatFilterManager { } public void resetPlayer(String name) { - mutedPlayers.remove(name); + boolean wasMuted = mutedPlayers.remove(name) != null; violationCount.remove(name); + // ★ 清禁言=解禁 → 必须重新人机验证;从未被禁言的只清计数,不打扰 + if (wasMuted) { + markReverify(name); + } + } + + /** 下一档违规将触发的处罚描述(命中时给玩家预告),无更高档返回 null */ + public String nextPunishHint(int violation) { + int nextKey = Integer.MAX_VALUE; + for (Integer k : punishmentRules.keySet()) { + if (k > violation && k < nextKey) { + nextKey = k; + } + } + if (nextKey == Integer.MAX_VALUE) { + return null; + } + String type = punishmentRules.get(nextKey); + int dur = punishmentDurations.getOrDefault(nextKey, muteDuration); + String cn = switchPunishType(type); + if ("mute".equals(type)) { + cn += " " + fmtDuration(dur); + } + return "第 " + nextKey + " 次违规: " + cn; } public boolean isPlayerWhitelisted( diff --git a/src/main/java/Sdf1_login/GarbageManager.java b/src/main/java/Sdf1_login/GarbageManager.java index c8adf54..2244314 100644 --- a/src/main/java/Sdf1_login/GarbageManager.java +++ b/src/main/java/Sdf1_login/GarbageManager.java @@ -37,8 +37,9 @@ public class GarbageManager { try { Class.forName("org.sqlite.JDBC"); File dbFile = new File( - plugin.getDataFolder(), + new File(plugin.getDataFolder(), "垃圾箱"), "garbage.db"); + dbFile.getParentFile().mkdirs(); db = DriverManager.getConnection( "jdbc:sqlite:" + dbFile.getAbsolutePath()); diff --git a/src/main/java/Sdf1_login/Main.java b/src/main/java/Sdf1_login/Main.java index c935059..b246287 100644 --- a/src/main/java/Sdf1_login/Main.java +++ b/src/main/java/Sdf1_login/Main.java @@ -386,6 +386,8 @@ public class Main extends JavaPlugin saveResource("illegal_domains.txt", false); getLogger().info("[配置] 已自动释放 illegal_domains.txt 到插件目录"); } + // ★ 任务94:根目录5类文件归位(必须在任何 Manager 打开文件/DB 之前) + migrateRootDataFiles(); // 启动时清理.sdf1临时文件 cleanupSdf1Files(); salesStats = new SalesStatsManager(this); @@ -986,6 +988,67 @@ public class Main extends JavaPlugin } } + // ===== 任务94:根目录5类文件归位(2026-10-08) ===== + /** + * 启动最早期把根目录杂项移入对应子文件夹(幂等)。 + * 目标已存在则跳过不覆盖;迁移失败只警告不阻断启动。 + * 必须在任何 Manager 打开文件/数据库之前调用。 + */ + private void migrateRootDataFiles() { + File root = getDataFolder(); + String[][] moves = { + {"bond.db", "财务债券"}, + {"bond.db-shm", "财务债券"}, + {"bond.db-wal", "财务债券"}, + {"chat.txt", "发言规则管控"}, + {"garbage.db", "垃圾箱"}, + {"garbage.db-shm", "垃圾箱"}, + {"garbage.db-wal", "垃圾箱"}, + {"pvp.db", "pvp"}, + {"pvp.db-shm", "pvp"}, + {"pvp.db-wal", "pvp"}, + {"maxsell_config.txt", "shop"}, + }; + for (String[] m : moves) { + File src = new File(root, m[0]); + if (!src.exists()) { + continue; + } + File dir = new File(root, m[1]); + dir.mkdirs(); + File dst = new File(dir, m[0]); + if (dst.exists()) { + getLogger().warning("[归位] " + m[0] + + " 目标已存在,跳过(根目录保留副本): " + + m[1] + "/" + m[0]); + continue; + } + if (src.renameTo(dst)) { + getLogger().info("[归位] " + m[0] + " → " + m[1] + "/"); + } else { + getLogger().warning("[归位] " + m[0] + " → " + m[1] + + "/ 迁移失败(保留原位,请手动移动)"); + } + } + // 用户点名的 maxshell_*.txt 也归入 shop/ + File[] extras = root.listFiles((d, n) -> + n.toLowerCase().startsWith("maxshell_") + && n.toLowerCase().endsWith(".txt")); + if (extras != null) { + File dir = new File(root, "shop"); + for (File src : extras) { + File dst = new File(dir, src.getName()); + if (dst.exists()) { + continue; + } + dir.mkdirs(); + if (src.renameTo(dst)) { + getLogger().info("[归位] " + src.getName() + " → shop/"); + } + } + } + } + /** * 清理插件目录下的 .sdf1 后缀文件 * 用于防风控任务准备 @@ -5434,6 +5497,12 @@ public class Main extends JavaPlugin chatFilter.markPlayerActive(p.getName()); } + // ★ 先探一次禁言状态:若刚好到期,isMuted() 内部会立即解禁并打上 + // 「需重新人机验证」标记(2026-10-08:解禁≠解除人机验证) + if (chatFilter != null) { + chatFilter.isMuted(p.getName()); + } + // ===== 新玩家验证码检查(N+1机制) ===== if (chatFilter != null && chatFilter.isEnabled()) { ChatFilterManager.VerificationResult vr = chatFilter.checkNewPlayerVerification(p); @@ -5649,6 +5718,16 @@ public class Main extends JavaPlugin } chatFilter.incrementViolation(p.getName()); int sc = chatFilter.getViolationCount(p.getName()); + // ★ 2026-10-08:命中必须给发送者明确反馈——此前 warn 档完全静默、 + // 自回显又与正常聊天一模一样 →观感「中文敏感词没过滤」 + getLogger().info("[Sdf1_chat] " + p.getName() + " 命中敏感词(" + + ms.get(0) + "),累计违规 " + sc + " 次,消息已拦截"); + p.sendMessage("§c§l[聊天过滤] §7消息包含违规词 §f" + ms.get(0) + + " §7已被拦截 §8(第 " + sc + " 次违规)"); + String punishHint = chatFilter.nextPunishHint(sc); + if (punishHint != null) { + p.sendMessage("§8下次违规将触发: §e" + punishHint); + } String swReason = "说出敏感词(" + ms.get(0) + ")"; String ptype = chatFilter.applyPunishment( diff --git a/src/main/java/Sdf1_login/OpWhiteListManager.java b/src/main/java/Sdf1_login/OpWhiteListManager.java index 0cbdf12..37193b4 100644 --- a/src/main/java/Sdf1_login/OpWhiteListManager.java +++ b/src/main/java/Sdf1_login/OpWhiteListManager.java @@ -755,6 +755,12 @@ public class OpWhiteListManager implements Listener { // 服主 /op list 也看不到他们)。现改为:在线走 Player.setOp、 // 离线走 OfflinePlayer.setOp(无档案的自动跳过,等其上线时再补)。 for (String wn : whitelistedNames()) { + // ★ 2026-10-08 修复:过期/时长无法解析的临时条目不再补授。 + // 否则每轮巡检「第1步撤销过期者 → 第2步又按配置条目原样授予」 + // 形成无限横跳(截图现象:26-10-08 16:00 到期后每分钟撤销+授予各一次)。 + if (!isWhitelisted(wn)) { + continue; + } Player online = Bukkit.getPlayerExact(wn); if (online != null) { if (online.isOp()) { diff --git a/src/main/java/Sdf1_login/PVPManager.java b/src/main/java/Sdf1_login/PVPManager.java index 4f2fcbb..1e46f6b 100644 --- a/src/main/java/Sdf1_login/PVPManager.java +++ b/src/main/java/Sdf1_login/PVPManager.java @@ -108,8 +108,9 @@ public class PVPManager implements Listener { Class.forName( "org.sqlite.JDBC"); File dbFile = new File( - plugin.getDataFolder(), + new File(plugin.getDataFolder(), "pvp"), "pvp.db"); + dbFile.getParentFile().mkdirs(); pvpDb = DriverManager.getConnection( "jdbc:sqlite:" + dbFile.getAbsolutePath()); diff --git a/src/main/java/Sdf1_login/ShopManager.java b/src/main/java/Sdf1_login/ShopManager.java index c0ce181..cde0058 100644 --- a/src/main/java/Sdf1_login/ShopManager.java +++ b/src/main/java/Sdf1_login/ShopManager.java @@ -1635,7 +1635,7 @@ public class ShopManager implements Listener { // ===== maxSell 配置持久化 ===== /** 加载 maxSell 配置从插件数据文件夹下的 maxsell_config.txt */ private void loadMaxSellConfig() { - File cfgFile = new File(plugin.getDataFolder(), "maxsell_config.txt"); + File cfgFile = new File(new File(plugin.getDataFolder(), "shop"), "maxsell_config.txt"); try { if (cfgFile.exists()) { BufferedReader br = new BufferedReader(new FileReader(cfgFile)); @@ -1673,7 +1673,7 @@ public class ShopManager implements Listener { try { File dir = new File(plugin.getDataFolder(), "shop"); if (!dir.exists()) dir.mkdirs(); - File cfgFile = new File(dir.getParentFile(), "maxsell_config.txt"); + File cfgFile = new File(dir, "maxsell_config.txt"); BufferedWriter bw = new BufferedWriter(new FileWriter(cfgFile, StandardCharsets.UTF_8)); bw.write("maxSellLimit=" + maxSellLimit); bw.newLine();