15 Commits
1.6.1 ... 1.6.4

25 changed files with 302 additions and 136 deletions

View File

@@ -1,6 +1,6 @@
# [HamsterBall](https://git.airgame.net/MiniDay/hamster-ball) # [HamsterBall](https://git.airgame.net/MiniDay/hamster-ball)
仓鼠球:一个基于 Redis 的 Minecraft 服务端通用消息中间件原HamsterService 基于 Redis 的 Minecraft 服务端通用消息中间件
该插件依赖于 [仓鼠核心](https://git.airgame.net/MiniDay/hamster-core) 该插件依赖于 [仓鼠核心](https://git.airgame.net/MiniDay/hamster-core)
@@ -49,9 +49,9 @@ repositories {
dependencies { dependencies {
// 对于 Bukkit 插件 // 对于 Bukkit 插件
compileOnly("cn.hamster3.mc.plugin:ball-bukkit:1.6.1") compileOnly("cn.hamster3.mc.plugin:ball-bukkit:1.6.4")
// 对于 BungeeCord 插件 // 对于 BungeeCord 插件
compileOnly("cn.hamster3.mc.plugin:ball-bungee:1.6.1") compileOnly("cn.hamster3.mc.plugin:ball-bungee:1.6.4")
} }
``` ```
@@ -77,13 +77,13 @@ dependencies {
<dependency> <dependency>
<groupId>cn.hamster3.mc.plugin</groupId> <groupId>cn.hamster3.mc.plugin</groupId>
<artifactId>ball-bukkit</artifactId> <artifactId>ball-bukkit</artifactId>
<version>1.6.1</version> <version>1.6.4</version>
</dependency> </dependency>
<!--对于 BungeeCord 插件--> <!--对于 BungeeCord 插件-->
<dependency> <dependency>
<groupId>cn.hamster3.mc.plugin</groupId> <groupId>cn.hamster3.mc.plugin</groupId>
<artifactId>ball-bungee</artifactId> <artifactId>ball-bungee</artifactId>
<version>1.6.1</version> <version>1.6.4</version>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -9,6 +9,7 @@ import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo; import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo;
import cn.hamster3.mc.plugin.ball.common.event.BallActions; import cn.hamster3.mc.plugin.ball.common.event.BallActions;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent; import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@@ -55,7 +56,8 @@ public class HamsterBallPlugin extends JavaPlugin {
StandardCopyOption.REPLACE_EXISTING StandardCopyOption.REPLACE_EXISTING
); );
} }
BallBukkitAPI.init(configFile); YamlConfig config = YamlConfig.load(configFile);
BallBukkitAPI.init(config);
logger.info("已初始化 BallAPI"); logger.info("已初始化 BallAPI");
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "BallAPI 初始化失败", e); logger.log(Level.SEVERE, "BallAPI 初始化失败", e);
@@ -98,7 +100,7 @@ public class HamsterBallPlugin extends JavaPlugin {
BallBukkitUtils.uploadPlayerInfo(playerInfo); BallBukkitUtils.uploadPlayerInfo(playerInfo);
}); });
} else { } else {
BallAPI.getInstance().subscribeIgnorePrefix(BallAPI.PLAYER_INFO_CHANNEL); BallAPI.getInstance().subscribeRaw(BallAPI.PLAYER_INFO_CHANNEL);
} }
sync(() -> { sync(() -> {
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {

View File

@@ -8,8 +8,6 @@ import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -22,11 +20,10 @@ public class BallBukkitAPI extends BallAPI {
return (BallBukkitAPI) instance; return (BallBukkitAPI) instance;
} }
public static void init(@NotNull File configFile) throws IOException { public static void init(@NotNull YamlConfig config) {
if (instance != null) { if (instance != null) {
return; return;
} }
YamlConfig config = YamlConfig.load(configFile);
instance = new BallBukkitAPI(config); instance = new BallBukkitAPI(config);
} }

View File

@@ -11,7 +11,7 @@ channel-prefix: ""
# 如果一个群组服同时拥有多个 BC 入口 # 如果一个群组服同时拥有多个 BC 入口
# 且每个 BC 入口为不同的玩家名称分配不同的 UUID # 且每个 BC 入口为不同的玩家名称分配不同的 UUID
# (例如正版、盗版双入口,或网易多入口接同一个子服) # (例如正版、盗版双入口,或网易多入口接同一个子服)
# 则可以启用该功能以防止 UUID 紊乱的问题 # 则可以启用该功能以防止同一个名称占用多个 UUID 的问题
game-server-update-player-info: false game-server-update-player-info: false
# 该选项仅在 game-server-update-player-info 为 true 时有效 # 该选项仅在 game-server-update-player-info 为 true 时有效
@@ -41,16 +41,18 @@ server-info:
# 如果你需要让每个服务器单独存储仓鼠球信息 # 如果你需要让每个服务器单独存储仓鼠球信息
# 这个选项就会很有用 # 这个选项就会很有用
#datasource: #datasource:
# # 数据库链接驱动地址 # # 数据库链接驱动地址旧版服务端低于1.13请使用com.mysql.jdbc.Driver
# driver: "com.mysql.jdbc.Driver" # driver: "com.mysql.cj.jdbc.Driver"
# # 数据库链接填写格式: # # MySQL数据库链接填写格式:
# # jdbc:mysql://{数据库地址}:{数据库端口}/{使用的库名}?参数 # # jdbc:mysql://{数据库地址}:{数据库端口}/{使用的库名}?参数
# # 除非你知道自己在做什么,否则不建议随意更改参数 # url: "jdbc:mysql://localhost:3306/Test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true"
# url: "jdbc:mysql://localhost:3306/Test1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true" # # 如果你不需要做多端跨服,那么请使用 sqlite 作本地数据库 ↓
# # driver: "org.sqlite.JDBC"
# # url: "jdbc:sqlite:./plugins/HamsterCore/database.db"
# # 用户名 # # 用户名
# username: "Test" # username: "root"
# # 密码 # # 密码
# password: "Test123.." # password: "Root123.."
# # 最小闲置链接数 # # 最小闲置链接数
# # 推荐值1~3 # # 推荐值1~3
# minimum-idle: 0 # minimum-idle: 0

View File

@@ -4,8 +4,15 @@ version: ${version}
api-version: 1.13 api-version: 1.13
author: MiniDay author: MiniDay
description: ${description}
website: https://git.airgame.net/MiniDay/hamster-ball website: https://git.airgame.net/MiniDay/hamster-ball
description: 仓鼠球:一个基于 Redis 的 Minecraft 服务端通用消息中间件原HamsterService
UPDATE_CHECKER:
VERSION: ${version}
CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-ball
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/
load: STARTUP load: STARTUP

View File

@@ -1,6 +0,0 @@
version: ${version}
CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-ball
GIT_TOKEN: a44a69a4d1b8601bf6091403247759cd28764d5e
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/

View File

@@ -3,12 +3,14 @@ package cn.hamster3.mc.plugin.ball.bungee;
import cn.hamster3.mc.plugin.ball.bungee.api.BallBungeeCordAPI; import cn.hamster3.mc.plugin.ball.bungee.api.BallBungeeCordAPI;
import cn.hamster3.mc.plugin.ball.bungee.listener.BallBungeeListener; import cn.hamster3.mc.plugin.ball.bungee.listener.BallBungeeListener;
import cn.hamster3.mc.plugin.ball.bungee.listener.BallBungeeMainListener; import cn.hamster3.mc.plugin.ball.bungee.listener.BallBungeeMainListener;
import cn.hamster3.mc.plugin.ball.bungee.listener.BungeeServerListener;
import cn.hamster3.mc.plugin.ball.bungee.listener.UpdatePlayerInfoListener; import cn.hamster3.mc.plugin.ball.bungee.listener.UpdatePlayerInfoListener;
import cn.hamster3.mc.plugin.ball.bungee.util.BallBungeeCordUtils; import cn.hamster3.mc.plugin.ball.bungee.util.BallBungeeCordUtils;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI; import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo; import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo;
import cn.hamster3.mc.plugin.ball.common.event.BallActions; import cn.hamster3.mc.plugin.ball.common.event.BallActions;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent; import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import lombok.Getter; import lombok.Getter;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
@@ -22,6 +24,8 @@ import java.util.logging.Logger;
public class HamsterBallPlugin extends Plugin { public class HamsterBallPlugin extends Plugin {
@Getter @Getter
private static HamsterBallPlugin instance; private static HamsterBallPlugin instance;
@Getter
private YamlConfig config;
@Override @Override
public void onLoad() { public void onLoad() {
@@ -42,7 +46,8 @@ public class HamsterBallPlugin extends Plugin {
StandardCopyOption.REPLACE_EXISTING StandardCopyOption.REPLACE_EXISTING
); );
} }
BallBungeeCordAPI.init(configFile); config = YamlConfig.load(configFile);
BallBungeeCordAPI.init(config);
logger.info("已初始化 BallAPI"); logger.info("已初始化 BallAPI");
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "BallAPI 初始化失败", e); logger.log(Level.SEVERE, "BallAPI 初始化失败", e);
@@ -72,11 +77,16 @@ public class HamsterBallPlugin extends Plugin {
logger.info("已注册监听器 BallBungeeMainListener"); logger.info("已注册监听器 BallBungeeMainListener");
ProxyServer.getInstance().getPluginManager().registerListener(this, UpdatePlayerInfoListener.INSTANCE); ProxyServer.getInstance().getPluginManager().registerListener(this, UpdatePlayerInfoListener.INSTANCE);
logger.info("已注册监听器 UpdatePlayerInfoListener"); logger.info("已注册监听器 UpdatePlayerInfoListener");
if (config.getBoolean("auto-register-game-server", false)) {
BallAPI.getInstance().getEventBus().register(BungeeServerListener.INSTANCE);
logger.info("已注册监听器 BungeeServerListener");
BungeeServerListener.INSTANCE.onEnable();
}
if (BallAPI.getInstance().getBallConfig().isGameServerUpdatePlayerInfo()) { if (BallAPI.getInstance().getBallConfig().isGameServerUpdatePlayerInfo()) {
BallAPI.getInstance().subscribePatterns("*" + BallAPI.PLAYER_INFO_CHANNEL); BallAPI.getInstance().subscribePatterns("*" + BallAPI.PLAYER_INFO_CHANNEL);
} else { } else {
BallAPI.getInstance().subscribeIgnorePrefix(BallAPI.PLAYER_INFO_CHANNEL); BallAPI.getInstance().subscribeRaw(BallAPI.PLAYER_INFO_CHANNEL);
} }
BallAPI.getInstance().sendRawBallMessage( BallAPI.getInstance().sendRawBallMessage(
BallAPI.BALL_CHANNEL, BallActions.ServerOnline.name(), BallAPI.BALL_CHANNEL, BallActions.ServerOnline.name(),

View File

@@ -9,8 +9,6 @@ import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.config.ListenerInfo;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -24,11 +22,10 @@ public class BallBungeeCordAPI extends BallAPI {
return (BallBungeeCordAPI) instance; return (BallBungeeCordAPI) instance;
} }
public static void init(@NotNull File configFile) throws IOException { public static void init(@NotNull YamlConfig config) {
if (instance != null) { if (instance != null) {
return; return;
} }
YamlConfig config = YamlConfig.load(configFile);
instance = new BallBungeeCordAPI(config); instance = new BallBungeeCordAPI(config);
} }

View File

@@ -0,0 +1,59 @@
package cn.hamster3.mc.plugin.ball.bungee.listener;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOfflineEvent;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent;
import com.google.common.eventbus.Subscribe;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import java.net.InetSocketAddress;
import java.util.Map;
public class BungeeServerListener {
public static final BungeeServerListener INSTANCE = new BungeeServerListener();
private BungeeServerListener() {
}
public void onEnable() {
for (BallServerInfo info : BallAPI.getInstance().getAllServerInfo().values()) {
if (info.getType() != BallServerType.GAME) {
continue;
}
ProxyServer.getInstance().getServers().put(info.getId(), getServerInfo(info));
BallAPI.getInstance().getLogger().info("已添加服务器入口: " + info.getId());
}
}
@Subscribe
public void onServerOnline(ServerOnlineEvent event) {
if (event.getType() != BallServerType.GAME) {
return;
}
ProxyServer.getInstance().getServers().put(event.getId(), getServerInfo(event));
BallAPI.getInstance().getLogger().info("已添加服务器入口: " + event.getId());
}
@Subscribe
public void onServerOffline(ServerOfflineEvent event) {
if (event.getType() != BallServerType.GAME) {
return;
}
Map<String, ServerInfo> map = ProxyServer.getInstance().getServers();
if (map.remove(event.getId()) != null) {
BallAPI.getInstance().getLogger().info("已移除服务器入口: " + event.getId());
}
}
private ServerInfo getServerInfo(BallServerInfo serverInfo) {
return ProxyServer.getInstance().constructServerInfo(
serverInfo.getId(),
new InetSocketAddress(serverInfo.getHost(), serverInfo.getPort()),
serverInfo.getName(),
false
);
}
}

View File

@@ -3,7 +3,15 @@ main: cn.hamster3.mc.plugin.ball.bungee.HamsterBallPlugin
version: ${version} version: ${version}
author: MiniDay author: MiniDay
description: 仓鼠球:一个基于 Redis 的 Minecraft 服务端通用消息中间件原HamsterService description: ${description}
website: https://git.airgame.net/MiniDay/hamster-ball
UPDATE_CHECKER:
VERSION: ${version}
CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-ball
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/
depend: depend:
- HamsterCore - HamsterCore

View File

@@ -8,12 +8,14 @@ channel-prefix: ""
# 是否在子服端更新玩家信息 # 是否在子服端更新玩家信息
# 默认情况下BC 统一管理玩家信息,包括记录 UUID 和玩家名称 # 默认情况下BC 统一管理玩家信息,包括记录 UUID 和玩家名称
# 如果一个群组服同时拥有多个 BC 入口 # 如果一个群组服同时拥有多个 BC 入口,且每个 BC 入口为不同的玩家名称分配不同的 UUID
# 且每个 BC 入口为不同的玩家名称分配不同的 UUID
# (例如正版、盗版双入口,或网易多入口接同一个子服) # (例如正版、盗版双入口,或网易多入口接同一个子服)
# 则可以启用该功能以防止 UUID 紊乱的问题 # 则可以启用该功能以防止同一个名称占用多个 UUID 的问题
game-server-update-player-info: false game-server-update-player-info: false
# 启用后,子服启动时会自动注册该子服的入口配置,关闭时也会自动移除该子服的入口配置
auto-register-game-server: false
# 本服务器信息 # 本服务器信息
server-info: server-info:
# 服务器唯一识别码,最长 32 字符 # 服务器唯一识别码,最长 32 字符
@@ -34,21 +36,20 @@ server-info:
# 这个选项就会很有用 # 这个选项就会很有用
#datasource: #datasource:
# # 数据库链接驱动地址 # # 数据库链接驱动地址
# driver: "com.mysql.jdbc.Driver" # driver: "com.mysql.cj.jdbc.Driver"
# # 数据库链接填写格式: # # MySQL数据库链接填写格式:
# # jdbc:mysql://{数据库地址}:{数据库端口}/{使用的库名}?参数 # # jdbc:mysql://{数据库地址}:{数据库端口}/{使用的库名}?参数
# # 除非你知道自己在做什么,否则不建议随意更改参数 # url: "jdbc:mysql://localhost:3306/Test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true"
# url: "jdbc:mysql://localhost:3306/Test1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true"
# # 用户名 # # 用户名
# username: "Test" # username: "root"
# # 密码 # # 密码
# password: "Test123.." # password: "Root123.."
# # 最小闲置链接数 # # 最小闲置链接数
# # 推荐值1~3 # # 推荐值1~3
# minimum-idle: 0 # minimum-idle: 0
# # 最大链接数 # # 最大链接数
# # 推荐值:不低于3 # # 推荐值:不低于5
# maximum-pool-size: 3 # maximum-pool-size: 5
# # 保持连接池可用的间隔 # # 保持连接池可用的间隔
# # 除非你的服务器数据库连接经常断开,否则不建议启用该选项 # # 除非你的服务器数据库连接经常断开,否则不建议启用该选项
# # 单位:毫秒 # # 单位:毫秒

View File

@@ -1,6 +0,0 @@
version: ${version}
CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-ball
GIT_TOKEN: a44a69a4d1b8601bf6091403247759cd28764d5e
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/

View File

@@ -73,7 +73,7 @@ public abstract class BallAPI {
@Nullable @Nullable
private ScheduledFuture<?> lockUpdater; private ScheduledFuture<?> lockUpdater;
public BallAPI(@NotNull ConfigSection config, BallServerType type) { public BallAPI(@NotNull ConfigSection config, @NotNull BallServerType type) {
ConfigSection serverInfoConfig = config.getSection("server-info"); ConfigSection serverInfoConfig = config.getSection("server-info");
if (serverInfoConfig == null) { if (serverInfoConfig == null) {
throw new IllegalArgumentException("配置文件中未找到 server-info 节点"); throw new IllegalArgumentException("配置文件中未找到 server-info 节点");
@@ -88,7 +88,7 @@ public abstract class BallAPI {
datasource = CoreAPI.getInstance().getDataSource(); datasource = CoreAPI.getInstance().getDataSource();
} }
ballConfig = new BallConfig(config); ballConfig = new BallConfig(config);
eventBus = new AsyncEventBus("HamsterBall - EventBus", CoreAPI.getInstance().getExecutorService()); eventBus = new AsyncEventBus("HamsterBall", CoreAPI.getInstance().getExecutorService());
eventBus.register(BallCommonListener.INSTANCE); eventBus.register(BallCommonListener.INSTANCE);
allServerInfo = new ConcurrentHashMap<>(); allServerInfo = new ConcurrentHashMap<>();
allPlayerInfo = new ConcurrentHashMap<>(); allPlayerInfo = new ConcurrentHashMap<>();
@@ -105,6 +105,7 @@ public abstract class BallAPI {
} }
protected void enable() throws SQLException, InterruptedException { protected void enable() throws SQLException, InterruptedException {
CoreAPI.getInstance().getExecutorService().submit(() -> redisSub.subscribe(BallRedisListener.INSTANCE, BALL_CHANNEL));
try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) { try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
String key = "HamsterBall:ServerInfo:" + localServerInfo.getId(); String key = "HamsterBall:ServerInfo:" + localServerInfo.getId();
if (jedis.exists(key)) { if (jedis.exists(key)) {
@@ -179,7 +180,6 @@ public abstract class BallAPI {
} }
getLogger().info("从数据库中加载了 " + allServerInfo.size() + " 条服务器信息"); getLogger().info("从数据库中加载了 " + allServerInfo.size() + " 条服务器信息");
getLogger().info("从数据库中加载了 " + allPlayerInfo.size() + " 条玩家信息"); getLogger().info("从数据库中加载了 " + allPlayerInfo.size() + " 条玩家信息");
subscribeIgnorePrefix(BALL_CHANNEL);
} }
protected void disable() throws SQLException, InterruptedException { protected void disable() throws SQLException, InterruptedException {
@@ -506,26 +506,29 @@ public abstract class BallAPI {
* <p> * <p>
* 会自动加上 config 中设置的频道前缀 * 会自动加上 config 中设置的频道前缀
* *
* @param channel 频道名称 * @param channels 频道名称
*/ */
public void subscribe(@NotNull String... channel) { public void subscribe(@NotNull String... channels) {
for (int i = 0; i < channel.length; i++) { for (int i = 0; i < channels.length; i++) {
channel[i] = ballConfig.getChannelPrefix() + channel[i]; channels[i] = ballConfig.getChannelPrefix() + channels[i];
} }
CoreAPI.getInstance().getExecutorService().submit( subscribeRaw(channels);
() -> redisSub.subscribe(BallRedisListener.INSTANCE, channel)
);
} }
/** /**
* 忽略频道前缀配置,订阅 redis 消息频道 * 忽略频道前缀配置,订阅 redis 消息频道
* *
* @param channel 频道名称 * @param channels 频道名称
*/ */
public void subscribeIgnorePrefix(@NotNull String... channel) { public void subscribeRaw(@NotNull String... channels) {
CoreAPI.getInstance().getExecutorService().submit( BallRedisListener.INSTANCE.subscribe(channels);
() -> redisSub.subscribe(BallRedisListener.INSTANCE, channel) // CoreAPI.getInstance().getExecutorService().submit(() -> {
); // try {
// redisSub.subscribe(BallRedisListener.INSTANCE, channels);
// } catch (Exception | Error e) {
// e.printStackTrace();
// }
// });
} }
/** /**
@@ -534,9 +537,10 @@ public abstract class BallAPI {
* @param patterns 频道名称正则表达式 * @param patterns 频道名称正则表达式
*/ */
public void subscribePatterns(@NotNull String patterns) { public void subscribePatterns(@NotNull String patterns) {
CoreAPI.getInstance().getExecutorService().submit( BallRedisListener.INSTANCE.psubscribe(patterns);
() -> redisSub.psubscribe(BallRedisListener.INSTANCE, patterns) // CoreAPI.getInstance().getExecutorService().submit(
); // () -> redisSub.psubscribe(BallRedisListener.INSTANCE, patterns)
// );
} }
/** /**
@@ -544,22 +548,22 @@ public abstract class BallAPI {
* <p> * <p>
* 会自动加上 config 中设置的频道前缀 * 会自动加上 config 中设置的频道前缀
* *
* @param channel 频道名称 * @param channels 频道名称
*/ */
public void unsubscribe(@NotNull String... channel) { public void unsubscribe(@NotNull String... channels) {
for (int i = 0; i < channel.length; i++) { for (int i = 0; i < channels.length; i++) {
channel[i] = ballConfig.getChannelPrefix() + channel[i]; channels[i] = ballConfig.getChannelPrefix() + channels[i];
} }
BallRedisListener.INSTANCE.unsubscribe(channel); unsubscribeRaw(channels);
} }
/** /**
* 忽略频道前缀配置,取消订阅 redis 消息频道 * 忽略频道前缀配置,取消订阅 redis 消息频道
* *
* @param channel 频道名称 * @param channels 频道名称
*/ */
public void unsubscribeIgnorePrefix(@NotNull String... channel) { public void unsubscribeRaw(@NotNull String... channels) {
BallRedisListener.INSTANCE.unsubscribe(channel); BallRedisListener.INSTANCE.unsubscribe(channels);
} }
/** /**

View File

@@ -43,7 +43,7 @@ public class BallServerInfo {
*/ */
private int port; private int port;
public BallServerInfo(@NotNull ConfigSection config, BallServerType type) { public BallServerInfo(@NotNull ConfigSection config, @NotNull BallServerType type) {
Map<String, String> env = System.getenv(); Map<String, String> env = System.getenv();
id = env.getOrDefault("BALL_SERVER_ID", config.getString("id")); id = env.getOrDefault("BALL_SERVER_ID", config.getString("id"));
name = env.getOrDefault("BALL_SERVER_NAME", config.getString("name")); name = env.getOrDefault("BALL_SERVER_NAME", config.getString("name"));

View File

@@ -0,0 +1,18 @@
package cn.hamster3.mc.plugin.ball.common.event.server;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 服务器上线
*/
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class BallServerEvent extends BallServerInfo {
public BallServerEvent(BallServerInfo info) {
super(info.getId(), info.getName(), info.getType(), info.getHost(), info.getPort());
}
}

View File

@@ -3,19 +3,16 @@ package cn.hamster3.mc.plugin.ball.common.event.server;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo; import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import org.jetbrains.annotations.NotNull; import lombok.EqualsAndHashCode;
/** /**
* 服务器离线 * 服务器离线
*/ */
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public class ServerOfflineEvent { @EqualsAndHashCode(callSuper = true)
@NotNull public class ServerOfflineEvent extends BallServerEvent {
private final BallServerInfo serverInfo; public ServerOfflineEvent(BallServerInfo info) {
super(info);
@NotNull
public String getServerID() {
return serverInfo.getId();
} }
} }

View File

@@ -3,15 +3,16 @@ package cn.hamster3.mc.plugin.ball.common.event.server;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo; import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import org.jetbrains.annotations.NotNull; import lombok.EqualsAndHashCode;
/** /**
* 服务器上线 * 服务器上线
*/ */
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public class ServerOnlineEvent { @EqualsAndHashCode(callSuper = true)
@NotNull public class ServerOnlineEvent extends BallServerEvent {
private final BallServerInfo serverInfo; public ServerOnlineEvent(BallServerInfo info) {
super(info);
}
} }

View File

@@ -128,12 +128,11 @@ public class BallCommonListener {
@Subscribe @Subscribe
public void onServerOnline(ServerOnlineEvent event) { public void onServerOnline(ServerOnlineEvent event) {
BallServerInfo info = event.getServerInfo(); BallAPI.getInstance().getAllServerInfo().put(event.getId(), event);
BallAPI.getInstance().getAllServerInfo().put(info.getId(), info); switch (event.getType()) {
switch (info.getType()) {
case GAME: { case GAME: {
BallAPI.getInstance().getAllPlayerInfo().forEach((uuid, playerInfo) -> { BallAPI.getInstance().getAllPlayerInfo().forEach((uuid, playerInfo) -> {
if (playerInfo.getGameServer().equals(info.getId())) { if (playerInfo.getGameServer().equals(event.getId())) {
playerInfo.setOnline(false); playerInfo.setOnline(false);
} }
}); });
@@ -141,7 +140,7 @@ public class BallCommonListener {
} }
case PROXY: { case PROXY: {
BallAPI.getInstance().getAllPlayerInfo().forEach((uuid, playerInfo) -> { BallAPI.getInstance().getAllPlayerInfo().forEach((uuid, playerInfo) -> {
if (playerInfo.getProxyServer().equals(info.getId())) { if (playerInfo.getProxyServer().equals(event.getId())) {
playerInfo.setOnline(false); playerInfo.setOnline(false);
} }
}); });
@@ -152,7 +151,7 @@ public class BallCommonListener {
@Subscribe @Subscribe
public void onServerOffline(ServerOfflineEvent event) { public void onServerOffline(ServerOfflineEvent event) {
String serverID = event.getServerID(); String serverID = event.getId();
BallServerInfo info = BallAPI.getInstance().getAllServerInfo().remove(serverID); BallServerInfo info = BallAPI.getInstance().getAllServerInfo().remove(serverID);
if (info == null) { if (info == null) {
return; return;

View File

@@ -15,19 +15,26 @@ public class BallRedisListener extends JedisPubSub {
@Override @Override
public void onMessage(String channel, String message) { public void onMessage(String channel, String message) {
if (channel.startsWith(BallAPI.getInstance().getBallConfig().getChannelPrefix())) { CoreAPI.getInstance().getExecutorService().submit(() -> {
channel = channel.substring(BallAPI.getInstance().getBallConfig().getChannelPrefix().length()); try {
} String finalChannel = channel;
BallMessage ballMessage = CoreAPI.getInstance().getGson().fromJson(message, BallMessage.class); if (finalChannel.startsWith(BallAPI.getInstance().getBallConfig().getChannelPrefix())) {
BallAPI ballAPI = BallAPI.getInstance(); finalChannel = finalChannel.substring(BallAPI.getInstance().getBallConfig().getChannelPrefix().length());
EventBus eventBus = ballAPI.getEventBus(); }
if (ballMessage.getReceiverType() != null && ballMessage.getReceiverType() != ballAPI.getLocalServerInfo().getType()) { BallMessage ballMessage = CoreAPI.getInstance().getGson().fromJson(message, BallMessage.class);
return; BallAPI ballAPI = BallAPI.getInstance();
} EventBus eventBus = ballAPI.getEventBus();
if (ballMessage.getReceiverID() != null && !ballAPI.isLocalServer(ballMessage.getReceiverID())) { if (ballMessage.getReceiverType() != null && ballMessage.getReceiverType() != ballAPI.getLocalServerInfo().getType()) {
return; return;
} }
eventBus.post(new MessageReceivedEvent(channel, ballMessage)); if (ballMessage.getReceiverID() != null && !ballAPI.isLocalServer(ballMessage.getReceiverID())) {
return;
}
eventBus.post(new MessageReceivedEvent(finalChannel, ballMessage));
} catch (Exception | Error e) {
e.printStackTrace();
}
});
} }
@Override @Override

View File

@@ -8,7 +8,9 @@ import cn.hamster3.mc.plugin.ball.velocity.api.CoreVelocityAPI;
import cn.hamster3.mc.plugin.ball.velocity.listener.BallVelocityListener; import cn.hamster3.mc.plugin.ball.velocity.listener.BallVelocityListener;
import cn.hamster3.mc.plugin.ball.velocity.listener.BallVelocityMainListener; import cn.hamster3.mc.plugin.ball.velocity.listener.BallVelocityMainListener;
import cn.hamster3.mc.plugin.ball.velocity.listener.UpdatePlayerInfoListener; import cn.hamster3.mc.plugin.ball.velocity.listener.UpdatePlayerInfoListener;
import cn.hamster3.mc.plugin.ball.velocity.listener.VelocityServerListener;
import cn.hamster3.mc.plugin.ball.velocity.util.BallVelocityUtils; import cn.hamster3.mc.plugin.ball.velocity.util.BallVelocityUtils;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.Subscribe;
@@ -48,6 +50,8 @@ public class HamsterBallPlugin {
private final ProxyServer proxyServer; private final ProxyServer proxyServer;
@Getter @Getter
private final File dataFolder; private final File dataFolder;
@Getter
private YamlConfig config;
@Inject @Inject
public HamsterBallPlugin(Logger slf4jLogger, ProxyServer proxyServer, @DataDirectory Path dataPath) { public HamsterBallPlugin(Logger slf4jLogger, ProxyServer proxyServer, @DataDirectory Path dataPath) {
@@ -71,7 +75,8 @@ public class HamsterBallPlugin {
StandardCopyOption.REPLACE_EXISTING StandardCopyOption.REPLACE_EXISTING
); );
} }
CoreVelocityAPI.init(configFile); config = YamlConfig.load(configFile);
CoreVelocityAPI.init(config);
logger.info("已初始化 BallAPI"); logger.info("已初始化 BallAPI");
} catch (Exception e) { } catch (Exception e) {
slf4jLogger.error("BallAPI 初始化失败", e); slf4jLogger.error("BallAPI 初始化失败", e);
@@ -95,16 +100,21 @@ public class HamsterBallPlugin {
return; return;
} }
BallAPI.getInstance().getEventBus().register(BallVelocityListener.INSTANCE); BallAPI.getInstance().getEventBus().register(BallVelocityListener.INSTANCE);
logger.info("已注册监听器 BallBungeeListener"); logger.info("已注册监听器 BallVelocityListener");
proxyServer.getEventManager().register(this, BallVelocityMainListener.INSTANCE); proxyServer.getEventManager().register(this, BallVelocityMainListener.INSTANCE);
logger.info("已注册监听器 BallBungeeMainListener"); logger.info("已注册监听器 BallVelocityMainListener");
proxyServer.getEventManager().register(this, UpdatePlayerInfoListener.INSTANCE); proxyServer.getEventManager().register(this, UpdatePlayerInfoListener.INSTANCE);
logger.info("已注册监听器 UpdatePlayerInfoListener"); logger.info("已注册监听器 UpdatePlayerInfoListener");
if (config.getBoolean("auto-register-game-server", false)) {
BallAPI.getInstance().getEventBus().register(VelocityServerListener.INSTANCE);
logger.info("已注册监听器 VelocityServerListener");
VelocityServerListener.INSTANCE.onEnable();
}
if (BallAPI.getInstance().getBallConfig().isGameServerUpdatePlayerInfo()) { if (BallAPI.getInstance().getBallConfig().isGameServerUpdatePlayerInfo()) {
BallAPI.getInstance().subscribePatterns("*" + BallAPI.PLAYER_INFO_CHANNEL); BallAPI.getInstance().subscribePatterns("*" + BallAPI.PLAYER_INFO_CHANNEL);
} else { } else {
BallAPI.getInstance().subscribeIgnorePrefix(BallAPI.PLAYER_INFO_CHANNEL); BallAPI.getInstance().subscribeRaw(BallAPI.PLAYER_INFO_CHANNEL);
} }
BallAPI.getInstance().sendRawBallMessage( BallAPI.getInstance().sendRawBallMessage(
BallAPI.BALL_CHANNEL, BallActions.ServerOnline.name(), BallAPI.BALL_CHANNEL, BallActions.ServerOnline.name(),

View File

@@ -8,8 +8,6 @@ import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import com.velocitypowered.api.proxy.config.ProxyConfig; import com.velocitypowered.api.proxy.config.ProxyConfig;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -24,11 +22,10 @@ public final class CoreVelocityAPI extends BallAPI {
return (CoreVelocityAPI) instance; return (CoreVelocityAPI) instance;
} }
public static void init(@NotNull File configFile) throws IOException { public static void init(@NotNull YamlConfig config) {
if (instance != null) { if (instance != null) {
return; return;
} }
YamlConfig config = YamlConfig.load(configFile);
instance = new CoreVelocityAPI(config); instance = new CoreVelocityAPI(config);
} }

View File

@@ -0,0 +1,65 @@
package cn.hamster3.mc.plugin.ball.velocity.listener;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOfflineEvent;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent;
import cn.hamster3.mc.plugin.ball.velocity.HamsterBallPlugin;
import com.google.common.eventbus.Subscribe;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import java.net.InetSocketAddress;
public class VelocityServerListener {
public static final VelocityServerListener INSTANCE = new VelocityServerListener();
private VelocityServerListener() {
}
public void onEnable() {
for (BallServerInfo info : BallAPI.getInstance().getAllServerInfo().values()) {
if (info.getType() != BallServerType.GAME) {
continue;
}
ProxyServer server = HamsterBallPlugin.getInstance().getProxyServer();
server.getServer(info.getId())
.map(RegisteredServer::getServerInfo)
.ifPresent(server::unregisterServer);
ServerInfo serverInfo = new ServerInfo(info.getId(), new InetSocketAddress(info.getHost(), info.getPort()));
server.registerServer(serverInfo);
BallAPI.getInstance().getLogger().info("已添加服务器入口: " + info.getId());
}
}
@Subscribe
public void onServerOnline(ServerOnlineEvent event) {
if (event.getType() != BallServerType.GAME) {
return;
}
ProxyServer server = HamsterBallPlugin.getInstance().getProxyServer();
server.getServer(event.getId())
.map(RegisteredServer::getServerInfo)
.ifPresent(server::unregisterServer);
ServerInfo serverInfo = new ServerInfo(event.getId(), new InetSocketAddress(event.getHost(), event.getPort()));
server.registerServer(serverInfo);
BallAPI.getInstance().getLogger().info("已添加服务器入口: " + event.getId());
}
@Subscribe
public void onServerOffline(ServerOfflineEvent event) {
if (event.getType() != BallServerType.GAME) {
return;
}
ProxyServer server = HamsterBallPlugin.getInstance().getProxyServer();
ServerInfo serverInfo = server.getServer(event.getId())
.map(RegisteredServer::getServerInfo)
.orElse(null);
if (serverInfo != null) {
server.unregisterServer(serverInfo);
BallAPI.getInstance().getLogger().info("已移除服务器入口: " + event.getId());
}
}
}

View File

@@ -8,12 +8,14 @@ channel-prefix: ""
# 是否在子服端更新玩家信息 # 是否在子服端更新玩家信息
# 默认情况下BC 统一管理玩家信息,包括记录 UUID 和玩家名称 # 默认情况下BC 统一管理玩家信息,包括记录 UUID 和玩家名称
# 如果一个群组服同时拥有多个 BC 入口 # 如果一个群组服同时拥有多个 BC 入口,且每个 BC 入口为不同的玩家名称分配不同的 UUID
# 且每个 BC 入口为不同的玩家名称分配不同的 UUID
# (例如正版、盗版双入口,或网易多入口接同一个子服) # (例如正版、盗版双入口,或网易多入口接同一个子服)
# 则可以启用该功能以防止 UUID 紊乱的问题 # 则可以启用该功能以防止同一个名称占用多个 UUID 的问题
game-server-update-player-info: false game-server-update-player-info: false
# 启用后,子服启动时会自动注册该子服的入口配置,关闭时也会自动移除该子服的入口配置
auto-register-game-server: false
# 本服务器信息 # 本服务器信息
server-info: server-info:
# 服务器唯一识别码,最长 32 字符 # 服务器唯一识别码,最长 32 字符
@@ -34,21 +36,20 @@ server-info:
# 这个选项就会很有用 # 这个选项就会很有用
#datasource: #datasource:
# # 数据库链接驱动地址 # # 数据库链接驱动地址
# driver: "com.mysql.jdbc.Driver" # driver: "com.mysql.cj.jdbc.Driver"
# # 数据库链接填写格式: # # MySQL数据库链接填写格式:
# # jdbc:mysql://{数据库地址}:{数据库端口}/{使用的库名}?参数 # # jdbc:mysql://{数据库地址}:{数据库端口}/{使用的库名}?参数
# # 除非你知道自己在做什么,否则不建议随意更改参数 # url: "jdbc:mysql://localhost:3306/Test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true"
# url: "jdbc:mysql://localhost:3306/Test1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true"
# # 用户名 # # 用户名
# username: "Test" # username: "root"
# # 密码 # # 密码
# password: "Test123.." # password: "Root123.."
# # 最小闲置链接数 # # 最小闲置链接数
# # 推荐值1~3 # # 推荐值1~3
# minimum-idle: 0 # minimum-idle: 0
# # 最大链接数 # # 最大链接数
# # 推荐值:不低于3 # # 推荐值:不低于5
# maximum-pool-size: 3 # maximum-pool-size: 5
# # 保持连接池可用的间隔 # # 保持连接池可用的间隔
# # 除非你的服务器数据库连接经常断开,否则不建议启用该选项 # # 除非你的服务器数据库连接经常断开,否则不建议启用该选项
# # 单位:毫秒 # # 单位:毫秒

View File

@@ -1,6 +1,5 @@
version: ${version} VERSION: ${version}
CHECK_TYPE: GITEA_RELEASES CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-ball GIT_REPO: MiniDay/hamster-ball
GIT_TOKEN: a44a69a4d1b8601bf6091403247759cd28764d5e
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/ DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/

View File

@@ -5,7 +5,8 @@ plugins {
} }
group = "cn.hamster3.mc.plugin" group = "cn.hamster3.mc.plugin"
version = "1.6.1" version = "1.6.4"
description = "基于 Redis 的 Minecraft 服务端通用消息中间件"
subprojects { subprojects {
apply { apply {
@@ -16,6 +17,7 @@ subprojects {
group = rootProject.group group = rootProject.group
version = rootProject.version version = rootProject.version
description = rootProject.description
repositories { repositories {
maven("https://maven.airgame.net/maven-public/") maven("https://maven.airgame.net/maven-public/")
@@ -43,11 +45,6 @@ subprojects {
from(rootProject.file("LICENSE")) from(rootProject.file("LICENSE"))
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy = DuplicatesStrategy.EXCLUDE
} }
processResources {
filesMatching("update.yml") {
expand(rootProject.properties)
}
}
build { build {
dependsOn(shadowJar) dependsOn(shadowJar)
} }