diff --git a/pom.xml b/pom.xml index 451197e..ab46e0a 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,6 @@ easyplugin-main ${easyplugin.version} compile - true diff --git a/src/main/java/cc/carm/plugin/timereward/Main.java b/src/main/java/cc/carm/plugin/timereward/Main.java index 6eb0224..973e063 100644 --- a/src/main/java/cc/carm/plugin/timereward/Main.java +++ b/src/main/java/cc/carm/plugin/timereward/Main.java @@ -3,7 +3,12 @@ package cc.carm.plugin.timereward; import cc.carm.lib.easyplugin.EasyPlugin; import cc.carm.lib.easyplugin.i18n.EasyPluginMessageProvider; import cc.carm.plugin.timereward.configuration.PluginConfig; +import cc.carm.plugin.timereward.database.DataManager; +import cc.carm.plugin.timereward.listener.UserListener; import cc.carm.plugin.timereward.manager.ConfigManager; +import cc.carm.plugin.timereward.manager.RewardManager; +import cc.carm.plugin.timereward.manager.UserManager; +import org.bukkit.Bukkit; public class Main extends EasyPlugin { private static Main instance; @@ -13,20 +18,40 @@ public class Main extends EasyPlugin { instance = this; } - protected static ConfigManager configManager; + protected DataManager dataManager; + protected ConfigManager configManager; + protected UserManager userManager; + protected RewardManager rewardManager; @Override public boolean initialize() { log("加载插件配置文件..."); - Main.configManager = new ConfigManager(); - if (!Main.configManager.initConfig()) { + this.configManager = new ConfigManager(); + if (!this.configManager.initConfig()) { error("插件配置文件初始化失败,请检查文件权限。"); return false; } - log("加载玩家管理器..."); + info("初始化数据管理器..."); + this.dataManager = new DataManager(); + if (!dataManager.initialize()) { + severe("初始化数据库失败,请检查配置文件。"); + dataManager.shutdown(); + return false; // 初始化失败,不再继续加载 + } + + log("加载用户管理器..."); + this.userManager = new UserManager(); + if (Bukkit.getOnlinePlayers().size() > 0) { + log(" 加载当前在线用户数据..."); + this.userManager.loadAll(); + } + + log("加载奖励管理器..."); + this.rewardManager = new RewardManager(); log("注册监听器..."); + regListener(new UserListener()); log("注册指令..."); @@ -36,6 +61,15 @@ public class Main extends EasyPlugin { @Override protected void shutdown() { + info("卸载监听器..."); + Bukkit.getServicesManager().unregisterAll(this); + + info("保存用户数据..."); + this.userManager.unloadAll(true); + + info("结束数据库进程..."); + getDataManager().shutdown(); + } @Override @@ -59,4 +93,8 @@ public class Main extends EasyPlugin { getInstance().debug(messages); } + public static DataManager getDataManager() { + return getInstance().dataManager; + } + } \ No newline at end of file diff --git a/src/main/java/cc/carm/plugin/timereward/TimeRewardAPI.java b/src/main/java/cc/carm/plugin/timereward/TimeRewardAPI.java index 4476e9d..60f4aa1 100644 --- a/src/main/java/cc/carm/plugin/timereward/TimeRewardAPI.java +++ b/src/main/java/cc/carm/plugin/timereward/TimeRewardAPI.java @@ -1,4 +1,22 @@ package cc.carm.plugin.timereward; +import cc.carm.plugin.timereward.manager.ConfigManager; +import cc.carm.plugin.timereward.manager.RewardManager; +import cc.carm.plugin.timereward.manager.UserManager; + public class TimeRewardAPI { + + public static UserManager getUserManager() { + return Main.getInstance().userManager; + } + + public static ConfigManager getConfigManager() { + return Main.getInstance().configManager; + } + + public static RewardManager getRewardManager(){ + return Main.getInstance().rewardManager; + } + + } diff --git a/src/main/java/cc/carm/plugin/timereward/configuration/PluginConfig.java b/src/main/java/cc/carm/plugin/timereward/configuration/PluginConfig.java index 65152e2..c937671 100644 --- a/src/main/java/cc/carm/plugin/timereward/configuration/PluginConfig.java +++ b/src/main/java/cc/carm/plugin/timereward/configuration/PluginConfig.java @@ -1,12 +1,12 @@ package cc.carm.plugin.timereward.configuration; -import cc.carm.lib.easyplugin.configuration.impl.ConfigSound; import cc.carm.lib.easyplugin.configuration.values.ConfigValue; -import org.bukkit.Sound; public class PluginConfig { public static final ConfigValue DEBUG = new ConfigValue<>( "debug", Boolean.class, false ); + + } diff --git a/src/main/java/cc/carm/plugin/timereward/configuration/PluginMessages.java b/src/main/java/cc/carm/plugin/timereward/configuration/PluginMessages.java index c029eb2..460611b 100644 --- a/src/main/java/cc/carm/plugin/timereward/configuration/PluginMessages.java +++ b/src/main/java/cc/carm/plugin/timereward/configuration/PluginMessages.java @@ -6,9 +6,10 @@ import cc.carm.lib.easyplugin.configuration.language.MessagesRoot; public class PluginMessages extends MessagesRoot { - public static final EasyMessageList NOT_ONLINE = new EasyMessageList( - new String[]{"&7玩家 &c%(player) &7并不在线。"}, - new String[]{"%(player)"} - ); + public static final EasyMessageList NOT_ONLINE = EasyMessageList.builder() + .contents("&7玩家 &c%(player) &7并不在线。") + .params("player") + .build(); + } diff --git a/src/main/java/cc/carm/plugin/timereward/data/DataTaskRunner.java b/src/main/java/cc/carm/plugin/timereward/data/DataTaskRunner.java new file mode 100644 index 0000000..58cea80 --- /dev/null +++ b/src/main/java/cc/carm/plugin/timereward/data/DataTaskRunner.java @@ -0,0 +1,8 @@ +package cc.carm.plugin.timereward.data; + +import cc.carm.plugin.timereward.database.DataManager; + +@FunctionalInterface +public interface DataTaskRunner { + void run(TimeRewardUser user, DataManager dataManager) throws Exception; +} diff --git a/src/main/java/cc/carm/plugin/timereward/data/TimeRewardUser.java b/src/main/java/cc/carm/plugin/timereward/data/TimeRewardUser.java new file mode 100644 index 0000000..7925f2d --- /dev/null +++ b/src/main/java/cc/carm/plugin/timereward/data/TimeRewardUser.java @@ -0,0 +1,90 @@ +package cc.carm.plugin.timereward.data; + +import org.jetbrains.annotations.NotNull; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * 用户奖励数据,用于存储用户的奖励的领取情况。 + */ +public class TimeRewardUser { + + UUID userUUID; + + Set<@NotNull String> claimedRewards; //记录已领取的奖励ID + + long storedSeconds; //记录已经游玩的时间 + long joinMillis; // 记录本次加入的时间 + + public TimeRewardUser(UUID userUUID) { + this(userUUID, new LinkedHashSet<>(), 0); + } + + public TimeRewardUser(UUID userUUID, Set<@NotNull String> claimedRewards, + long storedSeconds) { + this(userUUID, claimedRewards, storedSeconds, System.currentTimeMillis()); + } + + + public TimeRewardUser(UUID userUUID, Set<@NotNull String> claimedRewards, + long storedSeconds, long joinMillis) { + this.userUUID = userUUID; + this.claimedRewards = claimedRewards; + this.storedSeconds = storedSeconds; + this.joinMillis = joinMillis; + } + + public UUID getUserUUID() { + return userUUID; + } + + public long getStoredSeconds() { + return storedSeconds; + } + + /** + * 得到本次加入游戏的时间 + * + * @return 本次加入游戏时间 + */ + public long getJoinMillis() { + return joinMillis; + } + + + /** + * 获取玩家本次加入服务器的时间 ,即为 当前时间-加入时间。 + * + * @return 玩家本次加入服务器的时间 + */ + public long getCurrentSeconds() { + return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - getJoinMillis()); + } + + /** + * 获取 玩家在数据库中已经游玩的时间 + (当前时间-加入时间) + * + * @return 之前游玩的世界与本次游玩时间之和 + */ + public long getAllSeconds() { + return getStoredSeconds() + getCurrentSeconds(); + } + + public Set getClaimedRewards() { + return claimedRewards; + } + + public boolean isClaimed(@NotNull String rewardId) { + return this.claimedRewards.contains(rewardId); + } + + public boolean claimReward(@NotNull String rewardId) { + if (isClaimed(rewardId)) return false; // 已经领取过了 + this.claimedRewards.add(rewardId); + return true; + } + +} diff --git a/src/main/java/cc/carm/plugin/timereward/database/DBConfiguration.java b/src/main/java/cc/carm/plugin/timereward/database/DBConfiguration.java new file mode 100644 index 0000000..8dae397 --- /dev/null +++ b/src/main/java/cc/carm/plugin/timereward/database/DBConfiguration.java @@ -0,0 +1,49 @@ +package cc.carm.plugin.timereward.database; + +import cc.carm.lib.easyplugin.configuration.values.ConfigValue; + +public class DBConfiguration { + + protected static final ConfigValue DRIVER_NAME = new ConfigValue<>( + "storage.mysql.driver", String.class, + "com.mysql.cj.jdbc.Driver" + ); + + protected static final ConfigValue HOST = new ConfigValue<>( + "storage.mysql.host", String.class, + "127.0.0.1" + ); + + protected static final ConfigValue PORT = new ConfigValue<>( + "storage.mysql.port", Integer.class, + 3306 + ); + + protected static final ConfigValue DATABASE = new ConfigValue<>( + "storage.mysql.database", String.class, + "minecraft" + ); + + protected static final ConfigValue USERNAME = new ConfigValue<>( + "storage.mysql.username", String.class, + "root" + ); + + protected static final ConfigValue PASSWORD = new ConfigValue<>( + "storage.mysql.password", String.class, + "password" + ); + + protected static final ConfigValue ADDITIONAL = new ConfigValue<>( + "storage.mysql.additional", String.class, + "?useSSL=false" + ); + + protected static String buildJDBC() { + return String.format("jdbc:mysql://%s:%s/%s%s", + HOST.get(), PORT.get(), DATABASE.get(), ADDITIONAL.get() + ); + } + + +} diff --git a/src/main/java/cc/carm/plugin/timereward/database/DBTables.java b/src/main/java/cc/carm/plugin/timereward/database/DBTables.java new file mode 100644 index 0000000..1f53847 --- /dev/null +++ b/src/main/java/cc/carm/plugin/timereward/database/DBTables.java @@ -0,0 +1,45 @@ +package cc.carm.plugin.timereward.database; + +import cc.carm.lib.easyplugin.configuration.values.ConfigValue; + +public class DBTables { + + protected static class UserOnlineTime { + + protected static final ConfigValue TABLE_NAME = new ConfigValue<>( + "database.tables.time", String.class, + "tr_user_times" + ); + + protected static final String[] TABLE_COLUMNS = new String[]{ + "`uuid` VARCHAR(36) NOT NULL PRIMARY KEY COMMENT '用户UUID'", // 用户的UUID + "`time` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户在线秒数'",// 用户在线时间(秒) + "`update` DATETIME NOT NULL " + + "DEFAULT CURRENT_TIMESTAMP " + + "ON UPDATE CURRENT_TIMESTAMP " + + " COMMENT '最后更新时间'" + }; + + } + + protected static class UserClaimedReward { + + protected static final ConfigValue TABLE_NAME = new ConfigValue<>( + "database.tables.claimed", String.class, + "tr_user_claimed" + ); + + protected static final String[] TABLE_COLUMNS = new String[]{ + "`id` INT NOT NULL UNSIGNED AUTO_INCREMENT UNIQUE KEY", // 排序键 + "`uuid` VARCHAR(36) NOT NULL PRIMARY KEY COMMENT '用户UUID'", // 用户的UUID 主键 + "`value` MEDIUMTEXT", // 已领取的奖励ID + "`update` DATETIME NOT NULL " + + "DEFAULT CURRENT_TIMESTAMP " + + "ON UPDATE CURRENT_TIMESTAMP " + + " COMMENT '最后更新时间'" + }; + + } + + +} diff --git a/src/main/java/cc/carm/plugin/timereward/database/DataManager.java b/src/main/java/cc/carm/plugin/timereward/database/DataManager.java new file mode 100644 index 0000000..bbb876a --- /dev/null +++ b/src/main/java/cc/carm/plugin/timereward/database/DataManager.java @@ -0,0 +1,117 @@ +package cc.carm.plugin.timereward.database; + +import cc.carm.lib.easysql.EasySQL; +import cc.carm.lib.easysql.api.SQLManager; +import cc.carm.plugin.timereward.Main; +import me.clip.placeholderapi.libs.gson.Gson; +import me.clip.placeholderapi.libs.gson.JsonElement; +import me.clip.placeholderapi.libs.gson.JsonParser; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; + +public class DataManager { + + protected static final Gson GSON = new Gson(); + private SQLManager sqlManager; + + public boolean initialize() { + try { + Main.info(" 尝试连接到数据库..."); + this.sqlManager = EasySQL.createManager( + DBConfiguration.DRIVER_NAME.get(), DBConfiguration.buildJDBC(), + DBConfiguration.USERNAME.get(), DBConfiguration.PASSWORD.get() + ); + this.sqlManager.setDebugMode(() -> Main.getInstance().isDebugging()); + } catch (Exception exception) { + Main.severe("无法连接到数据库,请检查配置文件。"); + exception.printStackTrace(); + return false; + } + + try { + Main.info(" 创建插件所需表..."); + getSQLManager().createTable(DBTables.UserOnlineTime.TABLE_NAME.get()) + .setColumns(DBTables.UserOnlineTime.TABLE_COLUMNS) + .build().execute(); + + getSQLManager().createTable(DBTables.UserClaimedReward.TABLE_NAME.get()) + .setColumns(DBTables.UserClaimedReward.TABLE_COLUMNS) + .build().execute(); + + } catch (SQLException exception) { + Main.severe("无法创建插件所需的表,请检查数据库权限。"); + exception.printStackTrace(); + return false; + } + + return true; + } + + public void shutdown() { + Main.info(" 关闭数据库连接..."); + EasySQL.shutdownManager(getSQLManager()); + this.sqlManager = null; + } + + public SQLManager getSQLManager() { + return sqlManager; + } + + public @Nullable Long getPlayTime(@NotNull UUID userUUID) throws SQLException { + return getSQLManager().createQuery().inTable(DBTables.UserOnlineTime.TABLE_NAME.get()) + .selectColumns("uuid", "time") + .addCondition("uuid", userUUID.toString()) + .setLimit(1).build().executeFunction(query -> { + ResultSet resultSet = query.getResultSet(); + return resultSet.next() ? resultSet.getLong("time") : 0L; + }); + } + + public @NotNull Set getClaimedData(@NotNull UUID userUUID) throws SQLException { + return getSQLManager().createQuery().inTable(DBTables.UserClaimedReward.TABLE_NAME.get()) + .selectColumns("uuid", "value") + .addCondition("uuid", userUUID.toString()) + .setLimit(1).build().executeFunction(query -> { + ResultSet resultSet = query.getResultSet(); + if (!resultSet.next()) return new LinkedHashSet<>(); + String json = resultSet.getString("value"); + if (json == null) return new LinkedHashSet<>(); + JsonElement element = JsonParser.parseString(json); + if (!element.isJsonArray()) return new LinkedHashSet<>(); + Set ids = new LinkedHashSet<>(); + for (JsonElement e : element.getAsJsonArray()) { + ids.add(e.getAsString()); + } + return ids; + }, new LinkedHashSet<>()); + } + + + public void saveClaimedData(@NotNull UUID userUUID, @Nullable Set claimedRewards) throws SQLException { + if (claimedRewards == null) { + getSQLManager().createDelete(DBTables.UserClaimedReward.TABLE_NAME.get()) + .addCondition("uuid", userUUID.toString()) + .setLimit(1).build().execute(); + } else { + getSQLManager().createReplace(DBTables.UserClaimedReward.TABLE_NAME.get()) + .setColumnNames("uuid", "value") + .setParams(userUUID.toString(), GSON.toJson(claimedRewards)) + .execute(); + } + } + + public void savePlayTime(@NotNull UUID userUUID, long time) throws SQLException { + getSQLManager().createReplace(DBTables.UserOnlineTime.TABLE_NAME.get()) + .setColumnNames("uuid", "time").setParams(userUUID.toString(), time) + .execute(); + } + + +} + diff --git a/src/main/java/cc/carm/plugin/timereward/listener/UserListener.java b/src/main/java/cc/carm/plugin/timereward/listener/UserListener.java new file mode 100644 index 0000000..ead428e --- /dev/null +++ b/src/main/java/cc/carm/plugin/timereward/listener/UserListener.java @@ -0,0 +1,21 @@ +package cc.carm.plugin.timereward.listener; + +import cc.carm.plugin.timereward.TimeRewardAPI; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +public class UserListener implements Listener { + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + TimeRewardAPI.getUserManager().loadData(event.getPlayer().getUniqueId()); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + TimeRewardAPI.getUserManager().unloadData(event.getPlayer().getUniqueId()); + } + +} diff --git a/src/main/java/cc/carm/plugin/timereward/manager/RewardManager.java b/src/main/java/cc/carm/plugin/timereward/manager/RewardManager.java new file mode 100644 index 0000000..42d54ae --- /dev/null +++ b/src/main/java/cc/carm/plugin/timereward/manager/RewardManager.java @@ -0,0 +1,6 @@ +package cc.carm.plugin.timereward.manager; + +public class RewardManager { + + +} \ No newline at end of file diff --git a/src/main/java/cc/carm/plugin/timereward/manager/UserManager.java b/src/main/java/cc/carm/plugin/timereward/manager/UserManager.java new file mode 100644 index 0000000..de0b198 --- /dev/null +++ b/src/main/java/cc/carm/plugin/timereward/manager/UserManager.java @@ -0,0 +1,134 @@ +package cc.carm.plugin.timereward.manager; + +import cc.carm.plugin.timereward.Main; +import cc.carm.plugin.timereward.data.DataTaskRunner; +import cc.carm.plugin.timereward.data.TimeRewardUser; +import cc.carm.plugin.timereward.database.DataManager; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public class UserManager { + + private final HashMap userDataMap; + + public UserManager() { + this.userDataMap = new HashMap<>(); + } + + @NotNull + public TimeRewardUser readData(UUID userUUID) { + try { + long start = System.currentTimeMillis(); + DataManager storage = Main.getDataManager(); + + Long playTime = storage.getPlayTime(userUUID); + Set claimedRewards = storage.getClaimedData(userUUID); + + if (playTime == null && claimedRewards.isEmpty()) { + Main.debugging("当前还不存在玩家 " + userUUID + " 的数据,视作新档。"); + return new TimeRewardUser(userUUID); + } + + Main.debugging("读取 " + userUUID + " 的用户数据完成, 耗时 " + (System.currentTimeMillis() - start) + "ms。"); + return new TimeRewardUser(userUUID, claimedRewards, playTime == null ? 0L : playTime); + } catch (Exception e) { + Main.severe("无法正常读取玩家数据,玩家操作将不会被保存,请检查数据配置!"); + Main.severe("Could not load user's data, please check the data configuration!"); + e.printStackTrace(); + return new TimeRewardUser(userUUID); + } + } + + public void saveData(TimeRewardUser user) { + try { + long start = System.currentTimeMillis(); + DataManager dataManager = Main.getDataManager(); + + Main.debugging("正在保存 " + user.getUserUUID() + " 的用户数据..."); + dataManager.saveClaimedData(user.getUserUUID(), user.getClaimedRewards()); + dataManager.savePlayTime(user.getUserUUID(), user.getAllSeconds()); + Main.debugging("保存 " + user.getUserUUID() + " 的用户数据完成,耗时 " + (System.currentTimeMillis() - start) + "ms。"); + + } catch (Exception e) { + Main.severe("无法正常保存玩家数据,请检查数据配置!"); + Main.severe("Could not save user's data, please check the data configuration!"); + e.printStackTrace(); + } + } + + public void loadData(UUID userUUID) { + getUsersMap().put(userUUID, readData(userUUID)); + } + + public void unloadData(UUID userUUID) { + unloadData(userUUID, true); + } + + public void unloadData(UUID userUUID, boolean save) { + TimeRewardUser data = getData(userUUID); + if (data == null) return; + if (save) saveData(data); + getUsersMap().remove(userUUID); + } + + public void loadAll() { + for (Player player : Bukkit.getOnlinePlayers()) { + if (getUsersMap().containsKey(player.getUniqueId())) continue; + loadData(player.getUniqueId()); + } + } + + public void saveAll() { + getUsersMap().values().forEach(this::saveData); + } + + public void unloadAll(boolean save) { + if (save) saveAll(); + getUsersMap().clear(); + } + + @Nullable + public TimeRewardUser getData(UUID userUUID) { + return getUsersMap().get(userUUID); + } + + @NotNull + public TimeRewardUser getData(Player player) { + return getUsersMap().get(player.getUniqueId()); + } + + public void editData(@NotNull UUID uuid, @NotNull DataTaskRunner task) { + TimeRewardUser user = getData(uuid); + if (user == null) user = readData(uuid); // 不在线则加载数据 + try { + task.run(user, Main.getDataManager()); + } catch (Exception exception) { + Main.severe("无法正常更改玩家数据,请检查数据配置!"); + Main.severe("Could not edit user's data, please check the data configuration!"); + exception.printStackTrace(); + } + } + + public void editDataAsync(@NotNull UUID uuid, @NotNull DataTaskRunner task) { + Main.getInstance().getScheduler().runAsync(() -> editData(uuid, task)); + } + + @NotNull + @Unmodifiable + public Map listUsers() { + return new HashMap<>(getUsersMap()); + } + + protected @NotNull HashMap getUsersMap() { + return userDataMap; + } + +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 672d154..ed82743 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -4,3 +4,20 @@ version: ${project.version} debug: false + + +database: + # 数据库驱动路径,默认为 MySQL + driver: "com.mysql.cj.jdbc.Driver" + # 数据库连接配置 + host: "127.0.0.1" + port: 3306 + database: "minecraft" + username: "username" + password: "password" + additional: "?useSSL=false" + # 插件相关表的名称 + tables: + claimed: "tr_user_claimed" # 用于记录用户奖励领取情况的表 + time: "tr_user_times" # 用于记录用户在线时间的表 +