从if-else优雅拓展:在Minecraft插件中使用责任链模式
今年准备开服的时候,想继续沿用CaaMoe的MultiLogin混合登录方案,结果意外的发现他们的混合登录方案已经放弃对Bukkit的支持了,正好一直在学习SpigotAPI的开发,于是乎决定VibeCoding自己写一套混合登录方案
作为混合登录,有一个需要处理的点是,服务器经常不仅需要支持正版,还需要支持各种外置皮肤站Yggdrasil的登陆验证,这导致我们可能需要使用非常多的if-else判断语句,导致代码可读性和可维护性都很差,甚至导致玩家的登陆速度奇慢
所以我想到了一个责任链的方案,引入责任链模式 (Chain of Responsibility) ,优化认证逻辑
我在我的XiMultiLogin中定义了一个AuthProvider接口,这样每个认证源只需要实现自己的接口即可
/**
* 具体的 Mojang 验证节点
*/
public class MojangAuthProvider implements AuthProvider {
private final Object originalSessionService;
private final boolean enabled;
public MojangAuthProvider(Object originalSessionService, boolean enabled) {
this.originalSessionService = originalSessionService;
this.enabled = enabled;
}
@Override
public Object authenticate(String username, String serverId) {
// 调用 Minecraft 源码中的 originalSessionService 进行正版验证
// 这里通常涉及反射或直接调用底层映射的方法
try {
return ReflectionUtils.invoke(originalSessionService, "hasJoinedServer", username, serverId);
} catch (Exception e) {
return null; // 验证失败或网络波动返回 null,交给链条下一个节点
}
}
@Override public String getName() { return "MOJANG"; }
@Override public boolean isEnabled() { return enabled; }
}
/**
* 灵活的 Yggdrasil 验证节点(支持外部 API)
*/
public class YggdrasilAuthProvider implements AuthProvider {
private final String name;
private final String apiUrl;
private final boolean enabled;
public YggdrasilAuthProvider(String name, String apiUrl, boolean enabled) {
this.name = name;
this.apiUrl = apiUrl;
this.enabled = enabled;
}
@Override
public Object authenticate(String username, String serverId) {
// 伪代码:构造请求发送至第三方的验证服务器(如 LittleSkin / Blessing Skin)
// HttpClient.post(apiUrl + "/hasJoined", {user: username, id: serverId})
// 这种设计允许我们在不重启插件的情况下,只通过配置就接入无数个皮肤站
return YggdrasilClient.verify(this.apiUrl, username, serverId);
}
@Override public String getName() { return name; }
@Override public boolean isEnabled() { return enabled; }
}但传统的责任链有个问题,即他是逐个攻破,从第一个开始,第一个无法解决再交给第二个,这样会极大的影响玩家的登入效率,所以我采用了竞态并行的方法,让接口同时开始处理登录事件
/*** 异步并行调度核心
*/
private CompletableFuture<Object> tryAllProvidersAsync(String username, String serverId) {
// 1. 转换任务流
List<CompletableFuture<Object>> taskList = providers.stream()
.map(p -> CompletableFuture.supplyAsync(() -> {
try {
return p.authenticate(username, serverId);
} catch (Exception e) {
return null;
}
}, AUTH_EXECUTOR))
.collect(Collectors.toList());// 2. 等待聚合结果
return CompletableFuture.allOf(taskList.toArray(new CompletableFuture[0]))
.thenApply(v -> {
// 3. 这里的逻辑类似并行的“短路”:找到第一个不为 null 的成功结果
for (CompletableFuture<Object> future : taskList) {
Object result = future.getNow(null);
if (result != null) return result;
}
return null;
})
.thenCompose(finalResult -> {
// 4. 降级逻辑:如果全部失败且允许盗版
if (finalResult == null && configManager.isAllowCracked()) {
return CompletableFuture.completedFuture(createTemporaryProfile(username));
}
return CompletableFuture.completedFuture(finalResult);
});
}
如果在传统的责任链模式中,考虑到Mojang的神奇验证服务器经常挂掉,而Mojang验证又作为插件的第一个验证方式,极可能导致登陆验证长到离谱,用并行的方式可以迅速的知道哪些方式是可用的,响应速度取决于最快的节点,而不会被最慢的节点拖累
当然,我们还需要使用配置文件来动态调整所有的登录模式
/**
* 在 XiSessionService 中初始化责任链
*/
private void initializeProviders(ConfigManager configManager) {
List<ConfigManager.ProviderConfig> configs = configManager.getPipelineConfig();
for (ConfigManager.ProviderConfig cfg : configs) {
if (!cfg.isEnabled()) continue;AuthProvider provider = null;
// 使用简单的工厂逻辑创建节点
switch (cfg.getType().toUpperCase()) {
case "MOJANG":
provider = new MojangAuthProvider(originalSessionService, true);
break;
case "YGGDRASIL":
provider = new YggdrasilAuthProvider(cfg.getName(), cfg.getApiUrl(), true);
break;
}if (provider != null) {
this.providers.add(provider);
LOGGER.info("已加载认证节点: " + provider.getName());
}
}
}
在分布式系统中,验证延迟 $L$ 往往是整个链路的瓶颈。通过将串行 $L = \sum l_i$ 优化为并行 $L = \min(l_1, l_2, \dots, l_n)$,我们不仅提升了速度,更增强了系统的容错能力。 责任链模式与 Java 异步并发的结合,为 XiMultiLogin 提供了高度的灵活性。目前插件已能完美胜任多验证源混合登录的需求。代码已同步至我的 GitHub XiNian-dada/XiMultiLogin
评论 暂无