第十九批:封禁公示按播报起点过滤(推送带created+ban_cutoff、零发送首轮/起点变化强制推) + 播报定时器生命周期(全员下线停机玩家上线恢复,关闭≠停机)
This commit is contained in:
@@ -6,6 +6,9 @@ import org.bukkit.BanEntry;
|
||||
import org.bukkit.BanList;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
@@ -42,7 +45,7 @@ import java.util.regex.Pattern;
|
||||
* 表格值:由近到远 / 由远到近 / 0。
|
||||
* 间隔或模式为 0 → 关闭播报。
|
||||
*/
|
||||
public class BanBroadcastManager {
|
||||
public class BanBroadcastManager implements Listener {
|
||||
|
||||
private final Main plugin;
|
||||
private final Random rnd = new Random();
|
||||
@@ -60,11 +63,24 @@ public class BanBroadcastManager {
|
||||
private boolean startCutoffAuto = true; // 起点未配置 → 自动取插件数据目录创建时间(=插件安装时间)
|
||||
private long autoCutoffMs = -1; // 自动起点缓存(-1=未取)
|
||||
private boolean jsonSyntaxOk = false; // json 文本语法是否有效(区分「语法坏」与「语法好但值空」)
|
||||
private volatile boolean timerRunning = false; // 已有待触发的排程(防重复拉起)
|
||||
private volatile boolean stoppedByEmpty = false; // 全员下线导致的停机(等玩家上线恢复)
|
||||
private static BanBroadcastManager instance; // 单插件单实例,供 WebManager 取播报起点
|
||||
|
||||
public BanBroadcastManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
instance = this;
|
||||
}
|
||||
|
||||
/** 单例访问(WebManager 推送封禁时取播报起点) */
|
||||
public static BanBroadcastManager get() { return instance; }
|
||||
|
||||
/**
|
||||
* 播报起点(毫秒),0=不过滤。与游戏内播报同一套语义(配置的播报起点 / 自动=插件数据目录创建时间),
|
||||
* 供 PHP 端封禁公示过滤「插件上服务器之前的历史封禁」——两端同源,不各配一套。
|
||||
*/
|
||||
public long getStartCutoff() { return resolveStartCutoff(); }
|
||||
|
||||
// ==================== 生命周期 ====================
|
||||
|
||||
public void start() {
|
||||
@@ -74,6 +90,7 @@ public class BanBroadcastManager {
|
||||
ensureConfigExists();
|
||||
reloadConfig();
|
||||
scheduleNext();
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
long cut0 = resolveStartCutoff();
|
||||
plugin.getLogger().info("[封禁播报] 已启动,模式=" + modeName()
|
||||
+ " 间隔=" + minInterval + "~" + maxInterval + "s 每页" + pageSize + "条"
|
||||
@@ -82,6 +99,20 @@ public class BanBroadcastManager {
|
||||
|
||||
public void shutdown() { /* BukkitRunnable 随插件卸载自动取消 */ }
|
||||
|
||||
/**
|
||||
* ★ 第十九批:定时器生命周期 —— 停止条件只有两个:全员下线、服务器关机/插件卸载。
|
||||
* 配置为「关闭」不停定时器:只静默本轮,下一轮醒来重读配置,
|
||||
* 改回开启(或把间隔从 0 恢复)即自动续播,无需重启。
|
||||
* 全员下线时 scheduleNext 的空服分支停掉排程,第一个玩家上线由此重新拉起。
|
||||
* (start/runTaskLater/PlayerJoinEvent 都在主线程,状态无并发问题)
|
||||
*/
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent e) {
|
||||
if (!stoppedByEmpty || timerRunning) return;
|
||||
stoppedByEmpty = false;
|
||||
scheduleNext();
|
||||
}
|
||||
|
||||
// ==================== 配置:自愈 + 双模式解析 ====================
|
||||
|
||||
/** 找 ban/ 目录下任意 *.json 配置文件 */
|
||||
@@ -358,7 +389,8 @@ public class BanBroadcastManager {
|
||||
int delaySec;
|
||||
if (mode == 0) {
|
||||
// 播报关闭时仍每 60 秒自检一次:配置文件被删则自愈补发、
|
||||
// 运行中把 0 改回正常值可自动恢复播报(无需重启)
|
||||
// 运行中把 0 改回正常值可自动恢复播报(无需重启)。
|
||||
// ★ 关闭 ≠ 停机:定时器只在全员下线/关服时才停(第十九批)
|
||||
delaySec = 60;
|
||||
} else {
|
||||
// ★ 每次在[最小间隔,最大间隔]之间随机取一个秒数,不是固定值
|
||||
@@ -367,14 +399,17 @@ public class BanBroadcastManager {
|
||||
lastDelaySec = delaySec;
|
||||
}
|
||||
long delayTicks = Math.max(20L, delaySec * 20L);
|
||||
timerRunning = true;
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
timerRunning = false;
|
||||
if (!plugin.isEnabled()) return;
|
||||
// ★ 服务器没人就闭嘴:无人在线时不播报、不重载、不自愈、不打任何日志,
|
||||
// 直接排下一轮(没人时播报和日志打出来也没人看)
|
||||
// ★ 服务器没人就停机:全员下线是定时器的两个停止条件之一(另一个是关服),
|
||||
// 不播报、不重载、不自愈、不打任何日志,直接停排程 ——
|
||||
// 第一个玩家上线时 onPlayerJoin 重新拉起(配置为关闭同样只静默本轮)
|
||||
if (Bukkit.getOnlinePlayers().isEmpty()) {
|
||||
scheduleNext();
|
||||
stoppedByEmpty = true;
|
||||
return;
|
||||
}
|
||||
// ★ 懒重载:每次播报窗口重读配置 —— 开关决定这次报/不报,
|
||||
|
||||
@@ -321,6 +321,8 @@ public class WebManager {
|
||||
private String lastBondBalanceHash = "";
|
||||
private String lastLandDataHash = "";
|
||||
private String lastBansHash = "";
|
||||
private boolean bansCreatedPushed = false; // ★ 第十九批:进程内是否已把 created/起点推给PHP过一次
|
||||
private long lastPushedCutoff = -1; // 上次推送时的播报起点(变了就再强制推一轮)
|
||||
private String lastAdminsHash = "";
|
||||
private String lastUserRegistrationHash = "";
|
||||
|
||||
@@ -5564,10 +5566,13 @@ public class WebManager {
|
||||
String reason = entry.getReason() != null ? entry.getReason() : "";
|
||||
String source = entry.getSource() != null ? entry.getSource() : "";
|
||||
long expireDate = entry.getExpiration() != null ? entry.getExpiration().getTime() : 0;
|
||||
// ★ 第十九批:封禁创建时间(毫秒)一并下发 —— PHP 公示按它过滤插件上架前的历史封禁
|
||||
long createdDate = entry.getCreated() != null ? entry.getCreated().getTime() : 0;
|
||||
sb.append("{\"type\":\"name\",\"target\":\"").append(escapeJson(target))
|
||||
.append("\",\"reason\":\"").append(escapeJson(reason))
|
||||
.append("\",\"source\":\"").append(escapeJson(source))
|
||||
.append("\",\"expire\":").append(expireDate).append("}");
|
||||
.append("\",\"expire\":").append(expireDate)
|
||||
.append(",\"created\":").append(createdDate).append("}");
|
||||
hashBuilder.append("n:").append(target).append(":").append(reason).append(":").append(expireDate).append("|");
|
||||
}
|
||||
|
||||
@@ -5581,10 +5586,12 @@ public class WebManager {
|
||||
String reason = entry.getReason() != null ? entry.getReason() : "";
|
||||
String source = entry.getSource() != null ? entry.getSource() : "";
|
||||
long expireDate = entry.getExpiration() != null ? entry.getExpiration().getTime() : 0;
|
||||
long createdDate = entry.getCreated() != null ? entry.getCreated().getTime() : 0;
|
||||
sb.append("{\"type\":\"ip\",\"target\":\"").append(escapeJson(target))
|
||||
.append("\",\"reason\":\"").append(escapeJson(reason))
|
||||
.append("\",\"source\":\"").append(escapeJson(source))
|
||||
.append("\",\"expire\":").append(expireDate).append("}");
|
||||
.append("\",\"expire\":").append(expireDate)
|
||||
.append(",\"created\":").append(createdDate).append("}");
|
||||
hashBuilder.append("i:").append(target).append(":").append(reason).append(":").append(expireDate).append("|");
|
||||
}
|
||||
|
||||
@@ -5598,14 +5605,20 @@ public class WebManager {
|
||||
Set<org.bukkit.BanEntry<?>> ib0 = (Set<org.bukkit.BanEntry<?>>) (Set<?>) ipBanList.getEntries();
|
||||
for (org.bukkit.BanEntry<?> e0 : ib0) banRows.add(banRowOf(e0, "ip"));
|
||||
AlignResult arAlign = alignGate("bans", banRows);
|
||||
if (arAlign != null && !arAlign.needPush()) return; // 零发送
|
||||
// ★ 第十九批:created/ban_cutoff(PHP公示过滤历史封禁用)不进对账列,两端行一致时
|
||||
// alignGate 判「零发送」会把它们永远拦住 —— 进程内首轮 & 起点配置变化时强制整包推一轮;
|
||||
// 只绕过「零发送」,「本轮只收不发」保持原样(pull 后立即整包推会先把 PHP 多的行抹掉)。
|
||||
BanBroadcastManager bbCut = BanBroadcastManager.get();
|
||||
long cutoffNow = bbCut != null ? bbCut.getStartCutoff() : 0L;
|
||||
boolean forceCreatedPush = !bansCreatedPushed || cutoffNow != lastPushedCutoff;
|
||||
if (arAlign != null && !arAlign.needPush() && !forceCreatedPush) return; // 零发送
|
||||
if (arAlign != null && arAlign.pulled > 0) return; // 本轮只收不发
|
||||
|
||||
sb.append("]");
|
||||
String currentHash = hashBuilder.toString();
|
||||
// ★ 对账明说要推时本地 hash 不得拦截(同 admins,2026-10-06 灾备修复)
|
||||
boolean alignNeedsPushBans = arAlign != null && arAlign.needPush();
|
||||
if (!alignNeedsPushBans && currentHash.equals(lastBansHash)) {
|
||||
if (!alignNeedsPushBans && !forceCreatedPush && currentHash.equals(lastBansHash)) {
|
||||
plugin.getLogger().fine("[防护-sync] 封禁名单无变化,跳过");
|
||||
return;
|
||||
}
|
||||
@@ -5632,12 +5645,16 @@ public class WebManager {
|
||||
body.put("secret", secretKey);
|
||||
body.put("bans", sb.toString());
|
||||
body.put("ips", ipsb.toString());
|
||||
// ★ 第十九批:连同「播报起点」一起推给PHP(get_ban_board 用它过滤插件安装前的历史封禁)
|
||||
body.put("ban_cutoff", String.valueOf(cutoffNow));
|
||||
String resp = httpPost("api/sync.php?action=sync_bans", mapToJson(body));
|
||||
plugin.getLogger().info("[防护-sync] 封禁名单同步: " + (nameEntries.size() + ipEntries.size())
|
||||
+ "条(IP黑名单 " + ipOnlyEntries.size() + "个) → " + resp);
|
||||
// ★ 关键修复:hash 成功后才提交(防 404 时把 hash 写脏 → 永久漏推,2026-10-06)
|
||||
if (resp != null && resp.contains("\"success\":true")) {
|
||||
lastBansHash = currentHash;
|
||||
bansCreatedPushed = true;
|
||||
lastPushedCutoff = cutoffNow;
|
||||
} else {
|
||||
plugin.getLogger().warning("[防护-sync] 封禁名单同步未确认成功,下轮重试(hash 不提交)");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user