feat: 优化配置报错

This commit is contained in:
叁玖领域 2026-06-09 13:43:00 +08:00
parent f3af0b7134
commit aafd7059bc
7 changed files with 94 additions and 32 deletions

View File

@ -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

View File

@ -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)
}.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 <T> loadYaml(filePath: Path, clazz: Class<T>): 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(),
)
}
/** 配置解析专用异常 — 不递归打印 cause 避免海量堆栈 */
class ConfigParseException(message: String, cause: Throwable? = null) :
RuntimeException(message, cause, true, false)

View File

@ -0,0 +1,3 @@
SELECT i.id, i.code
FROM blessingskin.invitation_codes i
WHERE i.code IN (${placeholders})

View File

@ -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})

View File

@ -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})

View File

@ -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)

View File

@ -0,0 +1 @@
SELECT status FROM minecraft_manager_ltd_9.players WHERE qq = ${placeholder}