From 8db0d353deed67ec3829527616ba3e279c785f8a Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Tue, 16 Jun 2026 19:14:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(sdk-bugcollect):=20Gradle=20plugin=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BB=8E=20config.xuqm=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E8=A7=A3=E5=AF=86=E8=AF=BB=E5=8F=96=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mode A 项目放置了 assets/xuqm/config.xuqm(PBKDF2+AES-256-GCM 加密), Gradle plugin 现可直接解密读取 appKey / platformUrl,无需额外的 xuqm.config.json。优先级:config.xuqm > xuqm.config.json > gradle.properties。 Co-Authored-By: Claude Sonnet 4.6 --- .../bugcollect/gradle/XuqmBugCollectPlugin.kt | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/gradle/XuqmBugCollectPlugin.kt b/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/gradle/XuqmBugCollectPlugin.kt index bcf5541..1fb23ee 100644 --- a/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/gradle/XuqmBugCollectPlugin.kt +++ b/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/gradle/XuqmBugCollectPlugin.kt @@ -4,14 +4,60 @@ import com.android.build.gradle.AppExtension import org.gradle.api.Plugin import org.gradle.api.Project import java.io.File +import java.util.Base64 +import javax.crypto.Cipher +import javax.crypto.SecretKeyFactory +import javax.crypto.spec.GCMParameterSpec +import javax.crypto.spec.PBEKeySpec +import javax.crypto.spec.SecretKeySpec class XuqmBugCollectPlugin : Plugin { + private fun base64UrlDecode(s: String): ByteArray { + val padded = s.replace('-', '+').replace('_', '/') + .let { it + "=".repeat((4 - it.length % 4) % 4) } + return Base64.getDecoder().decode(padded) + } + + /** + * Read appKey/platformUrl from the SDK Mode A encrypted config file: + * app/src/main/assets/xuqm/config.xuqm + * Format: XUQM-CONFIG-V1.{salt_b64url}.{iv_b64url}.{ciphertext_b64url} + * Decrypted JSON contains: appKey, baseUrl, serverUrl, ... + */ + private fun readEncryptedConfigFile(projectDir: File): Pair { + val f = File(projectDir, "src/main/assets/xuqm/config.xuqm").takeIf { it.exists() } + ?: return null to null + val content = runCatching { f.readText().trim() }.getOrElse { return null to null } + val parts = content.split(".") + if (parts.size != 4 || parts[0] != "XUQM-CONFIG-V1") return null to null + + return runCatching { + val salt = base64UrlDecode(parts[1]) + val iv = base64UrlDecode(parts[2]) + val ciphertext = base64UrlDecode(parts[3]) + val passphrase = "xuqm-config-file-v1.2026.internal" + + val keySpec = PBEKeySpec(passphrase.toCharArray(), salt, 120_000, 256) + val keyBytes = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keySpec).encoded + val aesKey = SecretKeySpec(keyBytes, "AES") + + val cipher = Cipher.getInstance("AES/GCM/NoPadding") + cipher.init(Cipher.DECRYPT_MODE, aesKey, GCMParameterSpec(128, iv)) + val json = String(cipher.doFinal(ciphertext)) + + val appKey = Regex(""""appKey"\s*:\s*"([^"]+)"""").find(json)?.groupValues?.get(1) + val platformUrl = Regex(""""baseUrl"\s*:\s*"([^"]+)"""").find(json)?.groupValues?.get(1) + ?: Regex(""""serverUrl"\s*:\s*"([^"]+)"""").find(json)?.groupValues?.get(1) + appKey to platformUrl + }.getOrElse { null to null } + } + /** Read appKey/platformUrl from xuqm.config.json at project root (optional). */ private fun readConfigFile(rootDir: File): Pair { val f = File(rootDir, "xuqm.config.json").takeIf { it.exists() } ?: return null to null val text = runCatching { f.readText() }.getOrElse { return null to null } - val appKey = Regex(""""appKey"\s*:\s*"([^"]+)"""").find(text)?.groupValues?.get(1) + val appKey = Regex(""""appKey"\s*:\s*"([^"]+)"""").find(text)?.groupValues?.get(1) val platformUrl = Regex(""""platformUrl"\s*:\s*"([^"]+)"""").find(text)?.groupValues?.get(1) return appKey to platformUrl } @@ -19,7 +65,10 @@ class XuqmBugCollectPlugin : Plugin { override fun apply(target: Project) { val android = target.extensions.findByType(AppExtension::class.java) ?: return - val (fileAppKey, filePlatformUrl) = readConfigFile(target.rootDir) + // Priority: config.xuqm (Mode A encrypted) > xuqm.config.json > gradle.properties + val (encAppKey, encPlatformUrl) = readEncryptedConfigFile(target.projectDir) + val (fileAppKey, filePlatformUrl) = if (encAppKey != null) encAppKey to encPlatformUrl + else readConfigFile(target.rootDir) @Suppress("DEPRECATION") android.applicationVariants.all { variant -> @@ -30,7 +79,7 @@ class XuqmBugCollectPlugin : Plugin { task.group = "xuqm" task.description = "Upload ProGuard mapping to BugCollect service (${variant.name})" - // Priority: xuqm.config.json > gradle.properties XUQM_APP_KEY > gradle.properties xuqm.appKey + // Priority: config.xuqm > xuqm.config.json > gradle.properties XUQM_APP_KEY > gradle.properties xuqm.appKey task.appKey.set( fileAppKey ?: target.findProperty("XUQM_APP_KEY")?.toString()