diff --git a/README.md b/README.md index 124d24f..b519d68 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ ## 插件功能与优势 - 物品指令绑定,给予玩家可执行对应指令的消耗物品,支持PlaceholderAPI变量。 -- 多种存储格式,按需选择。支持 YAML、JSON、h2 与 MySQL/MariaDB 的存储方式。 - **允许限定。** 允许给物品对应的指令组设定“领取次数”、“每日领取次数”与“领取时间”限定。 - **详细记录。** 每个物品均有独立ID,并对使用的玩家与执行结果进行详细记录,便于追踪查询。 - **异步存取。** 数据读取与存储均为异步操作,不影响服务器性能。 diff --git a/src/main/java/cc/carm/plugin/itemcommands/ItemCommands.java b/src/main/java/cc/carm/plugin/itemcommands/ItemCommands.java new file mode 100644 index 0000000..3967b1c --- /dev/null +++ b/src/main/java/cc/carm/plugin/itemcommands/ItemCommands.java @@ -0,0 +1,6 @@ +package cc.carm.plugin.itemcommands; + +public class ItemCommands { + + +} diff --git a/src/main/java/cc/carm/plugin/itemcommands/Main.java b/src/main/java/cc/carm/plugin/itemcommands/Main.java index 5099525..29afabe 100644 --- a/src/main/java/cc/carm/plugin/itemcommands/Main.java +++ b/src/main/java/cc/carm/plugin/itemcommands/Main.java @@ -1,4 +1,92 @@ package cc.carm.plugin.itemcommands; -public class Main { +import cc.carm.lib.easyplugin.EasyPlugin; +import cc.carm.lib.easyplugin.i18n.EasyPluginMessageProvider; +import cc.carm.plugin.itemcommands.configuration.PluginConfig; +import cc.carm.plugin.itemcommands.hooker.GHUpdateChecker; +import cc.carm.plugin.itemcommands.manager.ConfigManager; +import cc.carm.plugin.itemcommands.util.JarResourceUtils; +import org.bstats.bukkit.Metrics; +import org.bukkit.Bukkit; +import org.bukkit.event.Listener; + +import java.util.Optional; + +public class Main extends EasyPlugin { + + private static Main instance; + + public Main() { + super(new EasyPluginMessageProvider.zh_CN()); + instance = this; + } + + /** + * 注册监听器 + * + * @param listener 监听器 + */ + public static void regListener(Listener listener) { + Bukkit.getPluginManager().registerEvents(listener, getInstance()); + } + + public static void info(String... messages) { + getInstance().log(messages); + } + + public static void severe(String... messages) { + getInstance().error(messages); + } + + public static void debugging(String... messages) { + getInstance().debug(messages); + } + + public static Main getInstance() { + return instance; + } + + @Override + protected boolean initialize() { + + info("加载配置文件..."); + if (!ConfigManager.initConfig()) { + severe("配置文件初始化失败,请检查。"); + setEnabled(false); + return false; + } + + if (PluginConfig.METRICS.get()) { + info("启用统计数据..."); + new Metrics(this, 14459); + } + + if (PluginConfig.CHECK_UPDATE.get()) { + log("开始检查更新..."); + GHUpdateChecker checker = new GHUpdateChecker(getLogger(), "CarmJos", "ItemCommands"); + getScheduler().runAsync(() -> checker.checkUpdate(getDescription().getVersion())); + } else { + log("已禁用检查更新,跳过。"); + } + + return true; + } + + @Override + protected void shutdown() { + + info("卸载监听器..."); + Bukkit.getServicesManager().unregisterAll(this); + + } + + @Override + public boolean isDebugging() { + return PluginConfig.DEBUG.get(); + } + + @Override + public void outputInfo() { + Optional.ofNullable(JarResourceUtils.readResource(this.getResource("PLUGIN_INFO"))).ifPresent(this::log); + } } diff --git a/src/main/java/cc/carm/plugin/itemcommands/configuration/PluginConfig.java b/src/main/java/cc/carm/plugin/itemcommands/configuration/PluginConfig.java new file mode 100644 index 0000000..1c5d104 --- /dev/null +++ b/src/main/java/cc/carm/plugin/itemcommands/configuration/PluginConfig.java @@ -0,0 +1,23 @@ +package cc.carm.plugin.itemcommands.configuration; + +import cc.carm.lib.easyplugin.configuration.values.ConfigValue; + +public class PluginConfig { + + public static final ConfigValue DEBUG = new ConfigValue<>( + "debug", Boolean.class, false + ); + + public static final ConfigValue METRICS = new ConfigValue<>( + "metrics", Boolean.class, true + ); + + public static final ConfigValue CHECK_UPDATE = new ConfigValue<>( + "check-update", Boolean.class, true + ); + + public static final ConfigValue STORAGE_METHOD = new ConfigValue<>( + "storage.method", String.class, "YAML" + ); + +} diff --git a/src/main/java/cc/carm/plugin/itemcommands/configuration/PluginMessages.java b/src/main/java/cc/carm/plugin/itemcommands/configuration/PluginMessages.java new file mode 100644 index 0000000..c51f529 --- /dev/null +++ b/src/main/java/cc/carm/plugin/itemcommands/configuration/PluginMessages.java @@ -0,0 +1,9 @@ +package cc.carm.plugin.itemcommands.configuration; + + +import cc.carm.lib.easyplugin.configuration.language.MessagesRoot; + +public class PluginMessages extends MessagesRoot { + + +} diff --git a/src/main/java/cc/carm/plugin/itemcommands/hooker/GHUpdateChecker.java b/src/main/java/cc/carm/plugin/itemcommands/hooker/GHUpdateChecker.java new file mode 100644 index 0000000..2b26a9f --- /dev/null +++ b/src/main/java/cc/carm/plugin/itemcommands/hooker/GHUpdateChecker.java @@ -0,0 +1,38 @@ +package cc.carm.plugin.itemcommands.hooker; + +import cc.carm.lib.githubreleases4j.GithubReleases4J; + +import java.util.logging.Logger; + + +public class GHUpdateChecker { + + private final Logger logger; + private final String owner; + private final String repo; + + public GHUpdateChecker(Logger logger, String owner, String repo) { + this.logger = logger; + this.owner = owner; + this.repo = repo; + } + + public void checkUpdate(String currentVersion) { + Integer behindVersions = GithubReleases4J.getVersionBehind(owner, repo, currentVersion); + String downloadURL = GithubReleases4J.getReleasesURL(owner, repo); + if (behindVersions == null) { + logger.severe("检查更新失败,请您定期查看插件是否更新,避免安全问题。"); + logger.severe("下载地址 " + downloadURL); + } else if (behindVersions == 0) { + logger.info("检查完成,当前已是最新版本。"); + } else if (behindVersions > 0) { + logger.info("发现新版本! 目前已落后 " + behindVersions + " 个版本。"); + logger.info("最新版下载地址 " + downloadURL); + } else { + logger.severe("检查更新失败! 当前版本未知,请您使用原生版本以避免安全问题。"); + logger.severe("最新版下载地址 " + downloadURL); + } + } + + +} diff --git a/src/main/java/cc/carm/plugin/itemcommands/manager/ConfigManager.java b/src/main/java/cc/carm/plugin/itemcommands/manager/ConfigManager.java new file mode 100644 index 0000000..1fee9e7 --- /dev/null +++ b/src/main/java/cc/carm/plugin/itemcommands/manager/ConfigManager.java @@ -0,0 +1,57 @@ +package cc.carm.plugin.itemcommands.manager; + +import cc.carm.lib.easyplugin.configuration.file.FileConfig; +import cc.carm.lib.easyplugin.configuration.language.MessagesConfig; +import cc.carm.lib.easyplugin.configuration.language.MessagesInitializer; +import cc.carm.plugin.itemcommands.Main; +import cc.carm.plugin.itemcommands.configuration.PluginMessages; + +import java.io.IOException; + +public class ConfigManager { + + private static FileConfig config; + private static MessagesConfig messageConfig; + + public static boolean initConfig() { + try { + ConfigManager.config = new FileConfig(Main.getInstance()); + ConfigManager.messageConfig = new MessagesConfig(Main.getInstance()); + + FileConfig.pluginConfiguration = () -> config; + FileConfig.messageConfiguration = () -> messageConfig; + + MessagesInitializer.initialize(messageConfig, PluginMessages.class); + + return true; + } catch (IOException e) { + return false; + } + } + + public static FileConfig getPluginConfig() { + return config; + } + + public static FileConfig getMessageConfig() { + return messageConfig; + } + + public static void reload() { + try { + getPluginConfig().reload(); + getMessageConfig().reload(); + } catch (Exception ignored) { + } + } + + public static void saveConfig() { + try { + getPluginConfig().save(); + getMessageConfig().save(); + } catch (Exception ignored) { + } + } + + +} diff --git a/src/main/java/cc/carm/plugin/itemcommands/util/DatabaseTable.java b/src/main/java/cc/carm/plugin/itemcommands/util/DatabaseTable.java new file mode 100644 index 0000000..e7f7ce9 --- /dev/null +++ b/src/main/java/cc/carm/plugin/itemcommands/util/DatabaseTable.java @@ -0,0 +1,77 @@ +package cc.carm.plugin.itemcommands.util; + +import cc.carm.lib.easysql.api.SQLManager; +import cc.carm.lib.easysql.api.action.PreparedSQLUpdateAction; +import cc.carm.lib.easysql.api.action.PreparedSQLUpdateBatchAction; +import cc.carm.lib.easysql.api.builder.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.sql.SQLException; + +public class DatabaseTable { + + private final @NotNull String tableName; + private final @NotNull String[] columns; + + @Nullable String tableSettings; + + public DatabaseTable(@NotNull String tableName, @NotNull String[] columns) { + this(tableName, columns, null); + } + + public DatabaseTable(@NotNull String tableName, @NotNull String[] columns, + @Nullable String tableSettings) { + this.tableName = tableName; + this.columns = columns; + this.tableSettings = tableSettings; + } + + public @NotNull String getTableName() { + return tableName; + } + + public @NotNull String[] getColumns() { + return columns; + } + + public @Nullable String getTableSettings() { + return tableSettings; + } + + public int createTable(SQLManager sqlManager) throws SQLException { + TableCreateBuilder createAction = sqlManager.createTable(getTableName()); + createAction.setColumns(getColumns()); + if (getTableSettings() != null) createAction.setTableSettings(getTableSettings()); + return createAction.build().execute(); + } + + public TableQueryBuilder createQuery(SQLManager sqlManager) { + return sqlManager.createQuery().inTable(getTableName()); + } + + public DeleteBuilder createDelete(SQLManager sqlManager) { + return sqlManager.createDelete(getTableName()); + } + + public UpdateBuilder createUpdate(SQLManager sqlManager) { + return sqlManager.createUpdate(getTableName()); + } + + public InsertBuilder createInsert(SQLManager sqlManager) { + return sqlManager.createInsert(getTableName()); + } + + public InsertBuilder createInsertBatch(SQLManager sqlManager) { + return sqlManager.createInsertBatch(getTableName()); + } + + public ReplaceBuilder createReplace(SQLManager sqlManager) { + return sqlManager.createReplace(getTableName()); + } + + public ReplaceBuilder createReplaceBatch(SQLManager sqlManager) { + return sqlManager.createReplaceBatch(getTableName()); + } + +} diff --git a/src/main/java/cc/carm/plugin/itemcommands/util/JarResourceUtils.java b/src/main/java/cc/carm/plugin/itemcommands/util/JarResourceUtils.java new file mode 100644 index 0000000..0c29eb9 --- /dev/null +++ b/src/main/java/cc/carm/plugin/itemcommands/util/JarResourceUtils.java @@ -0,0 +1,106 @@ +package cc.carm.plugin.itemcommands.util; + +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +@SuppressWarnings("ResultOfMethodCallIgnored") +public class JarResourceUtils { + public static final char JAR_SEPARATOR = '/'; + + public static @Nullable String[] readResource(@Nullable InputStream resourceStream) { + if (resourceStream == null) return null; + try (Scanner scanner = new Scanner(resourceStream, StandardCharsets.UTF_8.name())) { + List contents = new ArrayList<>(); + while (scanner.hasNextLine()) { + contents.add(scanner.nextLine()); + } + return contents.toArray(new String[0]); + } catch (Exception e) { + return null; + } + } + + public static void copyFolderFromJar(String folderName, File destFolder, CopyOption option) + throws IOException { + copyFolderFromJar(folderName, destFolder, option, null); + } + + public static void copyFolderFromJar(String folderName, File destFolder, + CopyOption option, PathTrimmer trimmer) throws IOException { + if (!destFolder.exists()) + destFolder.mkdirs(); + + byte[] buffer = new byte[1024]; + + File fullPath; + String path = JarResourceUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath(); + if (trimmer != null) + path = trimmer.trim(path); + try { + if (!path.startsWith("file")) + path = "file://" + path; + + fullPath = new File(new URI(path)); + } catch (URISyntaxException e) { + e.printStackTrace(); + return; + } + + ZipInputStream zis = new ZipInputStream(new FileInputStream(fullPath)); + + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if (!entry.getName().startsWith(folderName + JAR_SEPARATOR)) + continue; + + String fileName = entry.getName(); + + if (fileName.charAt(fileName.length() - 1) == JAR_SEPARATOR) { + File file = new File(destFolder + File.separator + fileName); + if (file.isFile()) { + file.delete(); + } + file.mkdirs(); + continue; + } + + File file = new File(destFolder + File.separator + fileName); + if (option == CopyOption.COPY_IF_NOT_EXIST && file.exists()) + continue; + + if (!file.getParentFile().exists()) + file.getParentFile().mkdirs(); + + if (!file.exists()) + file.createNewFile(); + FileOutputStream fos = new FileOutputStream(file); + + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + fos.close(); + } + + zis.closeEntry(); + zis.close(); + } + + public enum CopyOption { + COPY_IF_NOT_EXIST, REPLACE_IF_EXIST + } + + @FunctionalInterface + public interface PathTrimmer { + String trim(String original); + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index a5fa2b7..d2fc665 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -15,17 +15,12 @@ metrics: true # 检查更新为异步操作,绝不会影响性能与使用体验。 check-update: true -stroage: - # 存储方式,可选 [ yaml | json | h2 | mysql(推荐) ] - method: yaml +log-stroage: + # 是否启用日志记录存储 + # 可用于追踪物品的发放、领取情况与执行记录。 + enable: true - # 选择 yaml/json 存储方式时的存储路径 - # 默认为相对路径,相对于插件生成的配置文件夹下的路径 - # 支持绝对路径,如 “/var/data/item-commands/"(linux) 或 "D:\data\item-commands\"(windows) - # 使用绝对路径时请注意权限问题 - file-path: data - - # 选择 mysql/mariadb 存储方式时的数据库配置 + # 启用日志记录存储时的数据库配置 database: # 数据库驱动路径,默认为 MySQL driver: "com.mysql.cj.jdbc.Driver"