From a642948f45979b32dc65ac3e1167c660617336fd Mon Sep 17 00:00:00 2001 From: LamGC Date: Sat, 26 Feb 2022 17:30:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8F=AF=E9=80=9A=E8=BF=87=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6=E8=AE=BE=E7=BD=AE=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E6=9F=A5=E6=89=BE=E6=89=A9=E5=B1=95=E5=8C=85=E7=9A=84=20Maven?= =?UTF-8?q?=20=E4=BB=93=E5=BA=93.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用 Maven 扩展包搜素器将不再限制仓库, 可通过配置文件添加其他仓库. --- scalabot-app/src/main/kotlin/AppConfigs.kt | 59 +++++++++- scalabot-app/src/main/kotlin/AppMain.kt | 13 +++ .../src/main/kotlin/ExtensionComponents.kt | 9 +- .../src/main/kotlin/ExtensionFinders.kt | 2 +- scalabot-app/src/main/kotlin/ScalaBot.kt | 6 +- .../src/main/kotlin/util/Serializers.kt | 108 ++++++++++++++++++ 6 files changed, 187 insertions(+), 10 deletions(-) diff --git a/scalabot-app/src/main/kotlin/AppConfigs.kt b/scalabot-app/src/main/kotlin/AppConfigs.kt index 5a52e0d..c4620c8 100644 --- a/scalabot-app/src/main/kotlin/AppConfigs.kt +++ b/scalabot-app/src/main/kotlin/AppConfigs.kt @@ -6,12 +6,17 @@ import com.google.gson.GsonBuilder import com.google.gson.reflect.TypeToken import mu.KotlinLogging import net.lamgc.scalabot.util.ArtifactSerializer +import net.lamgc.scalabot.util.AuthenticationSerializer +import net.lamgc.scalabot.util.MavenRepositoryConfigSerializer import net.lamgc.scalabot.util.ProxyTypeSerializer import org.eclipse.aether.artifact.Artifact +import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.Proxy +import org.eclipse.aether.repository.RemoteRepository import org.telegram.telegrambots.bots.DefaultBotOptions import org.telegram.telegrambots.meta.ApiConstants import java.io.File +import java.net.URL import java.nio.charset.StandardCharsets import java.util.concurrent.atomic.AtomicBoolean @@ -76,18 +81,56 @@ internal data class ProxyConfig( internal data class MetricsConfig( val enable: Boolean = false, val port: Int = 9386, - val bindAddress: String? = null + val bindAddress: String? = "0.0.0.0" ) +/** + * Maven 远端仓库配置. + * @property url 仓库地址. + * @property proxy 访问仓库所使用的代理, 仅支持 http/https 代理. + * @property layout 仓库布局版本, Maven 2 及以上使用 `default`, Maven 1 使用 `legacy`. + */ +internal data class MavenRepositoryConfig( + val url: URL, + val proxy: Proxy? = Proxy("http", "127.0.0.1", 1080), + val layout: String = "default", + // 可能要设计个 type 来判断解析成什么类型的 Authentication. + val authentication: Authentication? = null +) { + + fun toRemoteRepository(): RemoteRepository { + val builder = RemoteRepository.Builder(null, checkRepositoryLayout(layout), url.toString()) + if (proxy != null) { + builder.setProxy(proxy) + } else if (Const.config.proxy.type == DefaultBotOptions.ProxyType.HTTP) { + builder.setProxy(Const.config.proxy.toAetherProxy()) + } + return builder.build() + } + + private companion object { + fun checkRepositoryLayout(layoutType: String): String { + val type = layoutType.trim().lowercase() + if (type != "default" && type != "legacy") { + throw IllegalArgumentException("Invalid layout type (expecting 'default' or 'legacy')") + } + return type + } + } +} + /** * ScalaBot App 配置. * * App 配置信息与 BotConfig 分开, 分别存储在各自单独的文件中. * @property proxy Telegram API 代理配置. + * @property metrics 运行指标数据配置. 可通过时序数据库记录运行数据. + * @property mavenRepositories Maven 远端仓库配置. */ internal data class AppConfig( val proxy: ProxyConfig = ProxyConfig(), - val metrics: MetricsConfig = MetricsConfig() + val metrics: MetricsConfig = MetricsConfig(), + val mavenRepositories: List = emptyList() ) /** @@ -119,7 +162,15 @@ internal enum class AppPaths( DEFAULT_CONFIG_APPLICATION({ "$DATA_ROOT/config.json" }, { if (!file.exists()) { file.bufferedWriter(StandardCharsets.UTF_8).use { - GsonConst.botConfigGson.toJson(AppConfig(), it) + GsonConst.botConfigGson.toJson( + AppConfig( + mavenRepositories = listOf( + MavenRepositoryConfig( + URL(MavenRepositoryExtensionFinder.MAVEN_CENTRAL_URL) + ) + ) + ), it + ) } } }), @@ -217,6 +268,8 @@ private object GsonConst { val appConfigGson: Gson = baseGson.newBuilder() .registerTypeAdapter(DefaultBotOptions.ProxyType::class.java, ProxyTypeSerializer) + .registerTypeAdapter(MavenRepositoryConfig::class.java, MavenRepositoryConfigSerializer) + .registerTypeAdapter(Authentication::class.java, AuthenticationSerializer) .create() val botConfigGson: Gson = baseGson.newBuilder() diff --git a/scalabot-app/src/main/kotlin/AppMain.kt b/scalabot-app/src/main/kotlin/AppMain.kt index 8a78a3b..e8d85d9 100644 --- a/scalabot-app/src/main/kotlin/AppMain.kt +++ b/scalabot-app/src/main/kotlin/AppMain.kt @@ -53,6 +53,11 @@ internal class Launcher : AutoCloseable { private val botApi = TelegramBotsApi(DefaultBotSession::class.java) private val botSessionMap = mutableMapOf() + private val remoteRepositories = Const.config.mavenRepositories + .map(MavenRepositoryConfig::toRemoteRepository) + .toMutableList().apply { + add(MavenRepositoryExtensionFinder.getMavenCentralRepository(proxy = Const.config.proxy.toAetherProxy())) + }.toList() @Synchronized fun launch(): Boolean { @@ -94,6 +99,13 @@ internal class Launcher : AutoCloseable { } } val account = botConfig.account + val extensionPackageFinders = setOf( + MavenRepositoryExtensionFinder( + remoteRepositories = remoteRepositories, + proxy = Const.config.proxy.toAetherProxy() + ) + ) + val bot = ScalaBot( account.name, account.token, @@ -101,6 +113,7 @@ internal class Launcher : AutoCloseable { BotDBMaker.getBotMaker(account), botOption, botConfig.extensions, + extensionPackageFinders, botConfig.disableBuiltInAbility ) botSessionMap[bot] = botApi.registerBot(bot) diff --git a/scalabot-app/src/main/kotlin/ExtensionComponents.kt b/scalabot-app/src/main/kotlin/ExtensionComponents.kt index ae7ca15..7b30022 100644 --- a/scalabot-app/src/main/kotlin/ExtensionComponents.kt +++ b/scalabot-app/src/main/kotlin/ExtensionComponents.kt @@ -4,7 +4,6 @@ import mu.KotlinLogging import net.lamgc.scalabot.extension.BotExtensionFactory import net.lamgc.scalabot.util.getPriority import org.eclipse.aether.artifact.Artifact -import org.eclipse.aether.repository.LocalRepository import org.telegram.abilitybots.api.util.AbilityExtension import java.io.File import java.io.FileNotFoundException @@ -18,18 +17,18 @@ import java.util.concurrent.atomic.AtomicInteger internal class ExtensionLoader( private val bot: ScalaBot, private val extensionsDataFolder: File = AppPaths.DATA_EXTENSIONS.file, - private val extensionsPath: File = AppPaths.EXTENSIONS.file + private val extensionsPath: File = AppPaths.EXTENSIONS.file, + private val extensionFinders: Set = setOf() ) { private val log = KotlinLogging.logger { } - private val finders: Set = setOf( + private val finders: Set = mutableSetOf( FileNameFinder, MavenMetaInformationFinder, MavenRepositoryExtensionFinder( - LocalRepository("${System.getProperty("user.home")}/.m2/repository"), proxy = Const.config.proxy.toAetherProxy() ) - ) + ).apply { addAll(extensionFinders) }.toSet() fun getExtensions(): Set { val extensionEntries = mutableSetOf() diff --git a/scalabot-app/src/main/kotlin/ExtensionFinders.kt b/scalabot-app/src/main/kotlin/ExtensionFinders.kt index f8e08a0..b109a2b 100644 --- a/scalabot-app/src/main/kotlin/ExtensionFinders.kt +++ b/scalabot-app/src/main/kotlin/ExtensionFinders.kt @@ -234,7 +234,7 @@ internal object MavenMetaInformationFinder : ExtensionPackageFinder { */ @FinderRules(priority = FinderPriority.REMOTE) internal class MavenRepositoryExtensionFinder( - private val localRepository: LocalRepository, + private val localRepository: LocalRepository = LocalRepository("${System.getProperty("user.home")}/.m2/repository"), private val proxy: Proxy? = null, private val remoteRepositories: List = listOf(getMavenCentralRepository(proxy)), ) : ExtensionPackageFinder { diff --git a/scalabot-app/src/main/kotlin/ScalaBot.kt b/scalabot-app/src/main/kotlin/ScalaBot.kt index 42a6307..c2f0979 100644 --- a/scalabot-app/src/main/kotlin/ScalaBot.kt +++ b/scalabot-app/src/main/kotlin/ScalaBot.kt @@ -30,11 +30,15 @@ internal class ScalaBot( db: DBContext, options: DefaultBotOptions, val extensions: Set, + extensionFinders: Set, disableBuiltInAbility: Boolean ) : AbilityBot(token, name, db, if (disableBuiltInAbility) BareboneToggle() else DefaultToggle(), options) { - private val extensionLoader = ExtensionLoader(this) + private val extensionLoader = ExtensionLoader( + bot = this, + extensionFinders = extensionFinders + ) init { val extensionEntries = extensionLoader.getExtensions() diff --git a/scalabot-app/src/main/kotlin/util/Serializers.kt b/scalabot-app/src/main/kotlin/util/Serializers.kt index 5c69613..c8c776e 100644 --- a/scalabot-app/src/main/kotlin/util/Serializers.kt +++ b/scalabot-app/src/main/kotlin/util/Serializers.kt @@ -1,10 +1,16 @@ package net.lamgc.scalabot.util import com.google.gson.* +import mu.KotlinLogging +import net.lamgc.scalabot.MavenRepositoryConfig import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.DefaultArtifact +import org.eclipse.aether.repository.Authentication +import org.eclipse.aether.repository.Proxy +import org.eclipse.aether.util.repository.AuthenticationBuilder import org.telegram.telegrambots.bots.DefaultBotOptions import java.lang.reflect.Type +import java.net.URL internal object ProxyTypeSerializer : JsonDeserializer, JsonSerializer { @@ -55,3 +61,105 @@ internal object ArtifactSerializer : JsonSerializer, JsonDeserializer< } +internal object AuthenticationSerializer : JsonDeserializer { + + private val log = KotlinLogging.logger { } + + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Authentication? { + val builder = AuthenticationBuilder() + when (json) { + is JsonArray -> { + for (element in json) { + if (element is JsonArray) { + builder.addCustom(jsonArrayToAuthentication(element)) + } else if (element is JsonObject) { + jsonToAuthentication(element, builder) + } + } + } + is JsonObject -> { + jsonToAuthentication(json, builder) + } + else -> { + throw JsonParseException("Unsupported JSON data type: ${json::class.java}") + } + } + return builder.build() + } + + private fun jsonArrayToAuthentication(jsonArray: JsonArray): Authentication { + val builder = AuthenticationBuilder() + for (element in jsonArray) { + when (element) { + is JsonObject -> jsonToAuthentication(element, builder) + is JsonArray -> builder.addCustom(jsonArrayToAuthentication(element)) + else -> log.warn { "不支持的 Json 类型: ${element::class.java}" } + } + } + return builder.build() + } + + private const val KEY_TYPE = "type" + + private fun jsonToAuthentication(json: JsonObject, builder: AuthenticationBuilder) { + if (!json.has(KEY_TYPE)) { + log.warn { "缺少 type 字段, 无法判断 Maven 认证信息类型." } + return + } else if (!json.get(KEY_TYPE).isJsonPrimitive) { + log.warn { "type 字段类型错误(应为 Primitive 类型), 无法判断 Maven 认证信息类型.(实际类型: `${json::class.java}`)" } + return + } + + when (json.get(KEY_TYPE).asString.trim().lowercase()) { + "string" -> { + builder.addString(checkJsonKey(json, "key"), checkJsonKey(json, "value")) + } + "secret" -> { + builder.addSecret(checkJsonKey(json, "key"), checkJsonKey(json, "value")) + } + } + + } +} + +private fun checkJsonKey(json: JsonObject, key: String): String { + if (!json.has(key)) { + throw JsonParseException("Required field does not exist: $key") + } else if (!json.get(key).isJsonPrimitive) { + throw JsonParseException("Wrong field `$key` type: ${json.get(key)::class.java}") + } + return json.get(key).asString +} + +internal object MavenRepositoryConfigSerializer + : JsonDeserializer { + + override fun deserialize( + json: JsonElement, + typeOfT: Type, + context: JsonDeserializationContext + ): MavenRepositoryConfig { + return when (json) { + is JsonObject -> { + MavenRepositoryConfig( + url = URL(checkJsonKey(json, "url")), + proxy = if (json.has("proxy") && json.get("proxy").isJsonObject) + context.deserialize( + json.getAsJsonObject("proxy"), Proxy::class.java + ) else null, + layout = json.get("layout").asString ?: "default", + authentication = if (json.has("authentication") && json.get("authentication").isJsonObject) + context.deserialize( + json.getAsJsonObject("authentication"), Authentication::class.java + ) else null + ) + } + is JsonPrimitive -> { + MavenRepositoryConfig(URL(json.asString)) + } + else -> { + throw JsonParseException("Unsupported Maven warehouse configuration type.") + } + } + } +}