feat: Token泄露PHP联控作废 + 领地公告变量替换

1. Token安全防线:Java revokeToken异步调用PHP sync.php revoke_weblogin_token端点,从weblogin_tokens表删除
2. PHP新增revoke_weblogin_token action:验证SECRET_KEY后删除weblogin_tokens中的记录
3. 领地公告变量替换:支持(玩家)/(领地)及[]{}【】<>《》等括号格式
4. 默认消息:进入=欢迎(玩家)来到(领地),离开=感谢(玩家)光临(领地)
5. 公告管理菜单显示变量说明和支持的括号格式
This commit is contained in:
2026-07-12 17:43:23 +08:00
parent 0db237ed04
commit c0bb962a11
4 changed files with 61 additions and 26 deletions
+7 -6
View File
@@ -1625,8 +1625,8 @@ public class AreaCLIManager {
p.sendMessage(Component.text("§7配置玩家进出领地时的提示消息"));
p.sendMessage(Component.text("§7§l───────────────────────────────"));
// 显示当前进入消息
String enterMsg = (land.enterMsg != null && !land.enterMsg.isEmpty()) ? land.enterMsg : "§7(未设置)";
// 显示当前进入消息(含变量说明)
String enterMsg = (land.enterMsg != null && !land.enterMsg.isEmpty()) ? land.enterMsg : "§7(未设置,默认: 欢迎(玩家)来到(领地))";
p.sendMessage(Component.text("§a进入消息: §f" + enterMsg));
p.sendMessage(Component.empty()
.append(Component.text("§e[编辑进入消息] ")
@@ -1636,8 +1636,8 @@ public class AreaCLIManager {
p.sendMessage(Component.text(""));
// 显示当前离开消息
String leaveMsg = (land.leaveMsg != null && !land.leaveMsg.isEmpty()) ? land.leaveMsg : "§7(未设置)";
// 显示当前离开消息(含变量说明)
String leaveMsg = (land.leaveMsg != null && !land.leaveMsg.isEmpty()) ? land.leaveMsg : "§7(未设置,默认: 感谢(玩家)光临(领地))";
p.sendMessage(Component.text("§a离开消息: §f" + leaveMsg));
p.sendMessage(Component.empty()
.append(Component.text("§e[编辑离开消息] ")
@@ -1646,8 +1646,9 @@ public class AreaCLIManager {
.append(Component.text("§7点击编辑")));
p.sendMessage(Component.text("§7§l───────────────────────────────"));
p.sendMessage(Component.text("§7提示: 使用 §f&7§l颜色代码 §7格式化消息"));
p.sendMessage(Component.text("§7例如: §f&a欢迎来到 §6领地名"));
p.sendMessage(Component.text("§7变量: §f(玩家) §7= 玩家名, §f(领地) §7= 领地名"));
p.sendMessage(Component.text("§7支持括号: §f() [] {} 【】 <> 《》"));
p.sendMessage(Component.text("§7颜色: §f&a绿色 §6金色 §c红色 §7灰色"));
// 返回按钮
p.sendMessage(clickableAction("◀", "返回领地管理", "/protect cli manage " + landName));
+34 -18
View File
@@ -778,10 +778,26 @@ public class AreaProtection implements Listener {
* 4. 自动添加 [区域防护] 前缀
*/
private String formatAreaMsg(String raw) {
return formatAreaMsg(raw, null, null);
}
/**
* 格式化区域消息(支持变量替换)
* 支持括号: () [] {} 【】 <> 《》
*/
private String formatAreaMsg(String raw, String playerName, String landName) {
if (raw == null || raw.isEmpty()) return "";
String msg = raw;
// ★ 变量替换:支持多种括号格式
if (playerName != null) {
msg = msg.replaceAll("[((\\[【<《]玩家[))\\]】>》]", playerName);
}
if (landName != null) {
msg = msg.replaceAll("[((\\[【<《]领地[))\\]】>》]", landName);
}
// & → §
for (int i = 0; i <= 9; i++) {
msg = msg.replace("&" + i, "§" + i);
@@ -3028,10 +3044,11 @@ public class AreaProtection implements Listener {
AreaConfig oldAc = areas.get(oldArea);
// 离开提示
if (oldAc != null && oldAc.leaveMsg != null
&& !oldAc.leaveMsg.isEmpty()) {
p.sendMessage(formatAreaMsg(oldAc.leaveMsg));
// 离开提示(默认消息:感谢(玩家)光临(领地))
String leaveMsg = (oldAc != null && oldAc.leaveMsg != null && !oldAc.leaveMsg.isEmpty())
? oldAc.leaveMsg : "感谢(玩家)光临(领地)";
if (oldAc != null) {
p.sendMessage(formatAreaMsg(leaveMsg, p.getName(), oldArea));
}
// ★ 离开领地时展示边框粒子(3秒)
@@ -3147,11 +3164,10 @@ public class AreaProtection implements Listener {
return;
}
if (newAc.enterMsg != null
&& !newAc.enterMsg.isEmpty()) {
p.sendMessage(
formatAreaMsg(newAc.enterMsg));
}
// 进入提示(默认消息:欢迎(玩家)来到(领地))
String enterMsg = (newAc.enterMsg != null && !newAc.enterMsg.isEmpty())
? newAc.enterMsg : "欢迎(玩家)来到(领地)";
p.sendMessage(formatAreaMsg(enterMsg, p.getName(), newArea));
// ★ 进入领地时展示边框粒子(3秒)
showBorderBrief(p, newAc);
// ★ 公共建筑设施:访客自动获得传送、免疫伤害、攻击敌对生物权限
@@ -4212,10 +4228,11 @@ public class AreaProtection implements Listener {
}
// 离开提示
if (fromArea != null && fromArea.leaveMsg != null
&& !fromArea.leaveMsg.isEmpty()) {
p.sendMessage(formatAreaMsg(fromArea.leaveMsg));
// 离开提示(默认消息:感谢(玩家)光临(领地))
String leaveMsg2 = (fromArea != null && fromArea.leaveMsg != null && !fromArea.leaveMsg.isEmpty())
? fromArea.leaveMsg : "感谢(玩家)光临(领地)";
if (fromArea != null) {
p.sendMessage(formatAreaMsg(leaveMsg2, p.getName(), fromName));
}
// ★ 离开领地时展示边框粒子(3秒)
if (fromArea != null) {
@@ -4242,11 +4259,10 @@ public class AreaProtection implements Listener {
}
}
// 进入提示
if (toArea != null && toArea.enterMsg != null
&& !toArea.enterMsg.isEmpty()) {
p.sendMessage(formatAreaMsg(toArea.enterMsg));
}
// 进入提示(默认消息:欢迎(玩家)来到(领地))
String enterMsg2 = (toArea != null && toArea.enterMsg != null && !toArea.enterMsg.isEmpty())
? toArea.enterMsg : "欢迎(玩家)来到(领地)";
p.sendMessage(formatAreaMsg(enterMsg2, p.getName(), toName));
// ★ 进入领地时展示边框粒子(3秒)
if (toArea != null) {
+1 -1
View File
@@ -2964,7 +2964,7 @@ public class Main extends JavaPlugin
p.sendMessage("§c§l⚠ 安全警告:§e你刚才在公屏发送了有效的webtoken!");
p.sendMessage("§c该token已被立即作废,以防止他人盗用。");
p.sendMessage("§e请勿将token分享给任何人,token是你唯一的登录凭证。");
p.sendMessage("§7如需重新获取token,请执行 /weblogin");
p.sendMessage("§7如需重新获取token,请执行 /web");
}
}
}
+19 -1
View File
@@ -1173,12 +1173,30 @@ public class WebManager {
/**
* 作废指定Token(安全防线:公屏泄露时立即销毁)
* 同时联控PHP后端删除weblogin_tokens表中的记录
*
* @param token 要作废的token
* @return 如果token存在并被成功作废返回true
*/
public boolean revokeToken(String token) {
return tokenStore.remove(token) != null;
boolean removed = tokenStore.remove(token) != null;
// 异步联控PHP作废该token
if (removed) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
try {
String urlStr = webBaseUrl + "/api/sync.php?action=revoke_weblogin_token"
+ "&secret=" + java.net.URLEncoder.encode(secretKey, "UTF-8")
+ "&token=" + java.net.URLEncoder.encode(token, "UTF-8");
String response = doGet(urlStr);
plugin.getLogger().info("[安全防线] PHP联控作废token结果: " + response);
} catch (Exception e) {
plugin.getLogger().warning("[安全防线] PHP联控作废token失败: " + e.getMessage());
}
});
}
return removed;
}
/**