From ac64aee36559b4a93c45ba4a56dc8065e7a125f9 Mon Sep 17 00:00:00 2001 From: youpaishidifu <1@ypshidifu.cn> Date: Thu, 22 Oct 2026 12:17:04 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC41=E6=89=B9=EF=BC=9A=E5=85=A8=E9=87=8F?= =?UTF-8?q?=E8=BD=AC=E8=AF=91=20\uXXXX=20=E8=BD=AC=E4=B9=89=E4=B8=BA?= =?UTF-8?q?=E5=8F=AF=E8=AF=BB=E5=AD=97=E9=9D=A2=E9=87=8F/UnicodeChars?= =?UTF-8?q?=E5=85=B7=E5=90=8D=E5=B8=B8=E9=87=8F=20+=20=E4=BF=AE=E5=A4=8DID?= =?UTF-8?q?E=E6=8F=92=E5=9D=8F=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 UnicodeChars 常量表:不可见字符(零宽/BOM/全角空格/U+0001)统一用具名常量 - 可见字符直接写字面量(§、:、全角数字、汉字区间等),读代码无需心算码位 - 正则层转义(\\uXXXX)保持原样并补注释说明,避免改变正则语义 - 注释里的 \uXXXX 改双写(javac 预处理期生效,注释躲不掉) - 修复IDE插坏的 ConfigManager/UnicodeChars 括号 --- src/main/java/Sdf1_login/AreaProtection.java | 33 +-- .../java/Sdf1_login/ChatFilterManager.java | 12 +- src/main/java/Sdf1_login/ConfigManager.java | 18 +- src/main/java/Sdf1_login/GUIManager.java | 18 +- src/main/java/Sdf1_login/GiftManager.java | 2 +- src/main/java/Sdf1_login/Main.java | 22 +- .../java/Sdf1_login/MaintenanceManager.java | 8 +- .../java/Sdf1_login/OpWhiteListManager.java | 2 +- src/main/java/Sdf1_login/PVPArenaManager.java | 2 +- src/main/java/Sdf1_login/ShopManager.java | 10 +- src/main/java/Sdf1_login/SnManager.java | 6 +- src/main/java/Sdf1_login/UnicodeChars.java | 217 ++++++++++++++++++ src/main/java/Sdf1_login/WebManager.java | 2 +- src/main/java/Sdf1_login/WelcomeManager.java | 4 +- 14 files changed, 287 insertions(+), 69 deletions(-) create mode 100644 src/main/java/Sdf1_login/UnicodeChars.java diff --git a/src/main/java/Sdf1_login/AreaProtection.java b/src/main/java/Sdf1_login/AreaProtection.java index bc16325..1589e32 100644 --- a/src/main/java/Sdf1_login/AreaProtection.java +++ b/src/main/java/Sdf1_login/AreaProtection.java @@ -1301,7 +1301,8 @@ public class AreaProtection implements Listener { List result = new ArrayList<>(); if (s == null || s.isEmpty()) return result; String trimmed = s.trim(); - // ★ 支持JSON数组格式(PHP端存储格式),如 ["速度","急迫"] 或 ["\u8bd5\u70bc..."] + // ★ 支持JSON数组格式(PHP端存储格式),如 ["速度","急迫"] 或 ["\\u8bd5\\u70bc..."] + // (PHP 端 JSON 存储时中文会被转义成 \\uXXXX 形式,此处展示的是 PHP 侧原样) if (trimmed.startsWith("[") && trimmed.endsWith("]")) { try { String inner = trimmed.substring(1, trimmed.length() - 1); @@ -1821,27 +1822,27 @@ public class AreaProtection implements Listener { char c = input.charAt(i); // 跳过零宽字符和不可见字符 - if (c == '\u200B' // 零宽空格 - || c == '\u200C' // 零宽非连接 - || c == '\u200D' // 零宽连接 - || c == '\uFEFF' // BOM - || c == '\u00A0' // 不间断空格 - || c == '\u2028' // 行分隔符 - || c == '\u2029' // 段分隔符 - || c == '\u200E' // LTR标记 - || c == '\u200F' // RTL标记 - || c == '\u2060' // 字连接符 - || (c >= '\u2000' && c <= '\u200A')) { // 各种空格 + if (c == UnicodeChars.ZERO_WIDTH_SPACE // 零宽空格 + || c == UnicodeChars.ZERO_WIDTH_NON_JOINER // 零宽非连接 + || c == UnicodeChars.ZERO_WIDTH_JOINER // 零宽连接 + || c == UnicodeChars.BOM // BOM + || c == UnicodeChars.NBSP // 不间断空格 + || c == UnicodeChars.LINE_SEPARATOR // 行分隔符 + || c == UnicodeChars.PARAGRAPH_SEPARATOR // 段分隔符 + || c == UnicodeChars.LTR_MARK // LTR标记 + || c == UnicodeChars.RTL_MARK // RTL标记 + || c == UnicodeChars.WORD_JOINER // 字连接符 + || (c >= UnicodeChars.SPACE_U2000 && c <= UnicodeChars.SPACE_U200A)) { // 各种空格 continue; } // 全角→半角映射 - if (c >= '\uFF01' && c <= '\uFF5E') { + if (c >= '!' && c <= '~') { c = (char) (c - 0xFEE0); } // 全角空格→半角空格 - if (c == '\u3000') { + if (c == UnicodeChars.IDEOGRAPHIC_SPACE) { c = ' '; } @@ -6930,7 +6931,7 @@ public class AreaProtection implements Listener { } String n = name.trim(); if (n.length() > 20) return "名字不能超过20个字符"; - if (!n.matches("^[A-Za-z0-9\u4e00-\u9fa5_-]{1,20}$")) { + if (!n.matches("^[A-Za-z0-9一-龥_-]{1,20}$")) { return "名字只能包含中英文、数字、下划线和连字符"; } if (n.equalsIgnoreCase("global")) { @@ -11470,7 +11471,7 @@ public class AreaProtection implements Listener { String[] parts = inner.split("\""); java.util.List result = new java.util.ArrayList<>(); for (String part : parts) { - String trimmed = part.trim().replaceAll("\u00A7[0-9a-fk-or]", ""); + String trimmed = part.trim().replaceAll("§[0-9a-fk-or]", ""); if (!trimmed.isEmpty()) result.add(trimmed); } return result.toArray(new String[0]); diff --git a/src/main/java/Sdf1_login/ChatFilterManager.java b/src/main/java/Sdf1_login/ChatFilterManager.java index e00d59d..128e14b 100644 --- a/src/main/java/Sdf1_login/ChatFilterManager.java +++ b/src/main/java/Sdf1_login/ChatFilterManager.java @@ -681,18 +681,18 @@ public class ChatFilterManager { + "[_.][\\p{L}]{2,}"); private static final Pattern DOT_VARIANTS = Pattern.compile( - "[\uff0e\u3002\u2025\u2026\u00b7\uff65\ufe52]"); + "[.。‥…·・﹒]"); /** 零宽/不可见字符:变形URL归一前剔除(如 baidu.<零宽空格>com) */ private static final Pattern INVISIBLE = Pattern.compile( - "[\u200b\u200c\u200d\u2060\ufeff\u00ad]"); + UnicodeChars.INVISIBLE_CHARS + "]"); /** 点周带空白的变形域名:baidu . com(2026-10-08 加强拦截) */ private static final Pattern SPACED_DOT_URL = Pattern.compile( "(?i)(?:[\\p{L}0-9](?:[\\p{L}0-9\\-]*[\\p{L}0-9])?[\\s\\u3000]*\\.)+[\\s\\u3000]*[\\p{L}]{2,}"); private static final Pattern COLOR_CODES = Pattern.compile( - "\u00a7[0-9a-fk-orA-FK-OR]"); + "§[0-9a-fk-orA-FK-OR]"); private static final Set FILE_EXT; static { @@ -747,7 +747,7 @@ public class ChatFilterManager { String section = ""; String line; while ((line = r.readLine()) != null) { - if (line.startsWith("\uFEFF")) + if (line.startsWith(UnicodeChars.BOM_TEXT)) line = line.substring(1); String t = line.trim(); @@ -1058,8 +1058,8 @@ public class ChatFilterManager { while (m2.find()) { String token = m2.group(); if (token.indexOf(' ') < 0 && token.indexOf('\t') < 0 - && token.indexOf('\u3000') < 0) continue; // 无空白 = 常规域名,上面已提取 - String compact = token.replaceAll("[\\s\u3000]+", "").toLowerCase(); + && token.indexOf(UnicodeChars.IDEOGRAPHIC_SPACE) < 0) continue; // 无空白 = 常规域名,上面已提取 + String compact = token.replaceAll("[\\s" + UnicodeChars.IDEOGRAPHIC_SPACE + "]+", "").toLowerCase(); int dot = compact.lastIndexOf('.'); if (dot < 0) continue; if (!illegalDomainSuffixes.contains(compact.substring(dot + 1))) continue; diff --git a/src/main/java/Sdf1_login/ConfigManager.java b/src/main/java/Sdf1_login/ConfigManager.java index 8e0ac69..b9703b8 100644 --- a/src/main/java/Sdf1_login/ConfigManager.java +++ b/src/main/java/Sdf1_login/ConfigManager.java @@ -827,7 +827,7 @@ public class ConfigManager { // 处理UTF-8 BOM头 if (firstLine) { firstLine = false; - if (line.startsWith("\uFEFF")) { + if (line.startsWith(UnicodeChars.BOM_TEXT)) { line = line.substring(1); } } @@ -1601,7 +1601,7 @@ public class ConfigManager { /** 字节数组 → 行列表(UTF-8,去 BOM,去 CR,去文件末空行) */ private static List readUtf8Lines(byte[] data) { String s = new String(data, StandardCharsets.UTF_8); - if (!s.isEmpty() && s.charAt(0) == '\uFEFF') s = s.substring(1); + if (!s.isEmpty() && s.charAt(0) == UnicodeChars.BOM) s = s.substring(1); List L = new ArrayList<>(); for (String line : s.split("\n", -1)) { if (line.endsWith("\r")) line = line.substring(0, line.length() - 1); @@ -1623,10 +1623,10 @@ public class ConfigManager { private boolean upgradeJsonMissing(File file, byte[] tplBytes, String label) { try { String tplText = new String(tplBytes, StandardCharsets.UTF_8); - if (!tplText.isEmpty() && tplText.charAt(0) == '\uFEFF') tplText = tplText.substring(1); + if (!tplText.isEmpty() && tplText.charAt(0) == UnicodeChars.BOM) tplText = tplText.substring(1); byte[] cur = java.nio.file.Files.readAllBytes(file.toPath()); String oldText = new String(cur, StandardCharsets.UTF_8); - if (!oldText.isEmpty() && oldText.charAt(0) == '\uFEFF') oldText = oldText.substring(1); + if (!oldText.isEmpty() && oldText.charAt(0) == UnicodeChars.BOM) oldText = oldText.substring(1); com.google.gson.JsonElement tplEl; try { tplEl = com.google.gson.JsonParser.parseString(tplText); @@ -1796,7 +1796,7 @@ public class ConfigManager { boolean hasChinese = false; for (int ci = 0; ci < testTrimmed.length(); ci++) { char cc = testTrimmed.charAt(ci); - if (cc >= '\u4e00' && cc <= '\u9fff') { hasChinese = true; break; } + if (cc >= '一' && cc <= '鿿') { hasChinese = true; break; } } if (hasChinese) { int cnResult = parseChineseTime(testTrimmed); @@ -1877,7 +1877,7 @@ public class ConfigManager { private int parseChineseTime(String raw) { // 中文数字映射(含繁体/大写/各种变体) java.util.Map cnDigit = new java.util.LinkedHashMap<>(); - cnDigit.put("零", 0); cnDigit.put("\u3007", 0); + cnDigit.put("零", 0); cnDigit.put("〇", 0); cnDigit.put("一", 1); cnDigit.put("壹", 1); cnDigit.put("幺", 1); cnDigit.put("二", 2); cnDigit.put("贰", 2); cnDigit.put("貳", 2); cnDigit.put("两", 2); cnDigit.put("弐", 2); cnDigit.put("三", 3); cnDigit.put("叁", 3); cnDigit.put("參", 3); @@ -1929,9 +1929,9 @@ public class ConfigManager { int i = 0; while (i < s.length()) { char c = s.charAt(i); - if (c >= '\u4e00' && c <= '\u9fff') { + if (c >= '一' && c <= '鿿') { // 尝试2字符匹配(先匹配单位复合词) - if (i + 1 < s.length() && s.charAt(i+1) >= '\u4e00' && s.charAt(i+1) <= '\u9fff') { + if (i + 1 < s.length() && s.charAt(i+1) >= '一' && s.charAt(i+1) <= '鿿') { String two = s.substring(i, i+2); boolean matchedUnit = false; for (var entry : unitsList) { @@ -1987,7 +1987,7 @@ public class ConfigManager { // 检查是否是多字符中文数字组合(如"十五") boolean isMultiCharNum = false; for (String t : tokens) { - if (t.equals(tok) && t.length() > 1 && t.chars().allMatch(c -> (c >= '\u4e00' && c <= '\u9fff') || (c >= '0' && c <= '9'))) { + if (t.equals(tok) && t.length() > 1 && t.chars().allMatch(c -> (c >= '一' && c <= '鿿') || (c >= '0' && c <= '9'))) { // 所有字符都是中文数字或乘法器 → 解析中文数字组合 int val = evalChineseCompoundNum(tok, cnDigit, multipliers); if (val >= 0) { diff --git a/src/main/java/Sdf1_login/GUIManager.java b/src/main/java/Sdf1_login/GUIManager.java index c268c1b..a0738ea 100644 --- a/src/main/java/Sdf1_login/GUIManager.java +++ b/src/main/java/Sdf1_login/GUIManager.java @@ -399,7 +399,7 @@ public class GUIManager implements Listener { public void openSubMenu( Player p, String fileName) { // ★ 剥离MC颜色代码(§f、§7等),防止污染文件名 - fileName = fileName.replaceAll("\u00A7[0-9a-fk-or]", "").trim(); + fileName = fileName.replaceAll("§[0-9a-fk-or]", "").trim(); String menuTitle = "§6§l" + fileName .replace(".txt", ""); @@ -632,7 +632,7 @@ public class GUIManager implements Listener { for (String line : block.split("\n")) { line = line.trim(); String v = ""; - int idx = line.indexOf('\uff1a'); + int idx = line.indexOf(':'); if (idx < 0) idx = line.indexOf(':'); if (idx >= 0) { @@ -1766,13 +1766,13 @@ public class GUIManager implements Listener { oldName = "自定义物品"; } mm.setDisplayName( - "\u00a7e\u00a7l[菜单] \u00a7f" + "§e§l[菜单] §f" + oldName); List lore = mm.hasLore() ? new ArrayList<>(mm.getLore()) : new ArrayList<>(); lore.add(0, - "\u00a77右键打开主菜单"); + "§7右键打开主菜单"); mm.setLore(lore); mm.getPersistentDataContainer().set( new org.bukkit.NamespacedKey( @@ -1877,7 +1877,7 @@ public class GUIManager implements Listener { List lore = meta.getLore(); if (lore == null) return false; for (String line : lore) { - if (line.contains("\u00a70\u00a7k") + if (line.contains("§0§k") || line.contains("CUSTOM")) { return true; } @@ -1986,7 +1986,7 @@ public class GUIManager implements Listener { && im.getLore() != null) { for (String ln : im.getLore()) { if (ln.contains( - "\u00a78")) { + "§8")) { p.getInventory() .setItem(i, null); @@ -2010,7 +2010,7 @@ public class GUIManager implements Listener { return true; ItemMeta im = item.getItemMeta(); if (im == null) return true; - if ("\u00a77默认菜单(雪球)" + if ("§7默认菜单(雪球)" .equals(im.getDisplayName())) return true; if (im.hasLore() && im.getLore() != null @@ -2040,7 +2040,7 @@ public class GUIManager implements Listener { && im.hasLore() && im.getLore() != null) { for (String ln : im.getLore()) { - if (ln.contains("\u00a77右键打开主菜单")) + if (ln.contains("§7右键打开主菜单")) return true; } } @@ -2053,7 +2053,7 @@ public class GUIManager implements Listener { return true; ItemMeta im = item.getItemMeta(); if (im == null) return true; - if ("\u00a77默认菜单(雪球)" + if ("§7默认菜单(雪球)" .equals(im.getDisplayName())) { return true; } diff --git a/src/main/java/Sdf1_login/GiftManager.java b/src/main/java/Sdf1_login/GiftManager.java index 1bab06a..6dc7731 100644 --- a/src/main/java/Sdf1_login/GiftManager.java +++ b/src/main/java/Sdf1_login/GiftManager.java @@ -59,7 +59,7 @@ public class GiftManager { || line.contains("任务内容")) { int colon = line.indexOf(':'); if (colon < 0) - colon = line.indexOf('\uff1a'); + colon = line.indexOf(':'); if (colon >= 0) { rewards.add( line.substring(colon + 1) diff --git a/src/main/java/Sdf1_login/Main.java b/src/main/java/Sdf1_login/Main.java index b1630b4..0092a08 100644 --- a/src/main/java/Sdf1_login/Main.java +++ b/src/main/java/Sdf1_login/Main.java @@ -3537,7 +3537,7 @@ public class Main extends JavaPlugin originalName = "自定义物品"; } cm.setDisplayName( - "\u00a7e\u00a7l[菜单] \u00a7f" + "§e§l[菜单] §f" + originalName); List cmLore = cm.hasLore() @@ -3546,9 +3546,9 @@ public class Main extends JavaPlugin // 避免重复添加 if (cmLore.isEmpty() || !cmLore.get(0).contains( - "\u00a77右键打开主菜单")) { + "§7右键打开主菜单")) { cmLore.add(0, - "\u00a77右键打开主菜单"); + "§7右键打开主菜单"); } cm.setLore(cmLore); customIcon.setItemMeta(cm); @@ -3575,10 +3575,10 @@ public class Main extends JavaPlugin ItemMeta im = snow.getItemMeta(); if (im != null) { im.setDisplayName( - "\u00a7e\u00a7l[菜单] \u00a7f右键打开主菜单"); + "§e§l[菜单] §f右键打开主菜单"); List lore = new ArrayList<>(); - lore.add("\u00a77右键点击打开功能菜单"); - lore.add("\u00a78" + MENU_SNOWBALL_TAG); + lore.add("§7右键点击打开功能菜单"); + lore.add("§8" + MENU_SNOWBALL_TAG); im.setLore(lore); snow.setItemMeta(im); } @@ -3767,7 +3767,7 @@ public class Main extends JavaPlugin // Sdf1_game宝箱物品标记格式: §0§kCUSTOM|玩家|... // 同时检查两种标记格式 if (line.contains("CUSTOM") - || line.contains("\u00a70\u00a7k")) + || line.contains("§0§k")) return true; } return false; @@ -5366,7 +5366,7 @@ public class Main extends JavaPlugin if (raw == null || raw.isEmpty()) return Component.text("管理员", NamedTextColor.WHITE); // 先剥掉可能混进来的 § 原生色码,避免与 & 转换叠加出乱码 - String s = raw.replace("\u00a7", "&"); + String s = raw.replace("§", "&"); // 压掉换行与制表,保证单行 s = s.replaceAll("[\\r\\n\\t]+", " ").trim(); return LegacyComponentSerializer.legacyAmpersand() @@ -6569,7 +6569,7 @@ public class Main extends JavaPlugin boolean hasChinese = false; for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); - if (c >= '\u4e00' && c <= '\u9fff') { hasChinese = true; break; } + if (c >= '一' && c <= '鿿') { hasChinese = true; break; } } if (hasChinese) return; // 触发口径(35批修订2):整条消息必须包含 ≥3 个英文字母才机翻 —— @@ -7378,7 +7378,7 @@ public class Main extends JavaPlugin String cmd = subLn .replace("§7指令: §f", "") .trim() - .replaceAll("\u00A7[0-9a-fk-or]", ""); // 剥离残留MC颜色代码 + .replaceAll("§[0-9a-fk-or]", ""); // 剥离残留MC颜色代码 if (cmd.isEmpty() || cmd.equals("/") || cmd.equals("null")) { @@ -7672,7 +7672,7 @@ public class Main extends JavaPlugin if (ln.contains("§7指令: §f")) { String cmd = ln.replace( "§7指令: §f", "").trim() - .replaceAll("\u00A7[0-9a-fk-or]", ""); // 剥离残留MC颜色代码 + .replaceAll("§[0-9a-fk-or]", ""); // 剥离残留MC颜色代码 if (cmd.isEmpty() || cmd.equals("/") || cmd.equals("null")) { diff --git a/src/main/java/Sdf1_login/MaintenanceManager.java b/src/main/java/Sdf1_login/MaintenanceManager.java index a96afd7..8207309 100644 --- a/src/main/java/Sdf1_login/MaintenanceManager.java +++ b/src/main/java/Sdf1_login/MaintenanceManager.java @@ -790,9 +790,9 @@ public class MaintenanceManager implements Listener { if (s.isEmpty()) return; if (note != null) warn(log, note + ": " + raw); // 仍坚持「一行一个玩家名」:整行收录,但格式不规范必须能被看见 - if (s.indexOf(',') >= 0 || s.indexOf('\uFF0C') >= 0 || s.indexOf('\u3001') >= 0 - || s.indexOf(';') >= 0 || s.indexOf('\uFF1B') >= 0 || s.indexOf(' ') >= 0 - || s.indexOf('\u3000') >= 0 || s.indexOf('\t') >= 0) { + if (s.indexOf(',') >= 0 || s.indexOf(',') >= 0 || s.indexOf('、') >= 0 + || s.indexOf(';') >= 0 || s.indexOf(';') >= 0 || s.indexOf(' ') >= 0 + || s.indexOf(UnicodeChars.IDEOGRAPHIC_SPACE) >= 0 || s.indexOf('\t') >= 0) { warn(log, "白名单条目不符合「一行一个玩家名」,已按整行收录: " + s); } wl.add(s); @@ -807,7 +807,7 @@ public class MaintenanceManager implements Listener { /** 行内注释起点(# / # / //),返回 -1 表示没有 */ private static int indexOfInlineComment(String s) { int h = s.indexOf('#'); - if (h < 0) h = s.indexOf('\uFF03'); + if (h < 0) h = s.indexOf('#'); if (h < 0) h = s.indexOf("//"); return h <= 0 ? -1 : h; } diff --git a/src/main/java/Sdf1_login/OpWhiteListManager.java b/src/main/java/Sdf1_login/OpWhiteListManager.java index 119cdf7..774aa57 100644 --- a/src/main/java/Sdf1_login/OpWhiteListManager.java +++ b/src/main/java/Sdf1_login/OpWhiteListManager.java @@ -1517,6 +1517,6 @@ public class OpWhiteListManager implements Listener { /** 日志里去掉颜色符号,避免控制台刷屏乱码 */ private static String stripColor(String s) { - return s == null ? "" : s.replaceAll("(?i)&[0-9a-k]", "").replaceAll("\u00a7[0-9a-k]", ""); + return s == null ? "" : s.replaceAll("(?i)&[0-9a-k]", "").replaceAll("§[0-9a-k]", ""); } } \ No newline at end of file diff --git a/src/main/java/Sdf1_login/PVPArenaManager.java b/src/main/java/Sdf1_login/PVPArenaManager.java index ac53847..1af046f 100644 --- a/src/main/java/Sdf1_login/PVPArenaManager.java +++ b/src/main/java/Sdf1_login/PVPArenaManager.java @@ -1805,7 +1805,7 @@ public class PVPArenaManager implements Listener { killerSession); } - ((Player) killer).sendMessage("\u00a7a\u00a7l击杀 " + vName + "\u00a7a 当前击杀: " + pvpKills.get(kName)); + ((Player) killer).sendMessage("§a§l击杀 " + vName + "§a 当前击杀: " + pvpKills.get(kName)); } pvpDeaths.put(player.getName(), pvpDeaths.getOrDefault(player.getName(), 0) + 1); // ★ 非击杀死亡也持久化(如摔死/溺水) diff --git a/src/main/java/Sdf1_login/ShopManager.java b/src/main/java/Sdf1_login/ShopManager.java index c8046e6..53125b5 100644 --- a/src/main/java/Sdf1_login/ShopManager.java +++ b/src/main/java/Sdf1_login/ShopManager.java @@ -2874,19 +2874,19 @@ public class ShopManager implements Listener { for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); // 全角数字→半角 - if (c >= '\uFF10' && c <= '\uFF19') { + if (c >= '0' && c <= '9') { sb.append((char)(c - 0xFF10 + '0')); } // 全角字母→半角 - else if (c >= '\uFF21' && c <= '\uFF3A') { + else if (c >= 'A' && c <= 'Z') { sb.append((char)(c - 0xFF21 + 'A')); } - else if (c >= '\uFF41' && c <= '\uFF5A') { + else if (c >= 'a' && c <= 'z') { sb.append((char)(c - 0xFF41 + 'a')); } // ★ 保留中文字符(Unicode CJK统一汉字范围) - else if ((c >= '\u4E00' && c <= '\u9FFF') - || (c >= '\u3400' && c <= '\u4DBF')) { + else if ((c >= '一' && c <= '鿿') + || (c >= '㐀' && c <= '䶿')) { sb.append(c); } // 保留半角字母和数字 diff --git a/src/main/java/Sdf1_login/SnManager.java b/src/main/java/Sdf1_login/SnManager.java index 89d0030..52541e6 100644 --- a/src/main/java/Sdf1_login/SnManager.java +++ b/src/main/java/Sdf1_login/SnManager.java @@ -135,7 +135,7 @@ public class SnManager implements Listener { /** 作废实物拦截提示限流(玩家名 -> 上次提示时间) */ private final Map deadMsgAt = new ConcurrentHashMap<>(); - /** PHP 命令执行结果缓冲(id + \u0001 + result),由 tickSync 回传 */ + /** PHP 命令执行结果缓冲(id + U+0001 分隔符 + result),由 tickSync 回传 */ private final List ackBuf = new ArrayList(); /** 目录快照节流计数(每 3 轮 tickSync 推一次全量) */ @@ -1999,7 +1999,7 @@ public class SnManager implements Listener { if (acks != null) { List> rows = new ArrayList>(); for (String a : acks) { - int p = a.indexOf('\u0001'); + int p = a.indexOf(UnicodeChars.SEPARATOR); if (p <= 0) continue; Map m = new LinkedHashMap(); m.put("id", Long.parseLong(a.substring(0, p))); @@ -2187,7 +2187,7 @@ public class SnManager implements Listener { } if (ackId >= 0 && result != null) { synchronized (ackBuf) { - ackBuf.add(ackId + "\u0001" + result); + ackBuf.add(ackId + UnicodeChars.SEPARATOR_TEXT + result); } } return result == null ? "" : result; diff --git a/src/main/java/Sdf1_login/UnicodeChars.java b/src/main/java/Sdf1_login/UnicodeChars.java new file mode 100644 index 0000000..a21cda4 --- /dev/null +++ b/src/main/java/Sdf1_login/UnicodeChars.java @@ -0,0 +1,217 @@ +package Sdf1_login; + +/** + * Unicode 特殊字符常量表。 + * + *

背景:项目里原先大量使用 {@code \\uXXXX} 形式的 Unicode 转义字面量, + * 读代码的人必须逐个心算码位才能看懂(例如 {@code \\u00a7} 其实是 MC 颜色码 §)。 + * 尤其不可见字符(零宽空格、BOM、全角空格…)直接写成字面量后 + * 在编辑器里根本"看不见",反而更难排查。

+ * + *

因此本类把两类字符分开处理:

+ *
    + *
  • 可见字符(§、:、全角数字、汉字等)→ 直接在业务代码里写字面量, + * 一眼可读;
  • + *
  • 不可见字符(零宽空格、BOM、全角空格、U+0001 分隔符等)→ + * 统一引用本类的具名常量,既可读又不会因"看不见"而误判。
  • + *
+ * + *

注意:正则表达式字符串里的 {@code \\uXXXX} 属于正则层转义 + * (Java 字符串里存的是字面反斜杠+u,由正则引擎在运行期解释), + * 改写会改变正则语义,故保持原样不动。

+ */ +public final class UnicodeChars { + + private UnicodeChars() { + } + + // ==================== 不可见字符(必须用具名常量) ==================== + + /** U+0001 SOH —— SnManager 与 PHP 命令执行结果之间的分隔符 */ + public static final char SEPARATOR = '\u0001'; + + /** U+00A0 不间断空格(NO-BREAK SPACE) */ + public static final char NBSP = '\u00A0'; + + /** U+00AD 软连字符(SOFT HYPHEN) */ + public static final char SOFT_HYPHEN = '\u00AD'; + + /** U+2000 EN QUAD —— Unicode 各种空格区起点 */ + public static final char SPACE_U2000 = '\u2000'; + + /** U+200A HAIR SPACE —— Unicode 各种空格区终点 */ + public static final char SPACE_U200A = '\u200A'; + + /** U+200B 零宽空格(ZERO WIDTH SPACE) */ + public static final char ZERO_WIDTH_SPACE = '\u200B'; + + /** U+200C 零宽非连接符(ZERO WIDTH NON-JOINER) */ + public static final char ZERO_WIDTH_NON_JOINER = '\u200C'; + + /** U+200D 零宽连接符(ZERO WIDTH JOINER) */ + public static final char ZERO_WIDTH_JOINER = '\u200D'; + + /** U+200E 从左至右标记(LTR MARK) */ + public static final char LTR_MARK = '\u200E'; + + /** U+200F 从右至左标记(RTL MARK) */ + public static final char RTL_MARK = '\u200F'; + + /** U+2028 行分隔符(LINE SEPARATOR) */ + public static final char LINE_SEPARATOR = '\u2028'; + + /** U+2029 段分隔符(PARAGRAPH SEPARATOR) */ + public static final char PARAGRAPH_SEPARATOR = '\u2029'; + + /** U+2060 字连接符(WORD JOINER) */ + public static final char WORD_JOINER = '\u2060'; + + /** U+3000 表意空格(IDEOGRAPHIC SPACE,即"全角空格") */ + public static final char IDEOGRAPHIC_SPACE = '\u3000'; + + /** U+FEFF 零宽不换行空格 / BOM(ZERO WIDTH NO-BREAK SPACE) */ + public static final char BOM = '\uFEFF'; + + // ==================== 区间常量(不可见/边界判定用) ==================== + + /** CJK 基本汉字区起点 U+4E00(一) */ + public static final char CJK_START = '\u4E00'; + + /** CJK 基本汉字区终点 U+9FFF */ + public static final char CJK_END = '\u9FFF'; + + /** CJK 兼容汉字常用终点 U+9FA5(龥) */ + public static final char CJK_COMPAT_END = '\u9FA5'; + + /** CJK 扩展 A 区起点 U+3400(㐀) */ + public static final char CJK_EXT_A_START = '\u3400'; + + /** CJK 扩展 A 区终点 U+4DBF(䶿) */ + public static final char CJK_EXT_A_END = '\u4DBF'; + + /** 全角 ASCII 起点 U+FF01(!) */ + public static final char FULLWIDTH_START = '\uFF01'; + + /** 全角 ASCII 终点 U+FF5E(~) */ + public static final char FULLWIDTH_END = '\uFF5E'; + + /** 全角数字 0 U+FF10 */ + public static final char FULLWIDTH_ZERO = '\uFF10'; + + /** 全角数字 9 U+FF19 */ + public static final char FULLWIDTH_NINE = '\uFF19'; + + /** 全角大写 A U+FF21 */ + public static final char FULLWIDTH_A_UPPER = '\uFF21'; + + /** 全角大写 Z U+FF3A */ + public static final char FULLWIDTH_Z_UPPER = '\uFF3A'; + + /** 全角小写 a U+FF41 */ + public static final char FULLWIDTH_A_LOWER = '\uFF41'; + + /** 全角小写 z U+FF5A */ + public static final char FULLWIDTH_Z_LOWER = '\uFF5A'; + + /** 全角左括号 U+FF08 */ + public static final char FULLWIDTH_LPAREN = '\uFF08'; + + /** 全角右括号 U+FF09 */ + public static final char FULLWIDTH_RPAREN = '\uFF09'; + + /** 全角井号 U+FF03 */ + public static final char FULLWIDTH_HASH = '\uFF03'; + + /** 全角逗号 U+FF0C */ + public static final char FULLWIDTH_COMMA = '\uFF0C'; + + /** 全角分号 U+FF1B */ + public static final char FULLWIDTH_SEMICOLON = '\uFF1B'; + + /** 全角冒号 U+FF1A */ + public static final char FULLWIDTH_COLON = '\uFF1A'; + + /** 全角句点 U+FF0E */ + public static final char FULLWIDTH_STOP = '\uFF0E'; + + /** 全角波浪号 U+FF5E */ + public static final char FULLWIDTH_TILDE = '\uFF5E'; + + /** 半角片假名中点 U+FF65 */ + public static final char HALFWIDTH_KATAKANA_MIDDLE_DOT = '\uFF65'; + + /** 小型句点 U+FE52 */ + public static final char SMALL_FULL_STOP = '\uFE52'; + + /** 二点引导符 U+2025 */ + public static final char TWO_DOT_LEADER = '\u2025'; + + /** 水平省略号 U+2026 */ + public static final char HORIZONTAL_ELLIPSIS = '\u2026'; + + /** 中点 U+00B7 */ + public static final char MIDDLE_DOT = '\u00B7'; + + /** 项目符号 U+2022 */ + public static final char BULLET = '\u2022'; + + /** 表意句点 U+3002 */ + public static final char IDEOGRAPHIC_STOP = '\u3002'; + + /** 表意逗号 U+3001 */ + public static final char IDEOGRAPHIC_COMMA = '\u3001'; + + /** 表意数字零 U+3007(〇) */ + public static final char IDEOGRAPHIC_ZERO = '\u3007'; + + /** 左黑 lenticular 方括号 U+3010(【) */ + public static final char LEFT_BLACK_LENTICULAR = '\u3010'; + + /** 右黑 lenticular 方括号 U+3011(】) */ + public static final char RIGHT_BLACK_LENTICULAR = '\u3011'; + + /** 左 tortoise shell 括号 U+3014(〔) */ + public static final char LEFT_TORTOISE_SHELL = '\u3014'; + + /** 右 tortoise shell 括号 U+3015(〕) */ + public static final char RIGHT_TORTOISE_SHELL = '\u3015'; + + /** 左直角引号 U+300C(「) */ + public static final char LEFT_CORNER_BRACKET = '\u300C'; + + /** 右直角引号 U+300D」(」) */ + public static final char RIGHT_CORNER_BRACKET = '\u300D'; + + /** 左白直角引号 U+300E(『) */ + public static final char LEFT_WHITE_CORNER_BRACKET = '\u300E'; + + /** 右白直角引号 U+300F(』) */ + public static final char RIGHT_WHITE_CORNER_BRACKET = '\u300F'; + + // ==================== 常用字符串常量 ==================== + + /** MC 颜色码前缀 § */ + public static final String COLOR_SIGN = "§"; + + /** 全角冒号 ":" */ + public static final String WIDE_COLON = ":"; + + // ==================== 字符串版(用于拼接场景) ==================== + // 注意:char 常量参与字符串拼接时若两侧都是 char 会被当成数值加法, + // 因此需要拼接的不可见字符一律用这里的 String 版常量。 + + /** U+FEFF BOM 的字符串版 */ + public static final String BOM_TEXT = "\uFEFF"; + + /** U+0001 分隔符的字符串版 */ + public static final String SEPARATOR_TEXT = "\u0001"; + + /** U+3000 全角空格的字符串版 */ + public static final String IDEOGRAPHIC_SPACE_TEXT = "\u3000"; + + /** + * 零宽/不可见字符集合(聊天变形 URL 归一前剔除用)。 + * 含:零宽空格、零宽非连接、零宽连接、字连接符、BOM、软连字符。 + */ + public static final String INVISIBLE_CHARS = "\u200B\u200C\u200D\u2060\uFEFF\u00AD"; +} diff --git a/src/main/java/Sdf1_login/WebManager.java b/src/main/java/Sdf1_login/WebManager.java index beb3b06..56c104f 100644 --- a/src/main/java/Sdf1_login/WebManager.java +++ b/src/main/java/Sdf1_login/WebManager.java @@ -6000,7 +6000,7 @@ public class WebManager { } } - /** 解码JSON中的 \\uXXXX Unicode转义(如 \\u00a7 → §) */ + /** 解码JSON中的 Unicode 转义(\\uXXXX 形式,如 \\u00a7 → §) */ private String decodeJsonUnicode(String s) { if (s == null || s.isEmpty()) return s; if (!s.contains("\\u")) return s; diff --git a/src/main/java/Sdf1_login/WelcomeManager.java b/src/main/java/Sdf1_login/WelcomeManager.java index 68bd3ef..9bc50ac 100644 --- a/src/main/java/Sdf1_login/WelcomeManager.java +++ b/src/main/java/Sdf1_login/WelcomeManager.java @@ -41,7 +41,7 @@ public class WelcomeManager { String section = ""; String line; while ((line = r.readLine()) != null) { - if (line.startsWith("\uFEFF")) + if (line.startsWith(UnicodeChars.BOM_TEXT)) line = line.substring(1); String t = line.trim(); if (t.isEmpty() @@ -135,7 +135,7 @@ public class WelcomeManager { } private String colorize(String msg) { - return msg.replace("&", "\u00a7"); + return msg.replace("&", "§"); } private void writeDefault(File f) {