第41批:全量转译 \uXXXX 转义为可读字面量/UnicodeChars具名常量 + 修复IDE插坏代码

- 新增 UnicodeChars 常量表:不可见字符(零宽/BOM/全角空格/U+0001)统一用具名常量
- 可见字符直接写字面量(§、:、全角数字、汉字区间等),读代码无需心算码位
- 正则层转义(\\uXXXX)保持原样并补注释说明,避免改变正则语义
- 注释里的 \uXXXX 改双写(javac 预处理期生效,注释躲不掉)
- 修复IDE插坏的 ConfigManager/UnicodeChars 括号
This commit is contained in:
youpaishidifu
2026-10-22 12:17:04 +08:00
parent 14db0aa10c
commit ac64aee365
14 changed files with 287 additions and 69 deletions
+17 -16
View File
@@ -1301,7 +1301,8 @@ public class AreaProtection implements Listener {
List<String> result = new ArrayList<>(); List<String> result = new ArrayList<>();
if (s == null || s.isEmpty()) return result; if (s == null || s.isEmpty()) return result;
String trimmed = s.trim(); String trimmed = s.trim();
// ★ 支持JSON数组格式(PHP端存储格式),如 ["速度","急迫"] 或 ["\u8bd5\u70bc..."] // ★ 支持JSON数组格式(PHP端存储格式),如 ["速度","急迫"] 或 ["\\u8bd5\\u70bc..."]
// (PHP 端 JSON 存储时中文会被转义成 \\uXXXX 形式,此处展示的是 PHP 侧原样)
if (trimmed.startsWith("[") && trimmed.endsWith("]")) { if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
try { try {
String inner = trimmed.substring(1, trimmed.length() - 1); String inner = trimmed.substring(1, trimmed.length() - 1);
@@ -1821,27 +1822,27 @@ public class AreaProtection implements Listener {
char c = input.charAt(i); char c = input.charAt(i);
// 跳过零宽字符和不可见字符 // 跳过零宽字符和不可见字符
if (c == '\u200B' // 零宽空格 if (c == UnicodeChars.ZERO_WIDTH_SPACE // 零宽空格
|| c == '\u200C' // 零宽非连接 || c == UnicodeChars.ZERO_WIDTH_NON_JOINER // 零宽非连接
|| c == '\u200D' // 零宽连接 || c == UnicodeChars.ZERO_WIDTH_JOINER // 零宽连接
|| c == '\uFEFF' // BOM || c == UnicodeChars.BOM // BOM
|| c == '\u00A0' // 不间断空格 || c == UnicodeChars.NBSP // 不间断空格
|| c == '\u2028' // 行分隔符 || c == UnicodeChars.LINE_SEPARATOR // 行分隔符
|| c == '\u2029' // 段分隔符 || c == UnicodeChars.PARAGRAPH_SEPARATOR // 段分隔符
|| c == '\u200E' // LTR标记 || c == UnicodeChars.LTR_MARK // LTR标记
|| c == '\u200F' // RTL标记 || c == UnicodeChars.RTL_MARK // RTL标记
|| c == '\u2060' // 字连接符 || c == UnicodeChars.WORD_JOINER // 字连接符
|| (c >= '\u2000' && c <= '\u200A')) { // 各种空格 || (c >= UnicodeChars.SPACE_U2000 && c <= UnicodeChars.SPACE_U200A)) { // 各种空格
continue; continue;
} }
// 全角→半角映射 // 全角→半角映射
if (c >= '\uFF01' && c <= '\uFF5E') { if (c >= '!' && c <= '~') {
c = (char) (c - 0xFEE0); c = (char) (c - 0xFEE0);
} }
// 全角空格→半角空格 // 全角空格→半角空格
if (c == '\u3000') { if (c == UnicodeChars.IDEOGRAPHIC_SPACE) {
c = ' '; c = ' ';
} }
@@ -6930,7 +6931,7 @@ public class AreaProtection implements Listener {
} }
String n = name.trim(); String n = name.trim();
if (n.length() > 20) return "名字不能超过20个字符"; 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 "名字只能包含中英文、数字、下划线和连字符"; return "名字只能包含中英文、数字、下划线和连字符";
} }
if (n.equalsIgnoreCase("global")) { if (n.equalsIgnoreCase("global")) {
@@ -11470,7 +11471,7 @@ public class AreaProtection implements Listener {
String[] parts = inner.split("\""); String[] parts = inner.split("\"");
java.util.List<String> result = new java.util.ArrayList<>(); java.util.List<String> result = new java.util.ArrayList<>();
for (String part : parts) { 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); if (!trimmed.isEmpty()) result.add(trimmed);
} }
return result.toArray(new String[0]); return result.toArray(new String[0]);
@@ -681,18 +681,18 @@ public class ChatFilterManager {
+ "[_.][\\p{L}]{2,}"); + "[_.][\\p{L}]{2,}");
private static final Pattern DOT_VARIANTS = private static final Pattern DOT_VARIANTS =
Pattern.compile( Pattern.compile(
"[\uff0e\u3002\u2025\u2026\u00b7\uff65\ufe52]"); "[.。‥…·・﹒]");
/** 零宽/不可见字符:变形URL归一前剔除(如 baidu.<零宽空格>com) */ /** 零宽/不可见字符:变形URL归一前剔除(如 baidu.<零宽空格>com) */
private static final Pattern INVISIBLE = private static final Pattern INVISIBLE =
Pattern.compile( Pattern.compile(
"[\u200b\u200c\u200d\u2060\ufeff\u00ad]"); UnicodeChars.INVISIBLE_CHARS + "]");
/** 点周带空白的变形域名:baidu . com(2026-10-08 加强拦截) */ /** 点周带空白的变形域名:baidu . com(2026-10-08 加强拦截) */
private static final Pattern SPACED_DOT_URL = private static final Pattern SPACED_DOT_URL =
Pattern.compile( Pattern.compile(
"(?i)(?:[\\p{L}0-9](?:[\\p{L}0-9\\-]*[\\p{L}0-9])?[\\s\\u3000]*\\.)+[\\s\\u3000]*[\\p{L}]{2,}"); "(?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 = private static final Pattern COLOR_CODES =
Pattern.compile( Pattern.compile(
"\u00a7[0-9a-fk-orA-FK-OR]"); "§[0-9a-fk-orA-FK-OR]");
private static final Set<String> FILE_EXT; private static final Set<String> FILE_EXT;
static { static {
@@ -747,7 +747,7 @@ public class ChatFilterManager {
String section = ""; String section = "";
String line; String line;
while ((line = r.readLine()) != null) { while ((line = r.readLine()) != null) {
if (line.startsWith("\uFEFF")) if (line.startsWith(UnicodeChars.BOM_TEXT))
line = line.substring(1); line = line.substring(1);
String t = line.trim(); String t = line.trim();
@@ -1058,8 +1058,8 @@ public class ChatFilterManager {
while (m2.find()) { while (m2.find()) {
String token = m2.group(); String token = m2.group();
if (token.indexOf(' ') < 0 && token.indexOf('\t') < 0 if (token.indexOf(' ') < 0 && token.indexOf('\t') < 0
&& token.indexOf('\u3000') < 0) continue; // 无空白 = 常规域名,上面已提取 && token.indexOf(UnicodeChars.IDEOGRAPHIC_SPACE) < 0) continue; // 无空白 = 常规域名,上面已提取
String compact = token.replaceAll("[\\s\u3000]+", "").toLowerCase(); String compact = token.replaceAll("[\\s" + UnicodeChars.IDEOGRAPHIC_SPACE + "]+", "").toLowerCase();
int dot = compact.lastIndexOf('.'); int dot = compact.lastIndexOf('.');
if (dot < 0) continue; if (dot < 0) continue;
if (!illegalDomainSuffixes.contains(compact.substring(dot + 1))) continue; if (!illegalDomainSuffixes.contains(compact.substring(dot + 1))) continue;
+9 -9
View File
@@ -827,7 +827,7 @@ public class ConfigManager {
// 处理UTF-8 BOM头 // 处理UTF-8 BOM头
if (firstLine) { if (firstLine) {
firstLine = false; firstLine = false;
if (line.startsWith("\uFEFF")) { if (line.startsWith(UnicodeChars.BOM_TEXT)) {
line = line.substring(1); line = line.substring(1);
} }
} }
@@ -1601,7 +1601,7 @@ public class ConfigManager {
/** 字节数组 → 行列表(UTF-8,去 BOM,去 CR,去文件末空行) */ /** 字节数组 → 行列表(UTF-8,去 BOM,去 CR,去文件末空行) */
private static List<String> readUtf8Lines(byte[] data) { private static List<String> readUtf8Lines(byte[] data) {
String s = new String(data, StandardCharsets.UTF_8); 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<String> L = new ArrayList<>(); List<String> L = new ArrayList<>();
for (String line : s.split("\n", -1)) { for (String line : s.split("\n", -1)) {
if (line.endsWith("\r")) line = line.substring(0, line.length() - 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) { private boolean upgradeJsonMissing(File file, byte[] tplBytes, String label) {
try { try {
String tplText = new String(tplBytes, StandardCharsets.UTF_8); 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()); byte[] cur = java.nio.file.Files.readAllBytes(file.toPath());
String oldText = new String(cur, StandardCharsets.UTF_8); 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; com.google.gson.JsonElement tplEl;
try { try {
tplEl = com.google.gson.JsonParser.parseString(tplText); tplEl = com.google.gson.JsonParser.parseString(tplText);
@@ -1796,7 +1796,7 @@ public class ConfigManager {
boolean hasChinese = false; boolean hasChinese = false;
for (int ci = 0; ci < testTrimmed.length(); ci++) { for (int ci = 0; ci < testTrimmed.length(); ci++) {
char cc = testTrimmed.charAt(ci); char cc = testTrimmed.charAt(ci);
if (cc >= '\u4e00' && cc <= '\u9fff') { hasChinese = true; break; } if (cc >= '一' && cc <= '鿿') { hasChinese = true; break; }
} }
if (hasChinese) { if (hasChinese) {
int cnResult = parseChineseTime(testTrimmed); int cnResult = parseChineseTime(testTrimmed);
@@ -1877,7 +1877,7 @@ public class ConfigManager {
private int parseChineseTime(String raw) { private int parseChineseTime(String raw) {
// 中文数字映射(含繁体/大写/各种变体) // 中文数字映射(含繁体/大写/各种变体)
java.util.Map<String, Integer> cnDigit = new java.util.LinkedHashMap<>(); java.util.Map<String, Integer> 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("一", 1); cnDigit.put("壹", 1); cnDigit.put("幺", 1);
cnDigit.put("二", 2); cnDigit.put("贰", 2); cnDigit.put("貳", 2); cnDigit.put("两", 2); cnDigit.put("弐", 2); cnDigit.put("二", 2); cnDigit.put("贰", 2); cnDigit.put("貳", 2); cnDigit.put("两", 2); cnDigit.put("弐", 2);
cnDigit.put("三", 3); cnDigit.put("叁", 3); cnDigit.put("參", 3); cnDigit.put("三", 3); cnDigit.put("叁", 3); cnDigit.put("參", 3);
@@ -1929,9 +1929,9 @@ public class ConfigManager {
int i = 0; int i = 0;
while (i < s.length()) { while (i < s.length()) {
char c = s.charAt(i); char c = s.charAt(i);
if (c >= '\u4e00' && c <= '\u9fff') { if (c >= '一' && c <= '鿿') {
// 尝试2字符匹配(先匹配单位复合词) // 尝试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); String two = s.substring(i, i+2);
boolean matchedUnit = false; boolean matchedUnit = false;
for (var entry : unitsList) { for (var entry : unitsList) {
@@ -1987,7 +1987,7 @@ public class ConfigManager {
// 检查是否是多字符中文数字组合(如"十五") // 检查是否是多字符中文数字组合(如"十五")
boolean isMultiCharNum = false; boolean isMultiCharNum = false;
for (String t : tokens) { 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); int val = evalChineseCompoundNum(tok, cnDigit, multipliers);
if (val >= 0) { if (val >= 0) {
+9 -9
View File
@@ -399,7 +399,7 @@ public class GUIManager implements Listener {
public void openSubMenu( public void openSubMenu(
Player p, String fileName) { Player p, String fileName) {
// ★ 剥离MC颜色代码(§f、§7等),防止污染文件名 // ★ 剥离MC颜色代码(§f、§7等),防止污染文件名
fileName = fileName.replaceAll("\u00A7[0-9a-fk-or]", "").trim(); fileName = fileName.replaceAll("§[0-9a-fk-or]", "").trim();
String menuTitle = String menuTitle =
"§6§l" + fileName "§6§l" + fileName
.replace(".txt", ""); .replace(".txt", "");
@@ -632,7 +632,7 @@ public class GUIManager implements Listener {
for (String line : block.split("\n")) { for (String line : block.split("\n")) {
line = line.trim(); line = line.trim();
String v = ""; String v = "";
int idx = line.indexOf('\uff1a'); int idx = line.indexOf(':');
if (idx < 0) if (idx < 0)
idx = line.indexOf(':'); idx = line.indexOf(':');
if (idx >= 0) { if (idx >= 0) {
@@ -1766,13 +1766,13 @@ public class GUIManager implements Listener {
oldName = "自定义物品"; oldName = "自定义物品";
} }
mm.setDisplayName( mm.setDisplayName(
"\u00a7e\u00a7l[菜单] \u00a7f" "§e§l[菜单] §f"
+ oldName); + oldName);
List<String> lore = mm.hasLore() List<String> lore = mm.hasLore()
? new ArrayList<>(mm.getLore()) ? new ArrayList<>(mm.getLore())
: new ArrayList<>(); : new ArrayList<>();
lore.add(0, lore.add(0,
"\u00a77右键打开主菜单"); "§7右键打开主菜单");
mm.setLore(lore); mm.setLore(lore);
mm.getPersistentDataContainer().set( mm.getPersistentDataContainer().set(
new org.bukkit.NamespacedKey( new org.bukkit.NamespacedKey(
@@ -1877,7 +1877,7 @@ public class GUIManager implements Listener {
List<String> lore = meta.getLore(); List<String> lore = meta.getLore();
if (lore == null) return false; if (lore == null) return false;
for (String line : lore) { for (String line : lore) {
if (line.contains("\u00a70\u00a7k") if (line.contains("§0§k")
|| line.contains("CUSTOM")) { || line.contains("CUSTOM")) {
return true; return true;
} }
@@ -1986,7 +1986,7 @@ public class GUIManager implements Listener {
&& im.getLore() != null) { && im.getLore() != null) {
for (String ln : im.getLore()) { for (String ln : im.getLore()) {
if (ln.contains( if (ln.contains(
"\u00a78")) { "§8")) {
p.getInventory() p.getInventory()
.setItem(i, .setItem(i,
null); null);
@@ -2010,7 +2010,7 @@ public class GUIManager implements Listener {
return true; return true;
ItemMeta im = item.getItemMeta(); ItemMeta im = item.getItemMeta();
if (im == null) return true; if (im == null) return true;
if ("\u00a77默认菜单(雪球)" if ("§7默认菜单(雪球)"
.equals(im.getDisplayName())) .equals(im.getDisplayName()))
return true; return true;
if (im.hasLore() && im.getLore() != null if (im.hasLore() && im.getLore() != null
@@ -2040,7 +2040,7 @@ public class GUIManager implements Listener {
&& im.hasLore() && im.hasLore()
&& im.getLore() != null) { && im.getLore() != null) {
for (String ln : im.getLore()) { for (String ln : im.getLore()) {
if (ln.contains("\u00a77右键打开主菜单")) if (ln.contains("§7右键打开主菜单"))
return true; return true;
} }
} }
@@ -2053,7 +2053,7 @@ public class GUIManager implements Listener {
return true; return true;
ItemMeta im = item.getItemMeta(); ItemMeta im = item.getItemMeta();
if (im == null) return true; if (im == null) return true;
if ("\u00a77默认菜单(雪球)" if ("§7默认菜单(雪球)"
.equals(im.getDisplayName())) { .equals(im.getDisplayName())) {
return true; return true;
} }
+1 -1
View File
@@ -59,7 +59,7 @@ public class GiftManager {
|| line.contains("任务内容")) { || line.contains("任务内容")) {
int colon = line.indexOf(':'); int colon = line.indexOf(':');
if (colon < 0) if (colon < 0)
colon = line.indexOf('\uff1a'); colon = line.indexOf(':');
if (colon >= 0) { if (colon >= 0) {
rewards.add( rewards.add(
line.substring(colon + 1) line.substring(colon + 1)
+11 -11
View File
@@ -3537,7 +3537,7 @@ public class Main extends JavaPlugin
originalName = "自定义物品"; originalName = "自定义物品";
} }
cm.setDisplayName( cm.setDisplayName(
"\u00a7e\u00a7l[菜单] \u00a7f" "§e§l[菜单] §f"
+ originalName); + originalName);
List<String> cmLore = List<String> cmLore =
cm.hasLore() cm.hasLore()
@@ -3546,9 +3546,9 @@ public class Main extends JavaPlugin
// 避免重复添加 // 避免重复添加
if (cmLore.isEmpty() if (cmLore.isEmpty()
|| !cmLore.get(0).contains( || !cmLore.get(0).contains(
"\u00a77右键打开主菜单")) { "§7右键打开主菜单")) {
cmLore.add(0, cmLore.add(0,
"\u00a77右键打开主菜单"); "§7右键打开主菜单");
} }
cm.setLore(cmLore); cm.setLore(cmLore);
customIcon.setItemMeta(cm); customIcon.setItemMeta(cm);
@@ -3575,10 +3575,10 @@ public class Main extends JavaPlugin
ItemMeta im = snow.getItemMeta(); ItemMeta im = snow.getItemMeta();
if (im != null) { if (im != null) {
im.setDisplayName( im.setDisplayName(
"\u00a7e\u00a7l[菜单] \u00a7f右键打开主菜单"); "§e§l[菜单] §f右键打开主菜单");
List<String> lore = new ArrayList<>(); List<String> lore = new ArrayList<>();
lore.add("\u00a77右键点击打开功能菜单"); lore.add("§7右键点击打开功能菜单");
lore.add("\u00a78" + MENU_SNOWBALL_TAG); lore.add("§8" + MENU_SNOWBALL_TAG);
im.setLore(lore); im.setLore(lore);
snow.setItemMeta(im); snow.setItemMeta(im);
} }
@@ -3767,7 +3767,7 @@ public class Main extends JavaPlugin
// Sdf1_game宝箱物品标记格式: §0§kCUSTOM|玩家|... // Sdf1_game宝箱物品标记格式: §0§kCUSTOM|玩家|...
// 同时检查两种标记格式 // 同时检查两种标记格式
if (line.contains("CUSTOM") if (line.contains("CUSTOM")
|| line.contains("\u00a70\u00a7k")) || line.contains("§0§k"))
return true; return true;
} }
return false; return false;
@@ -5366,7 +5366,7 @@ public class Main extends JavaPlugin
if (raw == null || raw.isEmpty()) if (raw == null || raw.isEmpty())
return Component.text("管理员", NamedTextColor.WHITE); return Component.text("管理员", NamedTextColor.WHITE);
// 先剥掉可能混进来的 § 原生色码,避免与 & 转换叠加出乱码 // 先剥掉可能混进来的 § 原生色码,避免与 & 转换叠加出乱码
String s = raw.replace("\u00a7", "&"); String s = raw.replace("§", "&");
// 压掉换行与制表,保证单行 // 压掉换行与制表,保证单行
s = s.replaceAll("[\\r\\n\\t]+", " ").trim(); s = s.replaceAll("[\\r\\n\\t]+", " ").trim();
return LegacyComponentSerializer.legacyAmpersand() return LegacyComponentSerializer.legacyAmpersand()
@@ -6569,7 +6569,7 @@ public class Main extends JavaPlugin
boolean hasChinese = false; boolean hasChinese = false;
for (int i = 0; i < text.length(); i++) { for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i); char c = text.charAt(i);
if (c >= '\u4e00' && c <= '\u9fff') { hasChinese = true; break; } if (c >= '一' && c <= '鿿') { hasChinese = true; break; }
} }
if (hasChinese) return; if (hasChinese) return;
// 触发口径(35批修订2):整条消息必须包含 ≥3 个英文字母才机翻 —— // 触发口径(35批修订2):整条消息必须包含 ≥3 个英文字母才机翻 ——
@@ -7378,7 +7378,7 @@ public class Main extends JavaPlugin
String cmd = subLn String cmd = subLn
.replace("§7指令: §f", "") .replace("§7指令: §f", "")
.trim() .trim()
.replaceAll("\u00A7[0-9a-fk-or]", ""); // 剥离残留MC颜色代码 .replaceAll("§[0-9a-fk-or]", ""); // 剥离残留MC颜色代码
if (cmd.isEmpty() if (cmd.isEmpty()
|| cmd.equals("/") || cmd.equals("/")
|| cmd.equals("null")) { || cmd.equals("null")) {
@@ -7672,7 +7672,7 @@ public class Main extends JavaPlugin
if (ln.contains("§7指令: §f")) { if (ln.contains("§7指令: §f")) {
String cmd = ln.replace( String cmd = ln.replace(
"§7指令: §f", "").trim() "§7指令: §f", "").trim()
.replaceAll("\u00A7[0-9a-fk-or]", ""); // 剥离残留MC颜色代码 .replaceAll("§[0-9a-fk-or]", ""); // 剥离残留MC颜色代码
if (cmd.isEmpty() if (cmd.isEmpty()
|| cmd.equals("/") || cmd.equals("/")
|| cmd.equals("null")) { || cmd.equals("null")) {
@@ -790,9 +790,9 @@ public class MaintenanceManager implements Listener {
if (s.isEmpty()) return; if (s.isEmpty()) return;
if (note != null) warn(log, note + ": " + raw); if (note != null) warn(log, note + ": " + raw);
// 仍坚持「一行一个玩家名」:整行收录,但格式不规范必须能被看见 // 仍坚持「一行一个玩家名」:整行收录,但格式不规范必须能被看见
if (s.indexOf(',') >= 0 || s.indexOf('\uFF0C') >= 0 || s.indexOf('\u3001') >= 0 if (s.indexOf(',') >= 0 || s.indexOf(',') >= 0 || s.indexOf('、') >= 0
|| s.indexOf(';') >= 0 || s.indexOf('\uFF1B') >= 0 || s.indexOf(' ') >= 0 || s.indexOf(';') >= 0 || s.indexOf(';') >= 0 || s.indexOf(' ') >= 0
|| s.indexOf('\u3000') >= 0 || s.indexOf('\t') >= 0) { || s.indexOf(UnicodeChars.IDEOGRAPHIC_SPACE) >= 0 || s.indexOf('\t') >= 0) {
warn(log, "白名单条目不符合「一行一个玩家名」,已按整行收录: " + s); warn(log, "白名单条目不符合「一行一个玩家名」,已按整行收录: " + s);
} }
wl.add(s); wl.add(s);
@@ -807,7 +807,7 @@ public class MaintenanceManager implements Listener {
/** 行内注释起点(# / # / //),返回 -1 表示没有 */ /** 行内注释起点(# / # / //),返回 -1 表示没有 */
private static int indexOfInlineComment(String s) { private static int indexOfInlineComment(String s) {
int h = s.indexOf('#'); int h = s.indexOf('#');
if (h < 0) h = s.indexOf('\uFF03'); if (h < 0) h = s.indexOf('#');
if (h < 0) h = s.indexOf("//"); if (h < 0) h = s.indexOf("//");
return h <= 0 ? -1 : h; return h <= 0 ? -1 : h;
} }
@@ -1517,6 +1517,6 @@ public class OpWhiteListManager implements Listener {
/** 日志里去掉颜色符号,避免控制台刷屏乱码 */ /** 日志里去掉颜色符号,避免控制台刷屏乱码 */
private static String stripColor(String s) { 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]", "");
} }
} }
@@ -1805,7 +1805,7 @@ public class PVPArenaManager implements Listener {
killerSession); 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); pvpDeaths.put(player.getName(), pvpDeaths.getOrDefault(player.getName(), 0) + 1);
// ★ 非击杀死亡也持久化(如摔死/溺水) // ★ 非击杀死亡也持久化(如摔死/溺水)
+5 -5
View File
@@ -2874,19 +2874,19 @@ public class ShopManager implements Listener {
for (int i = 0; i < s.length(); i++) { for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i); char c = s.charAt(i);
// 全角数字→半角 // 全角数字→半角
if (c >= '\uFF10' && c <= '\uFF19') { if (c >= '0' && c <= '9') {
sb.append((char)(c - 0xFF10 + '0')); sb.append((char)(c - 0xFF10 + '0'));
} }
// 全角字母→半角 // 全角字母→半角
else if (c >= '\uFF21' && c <= '\uFF3A') { else if (c >= 'A' && c <= 'Z') {
sb.append((char)(c - 0xFF21 + 'A')); sb.append((char)(c - 0xFF21 + 'A'));
} }
else if (c >= '\uFF41' && c <= '\uFF5A') { else if (c >= 'a' && c <= 'z') {
sb.append((char)(c - 0xFF41 + 'a')); sb.append((char)(c - 0xFF41 + 'a'));
} }
// ★ 保留中文字符(Unicode CJK统一汉字范围) // ★ 保留中文字符(Unicode CJK统一汉字范围)
else if ((c >= '\u4E00' && c <= '\u9FFF') else if ((c >= '一' && c <= '鿿')
|| (c >= '\u3400' && c <= '\u4DBF')) { || (c >= '㐀' && c <= '䶿')) {
sb.append(c); sb.append(c);
} }
// 保留半角字母和数字 // 保留半角字母和数字
+3 -3
View File
@@ -135,7 +135,7 @@ public class SnManager implements Listener {
/** 作废实物拦截提示限流(玩家名 -> 上次提示时间) */ /** 作废实物拦截提示限流(玩家名 -> 上次提示时间) */
private final Map<String, Long> deadMsgAt = new ConcurrentHashMap<>(); private final Map<String, Long> deadMsgAt = new ConcurrentHashMap<>();
/** PHP 命令执行结果缓冲(id + \u0001 + result),由 tickSync 回传 */ /** PHP 命令执行结果缓冲(id + U+0001 分隔符 + result),由 tickSync 回传 */
private final List<String> ackBuf = new ArrayList<String>(); private final List<String> ackBuf = new ArrayList<String>();
/** 目录快照节流计数(每 3 轮 tickSync 推一次全量) */ /** 目录快照节流计数(每 3 轮 tickSync 推一次全量) */
@@ -1999,7 +1999,7 @@ public class SnManager implements Listener {
if (acks != null) { if (acks != null) {
List<Map<String, Object>> rows = new ArrayList<Map<String, Object>>(); List<Map<String, Object>> rows = new ArrayList<Map<String, Object>>();
for (String a : acks) { for (String a : acks) {
int p = a.indexOf('\u0001'); int p = a.indexOf(UnicodeChars.SEPARATOR);
if (p <= 0) continue; if (p <= 0) continue;
Map<String, Object> m = new LinkedHashMap<String, Object>(); Map<String, Object> m = new LinkedHashMap<String, Object>();
m.put("id", Long.parseLong(a.substring(0, p))); m.put("id", Long.parseLong(a.substring(0, p)));
@@ -2187,7 +2187,7 @@ public class SnManager implements Listener {
} }
if (ackId >= 0 && result != null) { if (ackId >= 0 && result != null) {
synchronized (ackBuf) { synchronized (ackBuf) {
ackBuf.add(ackId + "\u0001" + result); ackBuf.add(ackId + UnicodeChars.SEPARATOR_TEXT + result);
} }
} }
return result == null ? "" : result; return result == null ? "" : result;
+217
View File
@@ -0,0 +1,217 @@
package Sdf1_login;
/**
* Unicode 特殊字符常量表。
*
* <p>背景:项目里原先大量使用 {@code \\uXXXX} 形式的 Unicode 转义字面量,
* 读代码的人必须逐个心算码位才能看懂(例如 {@code \\u00a7} 其实是 MC 颜色码 §)。
* 尤其不可见字符(零宽空格、BOM、全角空格…)直接写成字面量后
* 在编辑器里根本"看不见",反而更难排查。</p>
*
* <p>因此本类把两类字符分开处理:</p>
* <ul>
* <li><b>可见字符</b>(§、:、全角数字、汉字等)→ 直接在业务代码里写字面量,
* 一眼可读;</li>
* <li><b>不可见字符</b>(零宽空格、BOM、全角空格、U+0001 分隔符等)→
* 统一引用本类的具名常量,既可读又不会因"看不见"而误判。</li>
* </ul>
*
* <p>注意:正则表达式字符串里的 {@code \\uXXXX} 属于<b>正则层</b>转义
* (Java 字符串里存的是字面反斜杠+u,由正则引擎在运行期解释),
* 改写会改变正则语义,故保持原样不动。</p>
*/
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";
}
+1 -1
View File
@@ -6000,7 +6000,7 @@ public class WebManager {
} }
} }
/** 解码JSON中的 \\uXXXX Unicode转义(如 \\u00a7 → §) */ /** 解码JSON中的 Unicode 转义(\\uXXXX 形式,如 \\u00a7 → §) */
private String decodeJsonUnicode(String s) { private String decodeJsonUnicode(String s) {
if (s == null || s.isEmpty()) return s; if (s == null || s.isEmpty()) return s;
if (!s.contains("\\u")) return s; if (!s.contains("\\u")) return s;
+2 -2
View File
@@ -41,7 +41,7 @@ public class WelcomeManager {
String section = ""; String section = "";
String line; String line;
while ((line = r.readLine()) != null) { while ((line = r.readLine()) != null) {
if (line.startsWith("\uFEFF")) if (line.startsWith(UnicodeChars.BOM_TEXT))
line = line.substring(1); line = line.substring(1);
String t = line.trim(); String t = line.trim();
if (t.isEmpty() if (t.isEmpty()
@@ -135,7 +135,7 @@ public class WelcomeManager {
} }
private String colorize(String msg) { private String colorize(String msg) {
return msg.replace("&", "\u00a7"); return msg.replace("&", "§");
} }
private void writeDefault(File f) { private void writeDefault(File f) {