Files
MoonBridge/backend/src/main/java/dev/moonbridge/bukkit/MoonBridgeBackendPlugin.java
T
layue13andClaude Opus 5.5 3f8e14f54d fix: 修复代码审查发现的一批缺陷
- 控制通道:注册字段长度上限统一放在 messaging 的 ControlHandshakeLimits,后端启动时
  按代理的上限校验,不再出现后端接受、代理每次重连都拒绝的配置;后端按阶段记录连接、
  注册与断线失败(每类只记一次,不含对端数据),配置错误时输出具体缺失的键
- 转服登录:握手与 Login Start 的写入合并为 SessionChannels.writeBackendLogin,
  Login Start 无法编码时关闭通道释放已排队的握手,不再泄漏缓冲区
- 命令:插件提供的 CommandSource 的 player()/reply() 不再在宿主锁内调用
- 配置:插件条目没有设置时视为空设置而不是空指针;--validate-config 也检查同一后端名的
  密钥冲突;地址解析不再做 DNS,后端地址按目录使用的 URI 规则校验
- WebSocket:IPv4 映射写法(::ffff:a.b.c.d)的可信代理现在能匹配 IPv4 对端

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:40:57 +08:00

94 lines
4.6 KiB
Java

package dev.moonbridge.bukkit;
import dev.moonbridge.backendchannel.BackendChannelClient;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
/** One process-level authenticated transport shared by all dependent plugins. */
public final class MoonBridgeBackendPlugin extends JavaPlugin implements Listener {
private volatile boolean stopping;
private BackendChannelClient client;
private OwnedMessagingService service;
private OwnedBukkitSessionService sessions;
@Override public void onEnable() {
stopping = false;
saveDefaultConfig();
byte[] secret = null;
try {
String secretText = getConfig().getString("secret", "");
if (secretText.startsWith("replace-") || secretText.getBytes(StandardCharsets.UTF_8).length < 32) {
throw new IllegalArgumentException("Configure a unique secret of at least 32 UTF-8 bytes");
}
secret = secretText.getBytes(StandardCharsets.UTF_8);
client = new BackendChannelClient(
required("proxyHost"), getConfig().getInt("proxyPort", 28081),
required("instanceId"), required("backendName"), required("gameAddress"),
UUID.randomUUID().toString(), required("keyId"), secret);
service = new OwnedMessagingService(client::messaging, this::mainThreadExecutor);
sessions = new OwnedBukkitSessionService(required("backendName"), secret,
() -> client.isRegistered() ? client.getEpoch() : 0L,
() -> client.isRegistered() ? client.getProxyEpoch() : new UUID(0L, 0L));
getServer().getServicesManager().register(BukkitMessagingService.class, service, this, ServicePriority.Normal);
getServer().getServicesManager().register(BukkitSessionService.class, sessions, this, ServicePriority.Normal);
getServer().getPluginManager().registerEvents(this, this);
getServer().getPluginManager().registerEvents(sessions, this);
getLogger().info("Shared MoonBridge messaging service started; registration runs asynchronously");
} catch (IllegalArgumentException invalid) {
// The configuration checks above and in BackendChannelClient name the key, never its value.
getLogger().severe("Cannot start MoonBridge messaging: " + invalid.getMessage());
getServer().getPluginManager().disablePlugin(this);
} catch (RuntimeException failure) {
// Do not include configuration values or authentication peer data.
getLogger().severe("Cannot start MoonBridge messaging: " + failure.getClass().getSimpleName());
getServer().getPluginManager().disablePlugin(this);
} finally {
if (secret != null) Arrays.fill(secret, (byte) 0);
}
}
private String required(String key) {
String value = getConfig().getString(key, "").trim();
if (value.isEmpty()) throw new IllegalArgumentException("Missing configuration key: " + key);
return value;
}
private Executor mainThreadExecutor(final Plugin owner) {
return command -> {
if (stopping || !owner.isEnabled()) throw new RejectedExecutionException("Message owner is disabled");
try {
getServer().getScheduler().runTask(owner, () -> {
if (!stopping && owner.isEnabled()) command.run();
});
} catch (RuntimeException rejected) {
// A disable can race the enabled check. Preserve the Executor
// rejection contract so the dispatcher releases its admission slot.
throw new RejectedExecutionException("Bukkit scheduler rejected the message owner", rejected);
}
};
}
@EventHandler public void onPluginDisable(PluginDisableEvent event) {
OwnedMessagingService active = service;
if (active != null) active.release(event.getPlugin());
}
@Override public void onDisable() {
stopping = true;
getServer().getServicesManager().unregisterAll(this);
if (sessions != null) { sessions.close(); sessions = null; }
if (service != null) { service.close(); service = null; }
if (client != null) { client.close(); client = null; }
}
}