From aafd7059bc473b15c0ad6fe9eeb3fcf47f730aa9 Mon Sep 17 00:00:00 2001 From: 3944Realms Date: Tue, 9 Jun 2026 13:43:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- .../core/config/YamlConfigLoader.kt | 113 +++++++++++++----- .../sql/invitation/query_code_ids.sql | 3 + .../sql/invitation/query_qualification.sql | 3 + .../sql/invitation/query_token_info.sql | 3 + .../sql/invitation/upsert_ascription.sql | 1 + .../sql/whitelist/query_whitelist_record.sql | 1 + 7 files changed, 94 insertions(+), 32 deletions(-) create mode 100644 src/main/resources/sql/invitation/query_code_ids.sql create mode 100644 src/main/resources/sql/invitation/query_qualification.sql create mode 100644 src/main/resources/sql/invitation/query_token_info.sql create mode 100644 src/main/resources/sql/invitation/upsert_ascription.sql create mode 100644 src/main/resources/sql/whitelist/query_whitelist_record.sql diff --git a/gradle.properties b/gradle.properties index 8edd2e8..60d8fd9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,5 +3,5 @@ org.gradle.downloadSources=false org.gradle.parallel=true org.gradle.degree_of_parallelism=16 project_group=top.r3944realms.ltdmanager -project_version=1.22-SNAPSHOT +project_version=1.22 dg_lab_version=4.4.14.19 diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/core/config/YamlConfigLoader.kt b/src/main/kotlin/top/r3944realms/ltdmanager/core/config/YamlConfigLoader.kt index eb6c578..8a8ef33 100644 --- a/src/main/kotlin/top/r3944realms/ltdmanager/core/config/YamlConfigLoader.kt +++ b/src/main/kotlin/top/r3944realms/ltdmanager/core/config/YamlConfigLoader.kt @@ -3,34 +3,34 @@ package top.r3944realms.ltdmanager.core.config import org.yaml.snakeyaml.LoaderOptions import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.constructor.Constructor +import org.yaml.snakeyaml.error.YAMLException import org.yaml.snakeyaml.introspector.Property import org.yaml.snakeyaml.introspector.PropertyUtils import top.r3944realms.ltdmanager.utils.ConfigInitializer +import top.r3944realms.ltdmanager.utils.LoggerUtil import top.r3944realms.ltdmanager.utils.NamingConventionUtil import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths object YamlConfigLoader { - val appConfigFilePath: Path = Paths.get("config/application.yaml") // 配置文件路径 - val moduleConfigFilePath: Path = Paths.get("config/module.yaml") // 配置文件路径 - private val _app_config by lazy { loadAppConfigWrapper() } // 延迟初始化 + val appConfigFilePath: Path = Paths.get("config/application.yaml") + val moduleConfigFilePath: Path = Paths.get("config/module.yaml") + private val _app_config by lazy { loadAppConfigWrapper() } val appConfig: AppConfigWrapper get() = _app_config - private val _module_config by lazy { loadModuleConfigWrapper() } // 延迟初始化 + private val _module_config by lazy { loadModuleConfigWrapper() } val moduleConfig: ModuleConfigWrapper get() = _module_config init { - // 第一次启动确保配置文件存在 ConfigInitializer.initConfig("module.yaml", "config", false) ConfigInitializer.initConfig("application.yaml", "config") - // 初始化后加密(确保只执行一次) runCatching { - ensureConfigEncrypted(_app_config) + ensureConfigEncrypted(_app_config) }.onFailure { e -> - println("初始化加密失败: ${e.message}") - e.printStackTrace() + LoggerUtil.syncError("配置加密失败: ${e.message?.lineSequence()?.firstOrNull() ?: e.message}") } } + private fun ensureConfigEncrypted(config: AppConfigWrapper?) { config?.database?.encryptPassword() config?.websocket?.encryptToken() @@ -43,47 +43,90 @@ object YamlConfigLoader { config?.imgTu?.encryptPassword() config?.whitelistSystem?.encryptToken() } + private fun loadAppConfigWrapper(): AppConfigWrapper { if (!Files.exists(appConfigFilePath)) { throw RuntimeException("应用配置文件未找到: $appConfigFilePath") } - - try { - val yamlContent = Files.readString(appConfigFilePath) - - return Yaml(getConstructor(AppConfigWrapper::class.java)).load(yamlContent) - ?: throw RuntimeException("YAML解析返回null") - - } catch (e: Exception) { - throw RuntimeException("YAML解析失败: ${e.message}", e) - } - + return loadYaml(appConfigFilePath, AppConfigWrapper::class.java) } + private fun loadModuleConfigWrapper(): ModuleConfigWrapper { if (!Files.exists(moduleConfigFilePath)) { throw RuntimeException("模块配置文件未找到: $moduleConfigFilePath") } + return loadYaml(moduleConfigFilePath, ModuleConfigWrapper::class.java) + } - try { - val yamlContent = Files.readString(moduleConfigFilePath) - - return Yaml(getConstructor(ModuleConfigWrapper::class.java)).load(yamlContent) - ?: throw RuntimeException("YAML解析返回null") - + private fun loadYaml(filePath: Path, clazz: Class): T { + val yamlContent = Files.readString(filePath) + return try { + Yaml(getConstructor(clazz)).load(yamlContent) + ?: throw RuntimeException("YAML 解析返回 null: $filePath") + } catch (e: ConfigParseException) { + throw e } catch (e: Exception) { - throw RuntimeException("YAML解析失败: ${e.message}", e) + throw ConfigParseException(formatYamlError(e, filePath), e) + } + } + + /** 提取 SnakeYAML 异常中的关键信息并格式化为可读的错误 */ + private fun formatYamlError(e: Throwable, filePath: Path): String { + val sb = StringBuilder() + sb.appendLine("配置文件解析失败: $filePath") + sb.appendLine("─".repeat(48)) + + // 提取所有嵌套的 YAMLException 信息 (逆序,最深层优先) + val causes = generateSequence(e) { it.cause }.toList().reversed() + for (cause in causes) { + val msg = cause.message ?: continue + // 只保留以 YAML 行号/列号开头的错误和 "Unable to find" 错误 + if (msg.contains("Unable to find") || msg.startsWith("Cannot create") || + msg.contains("in 'string', line") || msg.contains("Unable to find enum") + ) { + // 提取行号信息 + val lineMatch = Regex("line (\\d+)").find(msg) + val lineInfo = lineMatch?.let { " (第${it.groupValues[1]}行)" } ?: "" + // 提取关键描述 + val desc = when { + msg.contains("Unable to find property") -> { + val prop = Regex("property '(\\S+)'").find(msg)?.groupValues?.get(1) ?: "?" + "未知配置项: '$prop'$lineInfo" + } + msg.contains("Unable to find enum value") -> { + val value = Regex("enum value '(\\S+)'").find(msg)?.groupValues?.get(1) ?: "?" + val enumClass = Regex("enum class: (\\S+)").find(msg)?.groupValues?.get(1)?.substringAfterLast('.') ?: "" + "未知枚举值: '$value' (有效值见 $enumClass 定义)$lineInfo" + } + else -> msg.lines().first().trim() + } + sb.appendLine(" • $desc") + } } + if (sb.lines().count() <= 3) { + // 没有提取到结构化信息,回退到原始摘要 + sb.appendLine(" 详情: ${e.message?.lineSequence()?.firstOrNull()?.take(120) ?: e.javaClass.simpleName}") + } + return sb.toString().trimEnd() } + private fun getConstructor(clazz: Class<*>): Constructor { val propertyUtils = object : PropertyUtils() { override fun getProperty(type: Class<*>, name: String): Property { val processedName = if (name.contains("-")) { - NamingConventionUtil.hyphenToCamel(name) // 连字符转驼峰 + NamingConventionUtil.hyphenToCamel(name) } else { name } - return super.getProperty(type, processedName) + return try { + super.getProperty(type, processedName) + } catch (e: YAMLException) { + // 阻止 SnakeYAML 在错误信息中 dump 完整 JavaBean + throw ConfigParseException( + "未知配置项 '$name' (映射为 '$processedName'),类型 '${type.simpleName}' 中无此字段" + ) + } } } @@ -105,6 +148,7 @@ object YamlConfigLoader { fun loadTuImgConfig(): ImgTuConfig = appConfig.imgTu fun loadWhitelistSystemConfig(): WhitelistSystemConfig = appConfig.whitelistSystem fun loadModuleConfig(): ModuleConfig = moduleConfig.module + data class AppConfigWrapper( var database: DatabaseConfig = DatabaseConfig(), var crypto: CryptoConfig = CryptoConfig(), @@ -118,9 +162,16 @@ object YamlConfigLoader { var dgLab: DgLabConfig = DgLabConfig(), var imgTu: ImgTuConfig = ImgTuConfig(), var whitelistSystem: WhitelistSystemConfig = WhitelistSystemConfig(), - ) + ) { + override fun toString(): String = + "AppConfig(loaded=${database.url != null})" + } data class ModuleConfigWrapper( var module: ModuleConfig = ModuleConfig(), ) -} \ No newline at end of file +} + +/** 配置解析专用异常 — 不递归打印 cause 避免海量堆栈 */ +class ConfigParseException(message: String, cause: Throwable? = null) : + RuntimeException(message, cause, true, false) diff --git a/src/main/resources/sql/invitation/query_code_ids.sql b/src/main/resources/sql/invitation/query_code_ids.sql new file mode 100644 index 0000000..891a0cb --- /dev/null +++ b/src/main/resources/sql/invitation/query_code_ids.sql @@ -0,0 +1,3 @@ +SELECT i.id, i.code +FROM blessingskin.invitation_codes i +WHERE i.code IN (${placeholders}) diff --git a/src/main/resources/sql/invitation/query_qualification.sql b/src/main/resources/sql/invitation/query_qualification.sql new file mode 100644 index 0000000..1408df4 --- /dev/null +++ b/src/main/resources/sql/invitation/query_qualification.sql @@ -0,0 +1,3 @@ +SELECT q.player_id, q.effective, q.is_used, q.qq, q.status +FROM ltd_manager_bot.qualified_user_info q +WHERE q.qq IN (${placeholders}) diff --git a/src/main/resources/sql/invitation/query_token_info.sql b/src/main/resources/sql/invitation/query_token_info.sql new file mode 100644 index 0000000..406d4de --- /dev/null +++ b/src/main/resources/sql/invitation/query_token_info.sql @@ -0,0 +1,3 @@ +SELECT q.player_id, q.player_name, q.token, q.expires_at +FROM ltd_manager_bot.qualified_user_info q +WHERE q.player_id IN (${placeholders}) diff --git a/src/main/resources/sql/invitation/upsert_ascription.sql b/src/main/resources/sql/invitation/upsert_ascription.sql new file mode 100644 index 0000000..1ea198c --- /dev/null +++ b/src/main/resources/sql/invitation/upsert_ascription.sql @@ -0,0 +1 @@ +INSERT INTO ltd_manager_bot.invitation_code_ascription (id, token_id) VALUES ${values} ON DUPLICATE KEY UPDATE token_id = VALUES(token_id) diff --git a/src/main/resources/sql/whitelist/query_whitelist_record.sql b/src/main/resources/sql/whitelist/query_whitelist_record.sql new file mode 100644 index 0000000..41a35c6 --- /dev/null +++ b/src/main/resources/sql/whitelist/query_whitelist_record.sql @@ -0,0 +1 @@ +SELECT status FROM minecraft_manager_ltd_9.players WHERE qq = ${placeholder} \ No newline at end of file