From c7e7e0e953dd8e3e95da7fdc8af37293972e9795 Mon Sep 17 00:00:00 2001
From: HanaHime <62001729+HanaKDev@users.noreply.github.com>
Date: Sun, 12 Jul 2026 13:23:43 +0800
Subject: [PATCH 01/17] =?UTF-8?q?feat:=20=E6=9D=A5=E8=87=AA=E7=BE=A4U?=
=?UTF-8?q?=E7=9A=84=20=EF=BC=9F=E7=9B=B2=E7=9B=92=3F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 87 ++++++++++++
.../report/BlindBoxCommandController.kt | 59 ++++++++
.../bilibili/report/BlindBoxReportHandlers.kt | 55 ++++++++
.../bot/bilibili/report/BlindBoxStatsStore.kt | 131 ++++++++++++++++++
4 files changed, 332 insertions(+)
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxCommandController.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxReportHandlers.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxStatsStore.kt
diff --git a/pom.xml b/pom.xml
index fd2dc50..0a48a0a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,9 @@
17
+ 2.2.21
+ 17
+ 1.18.42
@@ -52,6 +55,16 @@
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+ ${kotlin.version}
+
com.starlwr
starbot-core
@@ -89,6 +102,76 @@
${project.artifactId}-${project.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+ true
+
+ ${kotlin.compiler.jvmTarget}
+ true
+
+ spring
+
+
+
+
+ kotlin-compile
+ process-sources
+ compile
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/main/java
+
+
+
+
+ kotlin-test-compile
+ process-test-sources
+ test-compile
+
+
+ ${project.basedir}/src/test/kotlin
+ ${project.basedir}/src/test/java
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${java.version}
+ true
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ -Djdk.net.URLClassPath.disableClassPathURLCheck=true
+ false
+
+
com.starlwr
starbot-plugin-processor
@@ -116,6 +199,10 @@
${project.build.directory}
+
+ plugin.json
+ dependency.json
+
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxCommandController.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxCommandController.kt
new file mode 100644
index 0000000..dd035c4
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxCommandController.kt
@@ -0,0 +1,59 @@
+package com.starlwr.bot.bilibili.report
+
+import com.alibaba.fastjson2.JSONObject
+import com.starlwr.bot.core.datasource.AbstractDataSource
+import com.starlwr.bot.core.enums.PushTargetType
+import com.starlwr.bot.core.plugin.StarBotComponent
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RestController
+import java.time.LocalDate
+
+/** OneBot HTTP adapter retained from the community edition, with its broken encoding repaired. */
+@RestController
+@StarBotComponent
+class BlindBoxCommandController(private val dataSource: AbstractDataSource) {
+ @PostMapping("/blindbox/onebot")
+ fun onOneBotEvent(@RequestBody event: JSONObject): JSONObject {
+ val operation = JSONObject()
+ if (event.getString("post_type") != "message") return operation
+ val raw = event.getString("raw_message")?.trim() ?: return operation
+ if (!raw.startsWith(COMMAND)) return operation
+ val range = parseRange(raw.removePrefix(COMMAND).trim()) ?: return operation.apply {
+ put("reply", HELP); put("auto_escape", false)
+ }
+ val type = if (event.getString("message_type") == "private") PushTargetType.FRIEND else PushTargetType.GROUP
+ val number = if (type == PushTargetType.GROUP) event.getLong("group_id") else event.getLong("user_id")
+ val uids = dataSource.allUsers.asSequence().flatMap { user ->
+ (user.targets ?: emptyList()).asSequence().filter { it.platform == PLATFORM && it.num == number && it.type == type }
+ .map { user.uid }
+ }.distinct().toList()
+ val stats = BlindBoxStatsStore.query(uids, range.start, range.end)
+ val reply = if (uids.isEmpty()) "当前会话没有关联直播间,无法查询盲盒统计"
+ else if (stats.boxCount == 0L) "盲盒统计 ${range.label}\n暂无记录"
+ else "盲盒统计 ${range.label}\n盲盒次数: ${stats.boxCount}\n盲盒成本: ${BlindBoxStatsStore.format(stats.cost)}" +
+ "\n开出价值: ${BlindBoxStatsStore.format(stats.value)}\n盈亏: ${BlindBoxStatsStore.format(stats.profit)}" +
+ "\n参与人数: ${stats.userCount}\nTOP礼物: ${stats.topGifts(10)}"
+ return operation.apply { put("reply", reply); put("auto_escape", false); put("at_sender", false) }
+ }
+
+ private fun parseRange(body: String): Range? {
+ val today = LocalDate.now()
+ if (body.isBlank() || body.equals("help", true) || body == "帮助") return null
+ if (body in setOf("一周", "周", "7天")) return Range(today.minusDays(6), today, "最近一周")
+ if (body in setOf("一月", "一个月", "月", "30天")) return Range(today.minusDays(29), today, "最近一个月")
+ return runCatching {
+ val dates = body.split(Regex("\\s+")).map(LocalDate::parse)
+ val first = dates.first(); val last = dates.getOrElse(1) { first }
+ val start = minOf(first, last); val end = maxOf(first, last)
+ Range(start, end, if (start == end) "$start" else "$start 至 $end")
+ }.getOrNull()
+ }
+
+ private data class Range(val start: LocalDate, val end: LocalDate, val label: String)
+ private companion object {
+ const val COMMAND = "盲盒统计"
+ const val PLATFORM = "qq-onebot"
+ const val HELP = "盲盒统计命令:\n盲盒统计 一周\n盲盒统计 一月\n盲盒统计 2026-07-10\n盲盒统计 2026-07-01 2026-07-10"
+ }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxReportHandlers.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxReportHandlers.kt
new file mode 100644
index 0000000..fe13399
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxReportHandlers.kt
@@ -0,0 +1,55 @@
+package com.starlwr.bot.bilibili.report
+
+import com.alibaba.fastjson2.JSONObject
+import com.starlwr.bot.core.event.StarBotExternalBaseEvent
+import com.starlwr.bot.core.event.live.common.RandomGiftEvent
+import com.starlwr.bot.core.handler.DefaultHandlerForEvent
+import com.starlwr.bot.core.handler.StarBotEventHandler
+import com.starlwr.bot.core.model.Message
+import com.starlwr.bot.core.model.PushMessage
+import com.starlwr.bot.core.plugin.StarBotComponent
+import com.starlwr.bot.core.sender.StarBotMessageSender
+
+@StarBotComponent
+@DefaultHandlerForEvent(event = "com.starlwr.bot.bilibili.event.live.BilibiliRandomGiftEvent")
+class BlindBoxRecordHandler : StarBotEventHandler {
+ override fun handle(event: StarBotExternalBaseEvent, pushMessage: PushMessage) {
+ if (event is RandomGiftEvent) BlindBoxStatsStore.record(event)
+ }
+ override fun getDefaultParams() = JSONObject().apply { put("note", "record blind-box statistics") }
+}
+
+/** Select this handler for the live-on event to start a clean report session. */
+@StarBotComponent
+class BlindBoxLiveOnResetHandler : StarBotEventHandler {
+ override fun handle(event: StarBotExternalBaseEvent, pushMessage: PushMessage) = BlindBoxStatsStore.reset(event)
+ override fun getDefaultParams() = JSONObject().apply { put("note", "reset blind-box statistics on live start") }
+}
+
+/** Select this handler for the live-off event to send the migrated blind-box section. */
+@StarBotComponent
+class BlindBoxLiveOffReportHandler(private val sender: StarBotMessageSender) : StarBotEventHandler {
+ override fun handle(event: StarBotExternalBaseEvent, pushMessage: PushMessage) {
+ val params = getDefaultParams().apply { pushMessage.paramsJsonObject?.let(::putAll) }
+ val stats = BlindBoxStatsStore.snapshot(event)
+ if (stats == null && params.getBooleanValue("only_when_non_empty", true)) return
+ if (stats != null && stats.boxCount == 0L && params.getBooleanValue("only_when_non_empty", true)) return
+
+ val raw = params.getString("message")
+ val content = raw
+ .replace("{uname}", stats?.uname ?: event.source.uname ?: "")
+ .replace("{box_count}", (stats?.boxCount ?: 0).toString())
+ .replace("{cost}", BlindBoxStatsStore.format(stats?.cost ?: 0.0))
+ .replace("{value}", BlindBoxStatsStore.format(stats?.value ?: 0.0))
+ .replace("{profit}", BlindBoxStatsStore.format(stats?.profit ?: 0.0))
+ .replace("{user_count}", (stats?.userCount ?: 0).toString())
+ .replace("{top_gifts}", stats?.topGifts(params.getIntValue("top_limit", 5)) ?: "无")
+ val target = pushMessage.target
+ Message.create(target.platform, target.type, target.num, content).forEach(sender::send)
+ }
+
+ override fun getDefaultParams() = JSONObject().apply {
+ put("only_when_non_empty", true); put("top_limit", 5)
+ put("message", "{uname} 本场盲盒统计\n盲盒次数: {box_count}\n盲盒成本: {cost}\n开出价值: {value}\n盈亏: {profit}\n参与人数: {user_count}\nTOP礼物: {top_gifts}")
+ }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxStatsStore.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxStatsStore.kt
new file mode 100644
index 0000000..6a4a2fc
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxStatsStore.kt
@@ -0,0 +1,131 @@
+package com.starlwr.bot.bilibili.report
+
+import com.alibaba.fastjson2.JSON
+import com.alibaba.fastjson2.JSONObject
+import com.starlwr.bot.core.event.StarBotExternalBaseEvent
+import com.starlwr.bot.core.event.live.common.RandomGiftEvent
+import com.starlwr.bot.core.model.GiftInfo
+import com.starlwr.bot.core.model.LiveStreamerInfo
+import org.slf4j.LoggerFactory
+import java.nio.charset.StandardCharsets
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.StandardOpenOption
+import java.text.DecimalFormat
+import java.time.LocalDate
+import java.time.ZoneId
+import java.util.concurrent.ConcurrentHashMap
+
+/** v2 blind-box accounting port. The purchased box and opened gift are deliberately kept distinct. */
+object BlindBoxStatsStore {
+ private val log = LoggerFactory.getLogger(javaClass)
+ private val sessions = ConcurrentHashMap()
+ private val seenEvents = ConcurrentHashMap.newKeySet()
+ private val money = DecimalFormat("0.##")
+ private val recordFile: Path = Path.of("blindbox-stats", "records.jsonl")
+
+ @JvmStatic fun reset(event: StarBotExternalBaseEvent) { sessions.remove(roomKey(event)) }
+
+ @JvmStatic fun record(event: RandomGiftEvent) {
+ if (event.source == null || !seenEvents.add(System.identityHashCode(event))) return
+ sessions.computeIfAbsent(roomKey(event)) { RoomStats(event.source) }.record(event)
+ append(event)
+ if (seenEvents.size > 4096) seenEvents.clear()
+ }
+
+ @JvmStatic fun snapshot(event: StarBotExternalBaseEvent): RoomStats? = sessions[roomKey(event)]?.copy()
+
+ @JvmStatic fun query(uids: Collection, start: LocalDate, end: LocalDate): RoomStats {
+ val result = RoomStats(LiveStreamerInfo(null, "关联直播间", null))
+ if (uids.isEmpty() || !Files.exists(recordFile)) return result
+ Files.newBufferedReader(recordFile, StandardCharsets.UTF_8).useLines { lines ->
+ lines.filter(String::isNotBlank).forEach { line ->
+ runCatching {
+ val json = JSON.parseObject(line)
+ val date = LocalDate.parse(json.getString("date"))
+ if (json.getLong("uid") in uids && date in start..end) result.add(json)
+ }.onFailure { log.warn("跳过无法解析的盲盒统计记录: {}", line, it) }
+ }
+ }
+ return result
+ }
+
+ @JvmStatic fun format(value: Double): String = synchronized(money) { money.format(value) }
+
+ private fun roomKey(event: StarBotExternalBaseEvent): String =
+ "${event.platform}:${event.source.uid}:${event.source.roomId}"
+
+ @Synchronized private fun append(event: RandomGiftEvent) {
+ runCatching {
+ Files.createDirectories(recordFile.parent)
+ Files.writeString(recordFile, values(event).toJson(event).toJSONString() + System.lineSeparator(),
+ StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND)
+ }.onFailure { log.warn("写入盲盒统计记录失败", it) }
+ }
+
+ private fun values(event: RandomGiftEvent): Values {
+ // Core contract: randomGiftInfo = purchased box, giftInfo = opened result.
+ val box = event.randomGiftInfo
+ val gift = event.giftInfo
+ val boxCount = box.countOrOne()
+ val giftCount = gift.countOrOne()
+ val cost = event.price ?: box.total(boxCount)
+ val profit = event.profit ?: (gift.total(giftCount) - cost)
+ return Values(box, gift, boxCount, giftCount, cost, cost + profit, profit)
+ }
+
+ private data class Values(
+ val box: GiftInfo?, val gift: GiftInfo?, val boxCount: Int, val giftCount: Int,
+ val cost: Double, val value: Double, val profit: Double
+ ) {
+ fun toJson(event: RandomGiftEvent) = JSONObject().apply {
+ val time = java.time.Instant.ofEpochMilli(event.timestamp)
+ put("time", time.toString()); put("date", LocalDate.ofInstant(time, ZoneId.systemDefault()).toString())
+ put("platform", event.platform); put("uid", event.source.uid); put("roomId", event.source.roomId)
+ put("uname", event.source.uname); put("senderUid", event.sender?.uid); put("senderName", event.sender?.uname)
+ put("boxName", box?.name); put("boxCount", boxCount); put("giftName", gift?.name); put("giftCount", giftCount)
+ put("cost", cost); put("value", value); put("profit", profit)
+ }
+ }
+
+ class RoomStats internal constructor(source: LiveStreamerInfo) {
+ val uid: Long? = source.uid
+ val roomId: Long? = source.roomId
+ val uname: String? = source.uname
+ var boxCount: Long = 0; private set
+ var cost: Double = 0.0; private set
+ var value: Double = 0.0; private set
+ var profit: Double = 0.0; private set
+ private val gifts = ConcurrentHashMap()
+ private val users = ConcurrentHashMap.newKeySet()
+
+ @Synchronized internal fun record(event: RandomGiftEvent) {
+ val v = values(event)
+ boxCount += v.boxCount; cost += v.cost; value += v.value; profit += v.profit
+ v.gift?.name?.takeIf(String::isNotBlank)?.let { gifts.merge(it, v.giftCount.toLong(), Long::plus) }
+ event.sender?.let { (it.uid?.toString() ?: it.uname)?.takeIf(String::isNotBlank)?.let(users::add) }
+ }
+
+ @Synchronized internal fun add(json: JSONObject) {
+ boxCount += json.getIntValue("boxCount", 1).coerceAtLeast(1)
+ cost += json.getDoubleValue("cost"); value += json.getDoubleValue("value"); profit += json.getDoubleValue("profit")
+ json.getString("giftName")?.takeIf(String::isNotBlank)?.let {
+ gifts.merge(it, json.getIntValue("giftCount", 1).coerceAtLeast(1).toLong(), Long::plus)
+ }
+ (json.getString("senderUid")?.takeIf(String::isNotBlank) ?: json.getString("senderName"))
+ ?.takeIf(String::isNotBlank)?.let(users::add)
+ }
+
+ @Synchronized internal fun copy() = RoomStats(LiveStreamerInfo(uid, uname, roomId)).also {
+ it.boxCount = boxCount; it.cost = cost; it.value = value; it.profit = profit
+ it.gifts.putAll(gifts); it.users.addAll(users)
+ }
+
+ val userCount: Int get() = users.size
+ fun topGifts(limit: Int): String = gifts.entries.sortedByDescending { it.value }.take(limit.coerceAtLeast(1))
+ .joinToString(", ") { "${it.key} x${it.value}" }.ifEmpty { "无" }
+ }
+
+ private fun GiftInfo?.countOrOne() = this?.count?.takeIf { it > 0 } ?: 1
+ private fun GiftInfo?.total(count: Int) = (this?.price ?: 0.0) * count
+}
From d51f2b6cbb151d7fd35f6ff5c92f48311ba1000d Mon Sep 17 00:00:00 2001
From: HanaHime <62001729+HanaKDev@users.noreply.github.com>
Date: Mon, 13 Jul 2026 22:47:55 +0800
Subject: [PATCH 02/17] =?UTF-8?q?=E5=BC=B9=E5=B9=95=E7=BB=9F=E8=AE=A1(WIP)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
屎山堆积...
---
.gitignore | 7 +-
pom.xml | 12 +
.../StarBotBilibiliThreadPoolConfig.java | 16 +
.../handler/BilibiliDynamicPushHandler.java | 1 -
.../handler/BilibiliLiveOffPushHandler.java | 1 -
.../handler/BilibiliLiveOnPushHandler.java | 1 -
.../starlwr/bot/bilibili/model/Cookies.java | 33 +
.../BilibiliBackupLivePushService.java | 5 +-
.../bot/bilibili/util/BilibiliApiUtil.java | 60 +-
.../bilibili/StarBotBilibiliApplication.kt | 45 +
.../bot/bilibili/StarBotNativeRuntimeHints.kt | 17 +
.../credential/BilibiliCredentialService.kt | 283 +
.../report/BlindBoxCommandController.kt | 40 +-
.../bilibili/report/BlindBoxReportHandlers.kt | 29 +-
.../bot/bilibili/report/BlindBoxStatsStore.kt | 131 -
.../report/BufferedLiveReportDataDriver.kt | 70 +
.../report/JdbcLiveReportDataDriver.kt | 103 +
.../bilibili/report/LegacyReportMigrator.kt | 83 +
.../report/LiveReportBaselineCollector.kt | 24 +
.../bilibili/report/LiveReportCollector.kt | 102 +
.../bot/bilibili/report/LiveReportConfig.kt | 55 +
.../bilibili/report/LiveReportDataDriver.kt | 42 +
.../report/LiveReportDemandService.kt | 33 +
.../bot/bilibili/report/LiveReportModel.kt | 95 +
.../bot/bilibili/report/LiveReportPainter.kt | 128 +
.../bilibili/report/LiveReportPushHandler.kt | 76 +
.../report/LiveReportStorageConfig.kt | 72 +
.../report/RedisLiveReportDataDriver.kt | 101 +
.../bot/bilibili/report/ReportArchive.kt | 37 +
.../reachability-metadata.json | 6852 +++++++++++++++++
src/main/resources/application.yml | 20 +-
.../report/LiveReportDataDriverTest.kt | 49 +
.../report/RedisLiveReportDataDriverTest.kt | 28 +
.../bot/bilibili/report/ReportArchiveTest.kt | 15 +
34 files changed, 8493 insertions(+), 173 deletions(-)
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/StarBotBilibiliApplication.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/StarBotNativeRuntimeHints.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/credential/BilibiliCredentialService.kt
delete mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxStatsStore.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/BufferedLiveReportDataDriver.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/JdbcLiveReportDataDriver.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/LegacyReportMigrator.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportBaselineCollector.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportCollector.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportConfig.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportDataDriver.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportDemandService.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportModel.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportPainter.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportPushHandler.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportStorageConfig.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/RedisLiveReportDataDriver.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/ReportArchive.kt
create mode 100644 src/main/resources/META-INF/native-image/com.starlwr/starbot-bilibili-agent/reachability-metadata.json
create mode 100644 src/test/kotlin/com/starlwr/bot/bilibili/report/LiveReportDataDriverTest.kt
create mode 100644 src/test/kotlin/com/starlwr/bot/bilibili/report/RedisLiveReportDataDriverTest.kt
create mode 100644 src/test/kotlin/com/starlwr/bot/bilibili/report/ReportArchiveTest.kt
diff --git a/.gitignore b/.gitignore
index 8ba78bd..f192b00 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,9 @@ DynamicDebug/
data.json
cookies.json
datasource.json
-src/main/resources/application-dev.yml
\ No newline at end of file
+config/*.sqlite3
+config/*.sqlite3-*
+data/live-report-recovery-*.pb
+src/main/resources/application-dev.yml
+release/
+release-template/
diff --git a/pom.xml b/pom.xml
index 0a48a0a..caa5aab 100644
--- a/pom.xml
+++ b/pom.xml
@@ -91,6 +91,18 @@
caffeine
3.2.0
+
+ org.xerialsqlite-jdbc3.51.1.0
+
+
+ com.mysqlmysql-connector-jruntime
+
+
+ io.lettucelettuce-core6.3.2.RELEASE
+
+
+ com.google.protobufprotobuf-java4.33.2
+
org.springframework.boot
diff --git a/src/main/java/com/starlwr/bot/bilibili/config/StarBotBilibiliThreadPoolConfig.java b/src/main/java/com/starlwr/bot/bilibili/config/StarBotBilibiliThreadPoolConfig.java
index ac3df4a..bea1e8e 100644
--- a/src/main/java/com/starlwr/bot/bilibili/config/StarBotBilibiliThreadPoolConfig.java
+++ b/src/main/java/com/starlwr/bot/bilibili/config/StarBotBilibiliThreadPoolConfig.java
@@ -5,6 +5,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.scheduling.annotation.EnableAsync;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
@@ -14,6 +15,7 @@
*/
@Slf4j
@StarBotComponent
+@EnableAsync
public class StarBotBilibiliThreadPoolConfig {
private final StarBotBilibiliProperties properties;
@@ -35,6 +37,20 @@ public ThreadPoolTaskExecutor bilibiliThreadPool() {
return executor;
}
+ /** Isolates optional report API baselines from the live-room websocket executor. */
+ @Bean
+ public ThreadPoolTaskExecutor bilibiliLiveReportThreadPool() {
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ executor.setCorePoolSize(2);
+ executor.setMaxPoolSize(4);
+ executor.setQueueCapacity(1000);
+ executor.setKeepAliveSeconds(60);
+ executor.setThreadNamePrefix("bilibili-report-");
+ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
+ executor.initialize();
+ return executor;
+ }
+
private static class BilibiliWithLogCallerRunsPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
diff --git a/src/main/java/com/starlwr/bot/bilibili/handler/BilibiliDynamicPushHandler.java b/src/main/java/com/starlwr/bot/bilibili/handler/BilibiliDynamicPushHandler.java
index 803dd27..9045286 100644
--- a/src/main/java/com/starlwr/bot/bilibili/handler/BilibiliDynamicPushHandler.java
+++ b/src/main/java/com/starlwr/bot/bilibili/handler/BilibiliDynamicPushHandler.java
@@ -138,7 +138,6 @@ public void handle(StarBotExternalBaseEvent baseEvent, PushMessage pushMessage)
*
* @return 事件类型
*/
- @Override
public Class extends StarBotExternalBaseEvent> getEventType() {
return BilibiliDynamicUpdateEvent.class;
}
diff --git a/src/main/java/com/starlwr/bot/bilibili/handler/BilibiliLiveOffPushHandler.java b/src/main/java/com/starlwr/bot/bilibili/handler/BilibiliLiveOffPushHandler.java
index d5d89d1..1c72300 100644
--- a/src/main/java/com/starlwr/bot/bilibili/handler/BilibiliLiveOffPushHandler.java
+++ b/src/main/java/com/starlwr/bot/bilibili/handler/BilibiliLiveOffPushHandler.java
@@ -123,7 +123,6 @@ public void handle(StarBotExternalBaseEvent baseEvent, PushMessage pushMessage)
*
* @return 事件类型
*/
- @Override
public Class extends StarBotExternalBaseEvent> getEventType() {
return BilibiliLiveOffEvent.class;
}
diff --git a/src/main/java/com/starlwr/bot/bilibili/handler/BilibiliLiveOnPushHandler.java b/src/main/java/com/starlwr/bot/bilibili/handler/BilibiliLiveOnPushHandler.java
index eac1ff1..74415ab 100644
--- a/src/main/java/com/starlwr/bot/bilibili/handler/BilibiliLiveOnPushHandler.java
+++ b/src/main/java/com/starlwr/bot/bilibili/handler/BilibiliLiveOnPushHandler.java
@@ -120,7 +120,6 @@ public void handle(StarBotExternalBaseEvent baseEvent, PushMessage pushMessage)
*
* @return 事件类型
*/
- @Override
public Class extends StarBotExternalBaseEvent> getEventType() {
return BilibiliLiveOnEvent.class;
}
diff --git a/src/main/java/com/starlwr/bot/bilibili/model/Cookies.java b/src/main/java/com/starlwr/bot/bilibili/model/Cookies.java
index 6aec499..30fe174 100644
--- a/src/main/java/com/starlwr/bot/bilibili/model/Cookies.java
+++ b/src/main/java/com/starlwr/bot/bilibili/model/Cookies.java
@@ -3,6 +3,9 @@
import lombok.Getter;
import lombok.Setter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
/**
* Bilibili Cookies
*/
@@ -24,15 +27,45 @@ public class Cookies {
*/
private String buvid3;
+ private String buvid4;
+
+ private String dedeUserId;
+
+ /** Cookie refresh token, named ac_time_value by bilibili-api-python. */
+ private String acTimeValue;
+
+ private String bNut;
+
+ private String biliTicket;
+
+ private Long biliTicketExpires;
+
+ /** Forward-compatible cookie values not yet modelled explicitly. */
+ private Map extraCookies;
+
public Cookies() {
this.sessData = "";
this.biliJct = "";
this.buvid3 = "";
+ this.buvid4 = "";
+ this.dedeUserId = "";
+ this.acTimeValue = "";
+ this.bNut = "";
+ this.biliTicket = "";
+ this.biliTicketExpires = 0L;
+ this.extraCookies = new LinkedHashMap<>();
}
public Cookies(String sessData, String biliJct, String buvid3) {
this.sessData = sessData;
this.biliJct = biliJct;
this.buvid3 = buvid3;
+ this.buvid4 = "";
+ this.dedeUserId = "";
+ this.acTimeValue = "";
+ this.bNut = "";
+ this.biliTicket = "";
+ this.biliTicketExpires = 0L;
+ this.extraCookies = new LinkedHashMap<>();
}
}
diff --git a/src/main/java/com/starlwr/bot/bilibili/service/BilibiliBackupLivePushService.java b/src/main/java/com/starlwr/bot/bilibili/service/BilibiliBackupLivePushService.java
index fd0110d..0092427 100644
--- a/src/main/java/com/starlwr/bot/bilibili/service/BilibiliBackupLivePushService.java
+++ b/src/main/java/com/starlwr/bot/bilibili/service/BilibiliBackupLivePushService.java
@@ -177,7 +177,8 @@ private boolean hasLivePushEvent(PushUser user) {
return user.getTargets().stream()
.map(PushTarget::getMessages)
.flatMap(List::stream)
- .map(PushMessage::getEventClass)
- .anyMatch(clazz -> clazz.equals(BilibiliLiveOnEvent.class) || clazz.equals(BilibiliLiveOffEvent.class));
+ .map(PushMessage::getEvent)
+ .anyMatch(event -> BilibiliLiveOnEvent.class.getName().equals(event)
+ || BilibiliLiveOffEvent.class.getName().equals(event));
}
}
diff --git a/src/main/java/com/starlwr/bot/bilibili/util/BilibiliApiUtil.java b/src/main/java/com/starlwr/bot/bilibili/util/BilibiliApiUtil.java
index 8494a60..2ce9d31 100644
--- a/src/main/java/com/starlwr/bot/bilibili/util/BilibiliApiUtil.java
+++ b/src/main/java/com/starlwr/bot/bilibili/util/BilibiliApiUtil.java
@@ -99,17 +99,25 @@ public Map getBilibiliHeaders() {
Map headers = new HashMap<>();
headers.put("Referer", "https://www.bilibili.com");
headers.put("User-Agent", properties.getNetwork().getUserAgent());
- if (StringUtil.isNotBlank(cookies.getSessData()) && StringUtil.isNotBlank(cookies.getBuvid3()) && StringUtil.isNotBlank(cookies.getBiliJct())) {
- headers.put(
- "Cookie", String.format(
- "SESSDATA=%s; buvid3=%s; bili_jct=%s; bili_ticket=%s; bili_ticket_expires=%s; ",
- cookies.getSessData(),
- cookies.getBuvid3(),
- cookies.getBiliJct(),
- sign.getTicket(),
- sign.getTicketExpires()
- )
- );
+ if (StringUtil.isNotBlank(cookies.getSessData()) && StringUtil.isNotBlank(cookies.getBiliJct())) {
+ Map values = new LinkedHashMap<>();
+ values.put("SESSDATA", cookies.getSessData());
+ values.put("bili_jct", cookies.getBiliJct());
+ values.put("buvid3", cookies.getBuvid3());
+ values.put("buvid4", cookies.getBuvid4());
+ values.put("DedeUserID", cookies.getDedeUserId());
+ values.put("b_nut", cookies.getBNut());
+ if (sign != null) {
+ values.put("bili_ticket", sign.getTicket());
+ values.put("bili_ticket_expires", String.valueOf(sign.getTicketExpires()));
+ }
+ if (cookies.getExtraCookies() != null) {
+ values.putAll(cookies.getExtraCookies());
+ }
+ headers.put("Cookie", values.entrySet().stream()
+ .filter(entry -> StringUtil.isNotBlank(entry.getValue()))
+ .map(entry -> entry.getKey() + "=" + entry.getValue())
+ .collect(Collectors.joining("; ")));
}
return headers;
}
@@ -274,6 +282,8 @@ public WebSign generateBilibiliWebSign() {
String subKey = sub.substring(sub.lastIndexOf("/") + 1, sub.lastIndexOf("."));
sign = new WebSign(ticket, ticketExpires, imgKey, subKey);
+ cookies.setBiliTicket(ticket);
+ cookies.setBiliTicketExpires(ticketExpires.longValue());
return sign;
}
@@ -750,4 +760,32 @@ public void followUp(Long uid) {
}
}
+
+ /**
+ * 获取直播报告的可选基线数据。每个接口独立容错,缺失字段表示未知,调用方不得按 0 处理。
+ */
+ public JSONObject getLiveReportBaseStats(Long uid, Long roomId) {
+ JSONObject stats = new JSONObject();
+ try {
+ JSONObject room = requestBilibiliApi("https://api.live.bilibili.com/room/v1/Room/get_info?room_id=" + roomId);
+ if (room.containsKey("attention")) stats.put("fans", room.getLong("attention"));
+ } catch (Exception e) {
+ log.warn("获取直播报告粉丝基线失败, uid={}, roomId={}", uid, roomId, e);
+ }
+ try {
+ JSONObject medal = requestBilibiliApi("https://api.live.bilibili.com/xlive/app-ucenter/v1/fansMedal/fans_medal_info?target_id=" + uid + "&room_id=" + roomId);
+ if (medal.containsKey("fans_medal_light_count")) stats.put("fans_medal", medal.getLong("fans_medal_light_count"));
+ } catch (Exception e) {
+ log.warn("获取直播报告粉丝团基线失败, uid={}, roomId={}", uid, roomId, e);
+ }
+ try {
+ JSONObject guard = requestBilibiliApi("https://api.live.bilibili.com/xlive/app-room/v2/guardTab/topListNew?roomid=" + roomId
+ + "&page=1&ruid=" + uid + "&page_size=1&typ=5&platform=web");
+ JSONObject info = guard.getJSONObject("info");
+ if (info != null && info.containsKey("num")) stats.put("guard", info.getLong("num"));
+ } catch (Exception e) {
+ log.warn("获取直播报告大航海基线失败, uid={}, roomId={}", uid, roomId, e);
+ }
+ return stats;
+ }
}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/StarBotBilibiliApplication.kt b/src/main/kotlin/com/starlwr/bot/bilibili/StarBotBilibiliApplication.kt
new file mode 100644
index 0000000..d8ecc66
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/StarBotBilibiliApplication.kt
@@ -0,0 +1,45 @@
+package com.starlwr.bot.bilibili
+
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+import org.springframework.context.annotation.ImportRuntimeHints
+import org.springframework.context.annotation.ComponentScan
+import org.springframework.context.annotation.FilterType
+import org.springframework.cache.annotation.EnableCaching
+import org.springframework.retry.annotation.EnableRetry
+import org.springframework.scheduling.annotation.EnableAsync
+import org.springframework.scheduling.annotation.EnableScheduling
+import org.springframework.context.annotation.EnableAspectJAutoProxy
+import com.starlwr.bot.core.StarBotCoreApplication
+import com.starlwr.bot.core.plugin.StarBotPluginDependencyDownloader
+import com.starlwr.bot.core.plugin.StarBotPluginLoader
+
+/** Standalone distribution entry point. The regular artifact remains loadable as a StarBot plugin. */
+@SpringBootApplication
+@ComponentScan(basePackages = ["com.starlwr.bot"], excludeFilters = [
+ ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ classes = [
+ StarBotCoreApplication::class,
+ StarBotPluginLoader::class,
+ StarBotPluginDependencyDownloader::class
+ ]
+ )
+])
+@EnableAsync
+@EnableRetry
+@EnableCaching
+@EnableScheduling
+@EnableAspectJAutoProxy(exposeProxy = true)
+@ImportRuntimeHints(StarBotNativeRuntimeHints::class)
+class StarBotBilibiliApplication
+
+fun main(args: Array) {
+ if (System.getProperty("java.home").isNullOrBlank()) {
+ val executable = ProcessHandle.current().info().command().orElse(null)
+ val runtimeHome = executable?.let { java.nio.file.Path.of(it).toAbsolutePath().parent }
+ ?: java.nio.file.Path.of(System.getProperty("user.dir")).toAbsolutePath()
+ System.setProperty("java.home", runtimeHome.toString())
+ }
+ runApplication(*args)
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/StarBotNativeRuntimeHints.kt b/src/main/kotlin/com/starlwr/bot/bilibili/StarBotNativeRuntimeHints.kt
new file mode 100644
index 0000000..65f13ad
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/StarBotNativeRuntimeHints.kt
@@ -0,0 +1,17 @@
+package com.starlwr.bot.bilibili
+
+import org.springframework.aot.hint.MemberCategory
+import org.springframework.aot.hint.RuntimeHints
+import org.springframework.aot.hint.RuntimeHintsRegistrar
+import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor
+
+class StarBotNativeRuntimeHints : RuntimeHintsRegistrar {
+ override fun registerHints(hints: RuntimeHints, classLoader: ClassLoader?) {
+ hints.reflection().registerType(PersistenceAnnotationBeanPostProcessor::class.java,
+ MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS)
+ // StarBot Core's plugin registry reparses configuration metadata even when no external
+ // plugins are present. Native images therefore need the corresponding class resources.
+ hints.resources().registerPattern("org/springframework/**/*.class")
+ hints.resources().registerPattern("com/starlwr/bot/**/*.class")
+ }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/credential/BilibiliCredentialService.kt b/src/main/kotlin/com/starlwr/bot/bilibili/credential/BilibiliCredentialService.kt
new file mode 100644
index 0000000..6d32a71
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/credential/BilibiliCredentialService.kt
@@ -0,0 +1,283 @@
+package com.starlwr.bot.bilibili.credential
+
+import com.alibaba.fastjson2.JSON
+import com.alibaba.fastjson2.JSONObject
+import com.alibaba.fastjson2.JSONWriter
+import com.starlwr.bot.bilibili.config.StarBotBilibiliProperties
+import com.starlwr.bot.bilibili.model.Cookies
+import com.starlwr.bot.core.plugin.StarBotComponent
+import org.slf4j.LoggerFactory
+import org.springframework.boot.context.properties.ConfigurationProperties
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import java.net.URI
+import java.net.URLDecoder
+import java.net.URLEncoder
+import java.net.http.HttpClient
+import java.net.http.HttpRequest
+import java.net.http.HttpResponse
+import java.nio.charset.StandardCharsets
+import java.nio.file.AtomicMoveNotSupportedException
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.StandardCopyOption
+import java.security.KeyFactory
+import java.security.spec.MGF1ParameterSpec
+import java.security.spec.X509EncodedKeySpec
+import java.time.Duration
+import java.util.Base64
+import java.util.LinkedHashMap
+import java.util.UUID
+import javax.crypto.Cipher
+import javax.crypto.spec.OAEPParameterSpec
+import javax.crypto.spec.PSource
+
+@ConfigurationProperties("starbot.bilibili.account")
+class BilibiliCredentialProperties {
+ var credentialFile: String = "./config/bilibili-credential.json"
+ var legacyCookieFile: String = "./cookies.json"
+ var autoRefresh: Boolean = true
+ var refreshCheckMillis: Long = 6 * 60 * 60 * 1000L
+ var qrPollMillis: Long = 3_000
+ var qrRegenerateOnExpiry: Boolean = true
+ var connectTimeoutSeconds: Long = 10
+ var requestTimeoutSeconds: Long = 30
+}
+
+data class QrCodeSession(val url: String, val key: String)
+enum class QrCodeState { WAIT_SCAN, WAIT_CONFIRM, EXPIRED, DONE, ERROR }
+data class QrCodePollResult(val state: QrCodeState, val credential: Cookies? = null, val message: String? = null)
+
+/** Full web Credential lifecycle ported from bilibili-api-python. */
+@StarBotComponent
+@EnableConfigurationProperties(BilibiliCredentialProperties::class)
+class BilibiliCredentialService(
+ private val properties: BilibiliCredentialProperties,
+ private val bilibiliProperties: StarBotBilibiliProperties
+) {
+ private val log = LoggerFactory.getLogger(javaClass)
+ private val client = HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(properties.connectTimeoutSeconds))
+ .followRedirects(HttpClient.Redirect.NORMAL).build()
+ private val lock = Any()
+
+ fun getProperties(): BilibiliCredentialProperties = properties
+ fun credentialPath(): Path = Path.of(properties.credentialFile).toAbsolutePath().normalize()
+
+ fun load(): Cookies? = synchronized(lock) {
+ val primary = credentialPath()
+ val legacy = Path.of(properties.legacyCookieFile)
+ val source = when {
+ Files.isRegularFile(primary) -> primary
+ Files.isRegularFile(legacy) -> legacy
+ else -> return null
+ }
+ val credential = JSON.parseObject(Files.readString(source), Cookies::class.java) ?: return null
+ normalize(credential)
+ if (!credential.hasLoginCredential()) return null
+ if (source != primary) {
+ log.info("Migrating legacy credential file {} to {}", source.toAbsolutePath(), primary)
+ save(credential)
+ }
+ credential
+ }
+
+ fun save(credential: Cookies) = synchronized(lock) {
+ normalize(credential)
+ val target = credentialPath()
+ Files.createDirectories(target.parent)
+ val temporary = target.resolveSibling("${target.fileName}.tmp-${UUID.randomUUID()}")
+ try {
+ Files.writeString(temporary, JSON.toJSONString(credential, JSONWriter.Feature.PrettyFormat), StandardCharsets.UTF_8)
+ try {
+ Files.move(temporary, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
+ } catch (_: AtomicMoveNotSupportedException) {
+ Files.move(temporary, target, StandardCopyOption.REPLACE_EXISTING)
+ }
+ } finally { Files.deleteIfExists(temporary) }
+ }
+
+ fun checkValid(credential: Cookies): Boolean {
+ val json = parseJson(request("GET", NAV_URL, credential))
+ return json.getIntValue("code", -1) == 0 && json.getJSONObject("data")?.getBooleanValue("isLogin") == true
+ }
+
+ fun checkRefresh(credential: Cookies): Boolean =
+ requireSuccess(request("GET", COOKIE_INFO_URL, credential), "check Credential refresh").getBooleanValue("refresh")
+
+ fun refreshIfNeeded(credential: Cookies, force: Boolean = false): Cookies = synchronized(lock) {
+ if (!force && (!properties.autoRefresh || !checkRefresh(credential))) credential else refresh(credential)
+ }
+
+ fun refresh(old: Cookies): Cookies = synchronized(lock) {
+ require(old.biliJct.isNotBlank()) { "bili_jct is required to refresh Credential" }
+ require(old.acTimeValue.isNotBlank()) { "ac_time_value/refresh_token is required to refresh Credential" }
+ val form = linkedMapOf(
+ "csrf" to old.biliJct,
+ "refresh_csrf" to getRefreshCsrf(old),
+ "refresh_token" to old.acTimeValue,
+ "source" to "main_web"
+ )
+ val response = request("POST", COOKIE_REFRESH_URL, old, form, randomizeBuvid3 = true)
+ val data = requireSuccess(response, "refresh Credential")
+ val responseCookies = parseSetCookies(response.headers().allValues("set-cookie"))
+ val refreshed = copyCredential(old).apply {
+ sessData = responseCookies["SESSDATA"] ?: error("Credential refresh response omitted SESSDATA")
+ biliJct = responseCookies["bili_jct"] ?: error("Credential refresh response omitted bili_jct")
+ dedeUserId = responseCookies["DedeUserID"] ?: dedeUserId
+ acTimeValue = data.getString("refresh_token") ?: error("Credential refresh response omitted refresh_token")
+ responseCookies["buvid3"]?.let { buvid3 = it }
+ responseCookies["buvid4"]?.let { buvid4 = it }
+ responseCookies["b_nut"]?.let { bNut = it }
+ extraCookies.putAll(responseCookies.filterKeys { it !in KNOWN_COOKIES })
+ }
+ val confirm = linkedMapOf("csrf" to refreshed.biliJct, "refresh_token" to old.acTimeValue)
+ requireSuccess(request("POST", CONFIRM_REFRESH_URL, refreshed, confirm), "confirm Credential refresh")
+ save(refreshed)
+ log.info("Bilibili Credential refreshed and old refresh token confirmed")
+ refreshed
+ }
+
+ fun generateQrCode(): QrCodeSession {
+ val data = requireSuccess(request("GET", QR_GENERATE_URL), "generate QR login")
+ return QrCodeSession(data.getString("url") ?: error("QR response omitted url"), data.getString("qrcode_key") ?: error("QR response omitted key"))
+ }
+
+ fun pollQrCode(session: QrCodeSession): QrCodePollResult {
+ val response = request("GET", "$QR_POLL_URL?qrcode_key=${encode(session.key)}")
+ val outer = parseJson(response)
+ if (response.statusCode() != 200 || outer.getIntValue("code", -1) != 0)
+ return QrCodePollResult(QrCodeState.ERROR, message = outer.getString("message"))
+ val data = outer.getJSONObject("data") ?: return QrCodePollResult(QrCodeState.ERROR, message = "missing data")
+ return when (val code = data.getIntValue("code")) {
+ 86101 -> QrCodePollResult(QrCodeState.WAIT_SCAN)
+ 86090 -> QrCodePollResult(QrCodeState.WAIT_CONFIRM)
+ 86038 -> QrCodePollResult(QrCodeState.EXPIRED)
+ 0 -> completeQrLogin(data)
+ else -> QrCodePollResult(QrCodeState.ERROR, message = data.getString("message") ?: "QR status $code")
+ }
+ }
+
+ private fun completeQrLogin(data: JSONObject): QrCodePollResult {
+ val query = parseQuery(data.getString("url") ?: "")
+ val buvid = fetchBuvid()
+ val credential = Cookies().apply {
+ sessData = query["SESSDATA"].orEmpty()
+ biliJct = query["bili_jct"].orEmpty()
+ dedeUserId = query["DedeUserID"].orEmpty()
+ acTimeValue = data.getString("refresh_token").orEmpty()
+ buvid3 = query["buvid3"] ?: buvid.first
+ buvid4 = query["buvid4"] ?: buvid.second
+ bNut = query["b_nut"].orEmpty()
+ extraCookies.putAll(query.filterKeys { it !in KNOWN_COOKIES })
+ }
+ if (!credential.hasRefreshableCredential())
+ return QrCodePollResult(QrCodeState.ERROR, message = "QR login returned an incomplete Credential")
+ save(credential)
+ return QrCodePollResult(QrCodeState.DONE, credential)
+ }
+
+ fun fetchBuvid(): Pair {
+ val data = requireSuccess(request("GET", SPI_URL), "get buvid3/buvid4")
+ return data.getString("b_3").orEmpty() to data.getString("b_4").orEmpty()
+ }
+
+ internal fun parseQuery(url: String): Map {
+ val raw = runCatching { URI.create(url).rawQuery }.getOrNull()
+ ?: url.substringAfter('?', "").takeIf { it.isNotBlank() } ?: return emptyMap()
+ return raw.split('&').mapNotNull { part ->
+ val index = part.indexOf('=')
+ if (index <= 0) null else decode(part.substring(0, index)) to decode(part.substring(index + 1))
+ }.toMap(LinkedHashMap())
+ }
+
+ internal fun parseSetCookies(headers: List): Map = headers.mapNotNull { header ->
+ val pair = header.substringBefore(';')
+ val index = pair.indexOf('=')
+ if (index <= 0) null else pair.substring(0, index).trim() to pair.substring(index + 1).trim()
+ }.toMap(LinkedHashMap())
+
+ private fun getRefreshCsrf(credential: Cookies): String {
+ val response = request("GET", "https://www.bilibili.com/correspond/1/${correspondPath()}", credential, randomizeBuvid3 = true)
+ if (response.statusCode() == 404) error("Credential correspondPath expired or was rejected")
+ if (response.statusCode() != 200) error("Unable to get refresh CSRF: HTTP ${response.statusCode()}")
+ return REFRESH_CSRF.find(response.body())?.groupValues?.get(1)
+ ?: error("Credential refresh CSRF was absent from correspond response")
+ }
+
+ private fun correspondPath(): String {
+ val key = KeyFactory.getInstance("RSA").generatePublic(X509EncodedKeySpec(Base64.getMimeDecoder().decode(CORRESPOND_PUBLIC_KEY)))
+ val cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding")
+ cipher.init(Cipher.ENCRYPT_MODE, key, OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT))
+ return cipher.doFinal("refresh_${System.currentTimeMillis()}".toByteArray(StandardCharsets.UTF_8))
+ .joinToString("") { "%02x".format(it.toInt() and 0xff) }
+ }
+
+ private fun request(method: String, url: String, credential: Cookies? = null, form: Map? = null, randomizeBuvid3: Boolean = false): HttpResponse {
+ val builder = HttpRequest.newBuilder(URI.create(url)).timeout(Duration.ofSeconds(properties.requestTimeoutSeconds))
+ .header("User-Agent", bilibiliProperties.network.userAgent).header("Referer", "https://www.bilibili.com")
+ .header("Accept", "application/json, text/plain, */*")
+ if (credential != null) builder.header("Cookie", cookieHeader(credential, randomizeBuvid3))
+ if (method == "POST") {
+ val body = form.orEmpty().entries.joinToString("&") { "${encode(it.key)}=${encode(it.value)}" }
+ builder.header("Content-Type", "application/x-www-form-urlencoded").POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
+ } else builder.GET()
+ return client.send(builder.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))
+ }
+
+ private fun requireSuccess(response: HttpResponse, operation: String): JSONObject {
+ val json = parseJson(response)
+ if (response.statusCode() != 200 || json.getIntValue("code", -1) != 0)
+ error("Failed to $operation: HTTP ${response.statusCode()}, code=${json.getIntValue("code", -1)}, message=${json.getString("message")}")
+ return json.getJSONObject("data") ?: json.getJSONObject("result") ?: JSONObject()
+ }
+
+ private fun parseJson(response: HttpResponse): JSONObject = runCatching { JSON.parseObject(response.body()) }
+ .getOrElse { error("Bilibili returned invalid JSON (HTTP ${response.statusCode()}): ${response.body().take(256)}") }
+
+ private fun cookieHeader(credential: Cookies, randomizeBuvid3: Boolean): String {
+ val values = linkedMapOf(
+ "SESSDATA" to credential.sessData, "bili_jct" to credential.biliJct,
+ "buvid3" to if (randomizeBuvid3) UUID.randomUUID().toString() else credential.buvid3,
+ "buvid4" to credential.buvid4, "DedeUserID" to credential.dedeUserId,
+ "b_nut" to credential.bNut, "bili_ticket" to credential.biliTicket,
+ "bili_ticket_expires" to credential.biliTicketExpires?.takeIf { it > 0 }?.toString()
+ )
+ values.putAll(credential.extraCookies.orEmpty())
+ return values.filterValues { !it.isNullOrBlank() }.entries.joinToString("; ") { "${it.key}=${it.value}" }
+ }
+
+ private fun normalize(c: Cookies) {
+ c.sessData = c.sessData.orEmpty(); c.biliJct = c.biliJct.orEmpty(); c.buvid3 = c.buvid3.orEmpty()
+ c.buvid4 = c.buvid4.orEmpty(); c.dedeUserId = c.dedeUserId.orEmpty(); c.acTimeValue = c.acTimeValue.orEmpty()
+ c.bNut = c.bNut.orEmpty(); c.biliTicket = c.biliTicket.orEmpty(); c.biliTicketExpires = c.biliTicketExpires ?: 0L
+ c.extraCookies = c.extraCookies ?: LinkedHashMap()
+ }
+
+ private fun copyCredential(s: Cookies) = Cookies().also {
+ it.sessData=s.sessData; it.biliJct=s.biliJct; it.buvid3=s.buvid3; it.buvid4=s.buvid4
+ it.dedeUserId=s.dedeUserId; it.acTimeValue=s.acTimeValue; it.bNut=s.bNut
+ it.biliTicket=s.biliTicket; it.biliTicketExpires=s.biliTicketExpires; it.extraCookies=LinkedHashMap(s.extraCookies.orEmpty())
+ }
+ private fun Cookies.hasLoginCredential() = sessData.isNotBlank() && biliJct.isNotBlank()
+ private fun Cookies.hasRefreshableCredential() = hasLoginCredential() && dedeUserId.isNotBlank() && acTimeValue.isNotBlank() && buvid3.isNotBlank()
+ private fun encode(value: String) = URLEncoder.encode(value, StandardCharsets.UTF_8)
+ private fun decode(value: String) = URLDecoder.decode(value, StandardCharsets.UTF_8)
+
+ companion object {
+ private const val QR_GENERATE_URL="https://passport.bilibili.com/x/passport-login/web/qrcode/generate"
+ private const val QR_POLL_URL="https://passport.bilibili.com/x/passport-login/web/qrcode/poll"
+ private const val NAV_URL="https://api.bilibili.com/x/web-interface/nav"
+ private const val COOKIE_INFO_URL="https://passport.bilibili.com/x/passport-login/web/cookie/info"
+ private const val COOKIE_REFRESH_URL="https://passport.bilibili.com/x/passport-login/web/cookie/refresh"
+ private const val CONFIRM_REFRESH_URL="https://passport.bilibili.com/x/passport-login/web/confirm/refresh"
+ private const val SPI_URL="https://api.bilibili.com/x/frontend/finger/spi"
+ private val REFRESH_CSRF=Regex("""(.+?)
""", RegexOption.DOT_MATCHES_ALL)
+ private val KNOWN_COOKIES=setOf("SESSDATA","bili_jct","buvid3","buvid4","DedeUserID","ac_time_value","b_nut","bili_ticket","bili_ticket_expires")
+ private const val CORRESPOND_PUBLIC_KEY="""
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLgd2OAkcGVtoE3ThUREbio0Eg
+ Uc/prcajMKXvkCKFCWhJYJcLkcM2DKKcSeFpD/j6Boy538YXnR6VhcuUJOhH2x71
+ nzPjfdTcqMz7djHum0qSZA0AyCBDABUqCrfNgCiJ00Ra7GmRj+YCK1NJEuewlb40
+ JNrRuoEUXpabUzGB8QIDAQAB
+ """
+ }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxCommandController.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxCommandController.kt
index dd035c4..07d4e1a 100644
--- a/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxCommandController.kt
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxCommandController.kt
@@ -12,28 +12,46 @@ import java.time.LocalDate
/** OneBot HTTP adapter retained from the community edition, with its broken encoding repaired. */
@RestController
@StarBotComponent
-class BlindBoxCommandController(private val dataSource: AbstractDataSource) {
+class BlindBoxCommandController(private val dataSource: AbstractDataSource, private val driver: LiveReportDataDriver,
+ private val painter: LiveReportPainter) {
@PostMapping("/blindbox/onebot")
fun onOneBotEvent(@RequestBody event: JSONObject): JSONObject {
val operation = JSONObject()
if (event.getString("post_type") != "message") return operation
val raw = event.getString("raw_message")?.trim() ?: return operation
- if (!raw.startsWith(COMMAND)) return operation
- val range = parseRange(raw.removePrefix(COMMAND).trim()) ?: return operation.apply {
- put("reply", HELP); put("auto_escape", false)
- }
val type = if (event.getString("message_type") == "private") PushTargetType.FRIEND else PushTargetType.GROUP
val number = if (type == PushTargetType.GROUP) event.getLong("group_id") else event.getLong("user_id")
val uids = dataSource.allUsers.asSequence().flatMap { user ->
(user.targets ?: emptyList()).asSequence().filter { it.platform == PLATFORM && it.num == number && it.type == type }
.map { user.uid }
}.distinct().toList()
- val stats = BlindBoxStatsStore.query(uids, range.start, range.end)
+ if (raw.startsWith(REPORT_COMMAND)) {
+ val reports = uids.mapNotNull { driver.recent(it, 1).firstOrNull() }
+ val reply = if (uids.isEmpty()) "当前会话没有关联直播间,无法查询直播报告"
+ else if (reports.isEmpty()) "暂无已完成的直播报告"
+ else reports.joinToString("\n\n") { painter.text(it, LiveReportTargetConfig(output = "text")) }
+ return operation.apply { put("reply", reply); put("auto_escape", false); put("at_sender", false) }
+ }
+ if (!raw.startsWith(COMMAND)) return operation
+ val range = parseRange(raw.removePrefix(COMMAND).trim()) ?: return operation.apply {
+ put("reply", HELP); put("auto_escape", false)
+ }
+ val snapshots = uids.flatMap { driver.recent(it, 100) }.filter {
+ val date = java.time.Instant.ofEpochMilli(it.startedAt).atZone(java.time.ZoneId.systemDefault()).toLocalDate()
+ date in range.start..range.end
+ }
+ val boxes = snapshots.sumOf { it.counts["box"] ?: 0 }
+ val cost = snapshots.sumOf { (it.values["box"] ?: 0.0) - (it.profits["box"] ?: 0.0) }
+ val value = snapshots.sumOf { it.values["box"] ?: 0.0 }
+ val profit = snapshots.sumOf { it.profits["box"] ?: 0.0 }
+ val participants = snapshots.flatMap { it.users["box"].orEmpty().keys }.toSet().size
+ val gifts = snapshots.flatMap { it.labels["box"].orEmpty().entries }.groupingBy { it.key }.fold(0L) { acc, entry -> acc + entry.value }
+ .entries.sortedByDescending { it.value }.take(10).joinToString(", ") { "${it.key} x${it.value}" }.ifEmpty { "无" }
val reply = if (uids.isEmpty()) "当前会话没有关联直播间,无法查询盲盒统计"
- else if (stats.boxCount == 0L) "盲盒统计 ${range.label}\n暂无记录"
- else "盲盒统计 ${range.label}\n盲盒次数: ${stats.boxCount}\n盲盒成本: ${BlindBoxStatsStore.format(stats.cost)}" +
- "\n开出价值: ${BlindBoxStatsStore.format(stats.value)}\n盈亏: ${BlindBoxStatsStore.format(stats.profit)}" +
- "\n参与人数: ${stats.userCount}\nTOP礼物: ${stats.topGifts(10)}"
+ else if (boxes == 0L) "盲盒统计 ${range.label}\n暂无记录"
+ else "盲盒统计 ${range.label}\n盲盒次数: $boxes\n盲盒成本: ${format(cost)}" +
+ "\n开出价值: ${format(value)}\n盈亏: ${format(profit)}" +
+ "\n参与人数: $participants\nTOP礼物: $gifts"
return operation.apply { put("reply", reply); put("auto_escape", false); put("at_sender", false) }
}
@@ -51,8 +69,10 @@ class BlindBoxCommandController(private val dataSource: AbstractDataSource) {
}
private data class Range(val start: LocalDate, val end: LocalDate, val label: String)
+ private fun format(value: Double) = java.text.DecimalFormat("0.##").format(value)
private companion object {
const val COMMAND = "盲盒统计"
+ const val REPORT_COMMAND = "直播报告"
const val PLATFORM = "qq-onebot"
const val HELP = "盲盒统计命令:\n盲盒统计 一周\n盲盒统计 一月\n盲盒统计 2026-07-10\n盲盒统计 2026-07-01 2026-07-10"
}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxReportHandlers.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxReportHandlers.kt
index fe13399..597f72f 100644
--- a/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxReportHandlers.kt
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxReportHandlers.kt
@@ -13,37 +13,37 @@ import com.starlwr.bot.core.sender.StarBotMessageSender
@StarBotComponent
@DefaultHandlerForEvent(event = "com.starlwr.bot.bilibili.event.live.BilibiliRandomGiftEvent")
class BlindBoxRecordHandler : StarBotEventHandler {
- override fun handle(event: StarBotExternalBaseEvent, pushMessage: PushMessage) {
- if (event is RandomGiftEvent) BlindBoxStatsStore.record(event)
- }
+ /** Compatibility handler; collection is performed once by LiveReportCollector. */
+ override fun handle(event: StarBotExternalBaseEvent, pushMessage: PushMessage) = Unit
override fun getDefaultParams() = JSONObject().apply { put("note", "record blind-box statistics") }
}
/** Select this handler for the live-on event to start a clean report session. */
@StarBotComponent
class BlindBoxLiveOnResetHandler : StarBotEventHandler {
- override fun handle(event: StarBotExternalBaseEvent, pushMessage: PushMessage) = BlindBoxStatsStore.reset(event)
+ override fun handle(event: StarBotExternalBaseEvent, pushMessage: PushMessage) = Unit
override fun getDefaultParams() = JSONObject().apply { put("note", "reset blind-box statistics on live start") }
}
/** Select this handler for the live-off event to send the migrated blind-box section. */
@StarBotComponent
-class BlindBoxLiveOffReportHandler(private val sender: StarBotMessageSender) : StarBotEventHandler {
+class BlindBoxLiveOffReportHandler(private val sender: StarBotMessageSender, private val collector: LiveReportCollector) : StarBotEventHandler {
override fun handle(event: StarBotExternalBaseEvent, pushMessage: PushMessage) {
val params = getDefaultParams().apply { pushMessage.paramsJsonObject?.let(::putAll) }
- val stats = BlindBoxStatsStore.snapshot(event)
- if (stats == null && params.getBooleanValue("only_when_non_empty", true)) return
- if (stats != null && stats.boxCount == 0L && params.getBooleanValue("only_when_non_empty", true)) return
+ val stats = collector.completed(event)
+ val count = stats?.counts?.get("box") ?: 0L
+ if (count == 0L && params.getBooleanValue("only_when_non_empty", true)) return
val raw = params.getString("message")
val content = raw
.replace("{uname}", stats?.uname ?: event.source.uname ?: "")
- .replace("{box_count}", (stats?.boxCount ?: 0).toString())
- .replace("{cost}", BlindBoxStatsStore.format(stats?.cost ?: 0.0))
- .replace("{value}", BlindBoxStatsStore.format(stats?.value ?: 0.0))
- .replace("{profit}", BlindBoxStatsStore.format(stats?.profit ?: 0.0))
- .replace("{user_count}", (stats?.userCount ?: 0).toString())
- .replace("{top_gifts}", stats?.topGifts(params.getIntValue("top_limit", 5)) ?: "无")
+ .replace("{box_count}", count.toString())
+ .replace("{cost}", fmt((stats?.values?.get("box") ?: 0.0) - (stats?.profits?.get("box") ?: 0.0)))
+ .replace("{value}", fmt(stats?.values?.get("box") ?: 0.0))
+ .replace("{profit}", fmt(stats?.profits?.get("box") ?: 0.0))
+ .replace("{user_count}", (stats?.users?.get("box")?.size ?: 0).toString())
+ .replace("{top_gifts}", stats?.labels?.get("box")?.entries?.sortedByDescending { it.value }
+ ?.take(params.getIntValue("top_limit", 5))?.joinToString(", ") { "${it.key} x${it.value}" }?.ifEmpty { "无" } ?: "无")
val target = pushMessage.target
Message.create(target.platform, target.type, target.num, content).forEach(sender::send)
}
@@ -52,4 +52,5 @@ class BlindBoxLiveOffReportHandler(private val sender: StarBotMessageSender) : S
put("only_when_non_empty", true); put("top_limit", 5)
put("message", "{uname} 本场盲盒统计\n盲盒次数: {box_count}\n盲盒成本: {cost}\n开出价值: {value}\n盈亏: {profit}\n参与人数: {user_count}\nTOP礼物: {top_gifts}")
}
+ private fun fmt(value: Double) = java.text.DecimalFormat("0.##").format(value)
}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxStatsStore.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxStatsStore.kt
deleted file mode 100644
index 6a4a2fc..0000000
--- a/src/main/kotlin/com/starlwr/bot/bilibili/report/BlindBoxStatsStore.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-package com.starlwr.bot.bilibili.report
-
-import com.alibaba.fastjson2.JSON
-import com.alibaba.fastjson2.JSONObject
-import com.starlwr.bot.core.event.StarBotExternalBaseEvent
-import com.starlwr.bot.core.event.live.common.RandomGiftEvent
-import com.starlwr.bot.core.model.GiftInfo
-import com.starlwr.bot.core.model.LiveStreamerInfo
-import org.slf4j.LoggerFactory
-import java.nio.charset.StandardCharsets
-import java.nio.file.Files
-import java.nio.file.Path
-import java.nio.file.StandardOpenOption
-import java.text.DecimalFormat
-import java.time.LocalDate
-import java.time.ZoneId
-import java.util.concurrent.ConcurrentHashMap
-
-/** v2 blind-box accounting port. The purchased box and opened gift are deliberately kept distinct. */
-object BlindBoxStatsStore {
- private val log = LoggerFactory.getLogger(javaClass)
- private val sessions = ConcurrentHashMap()
- private val seenEvents = ConcurrentHashMap.newKeySet()
- private val money = DecimalFormat("0.##")
- private val recordFile: Path = Path.of("blindbox-stats", "records.jsonl")
-
- @JvmStatic fun reset(event: StarBotExternalBaseEvent) { sessions.remove(roomKey(event)) }
-
- @JvmStatic fun record(event: RandomGiftEvent) {
- if (event.source == null || !seenEvents.add(System.identityHashCode(event))) return
- sessions.computeIfAbsent(roomKey(event)) { RoomStats(event.source) }.record(event)
- append(event)
- if (seenEvents.size > 4096) seenEvents.clear()
- }
-
- @JvmStatic fun snapshot(event: StarBotExternalBaseEvent): RoomStats? = sessions[roomKey(event)]?.copy()
-
- @JvmStatic fun query(uids: Collection, start: LocalDate, end: LocalDate): RoomStats {
- val result = RoomStats(LiveStreamerInfo(null, "关联直播间", null))
- if (uids.isEmpty() || !Files.exists(recordFile)) return result
- Files.newBufferedReader(recordFile, StandardCharsets.UTF_8).useLines { lines ->
- lines.filter(String::isNotBlank).forEach { line ->
- runCatching {
- val json = JSON.parseObject(line)
- val date = LocalDate.parse(json.getString("date"))
- if (json.getLong("uid") in uids && date in start..end) result.add(json)
- }.onFailure { log.warn("跳过无法解析的盲盒统计记录: {}", line, it) }
- }
- }
- return result
- }
-
- @JvmStatic fun format(value: Double): String = synchronized(money) { money.format(value) }
-
- private fun roomKey(event: StarBotExternalBaseEvent): String =
- "${event.platform}:${event.source.uid}:${event.source.roomId}"
-
- @Synchronized private fun append(event: RandomGiftEvent) {
- runCatching {
- Files.createDirectories(recordFile.parent)
- Files.writeString(recordFile, values(event).toJson(event).toJSONString() + System.lineSeparator(),
- StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND)
- }.onFailure { log.warn("写入盲盒统计记录失败", it) }
- }
-
- private fun values(event: RandomGiftEvent): Values {
- // Core contract: randomGiftInfo = purchased box, giftInfo = opened result.
- val box = event.randomGiftInfo
- val gift = event.giftInfo
- val boxCount = box.countOrOne()
- val giftCount = gift.countOrOne()
- val cost = event.price ?: box.total(boxCount)
- val profit = event.profit ?: (gift.total(giftCount) - cost)
- return Values(box, gift, boxCount, giftCount, cost, cost + profit, profit)
- }
-
- private data class Values(
- val box: GiftInfo?, val gift: GiftInfo?, val boxCount: Int, val giftCount: Int,
- val cost: Double, val value: Double, val profit: Double
- ) {
- fun toJson(event: RandomGiftEvent) = JSONObject().apply {
- val time = java.time.Instant.ofEpochMilli(event.timestamp)
- put("time", time.toString()); put("date", LocalDate.ofInstant(time, ZoneId.systemDefault()).toString())
- put("platform", event.platform); put("uid", event.source.uid); put("roomId", event.source.roomId)
- put("uname", event.source.uname); put("senderUid", event.sender?.uid); put("senderName", event.sender?.uname)
- put("boxName", box?.name); put("boxCount", boxCount); put("giftName", gift?.name); put("giftCount", giftCount)
- put("cost", cost); put("value", value); put("profit", profit)
- }
- }
-
- class RoomStats internal constructor(source: LiveStreamerInfo) {
- val uid: Long? = source.uid
- val roomId: Long? = source.roomId
- val uname: String? = source.uname
- var boxCount: Long = 0; private set
- var cost: Double = 0.0; private set
- var value: Double = 0.0; private set
- var profit: Double = 0.0; private set
- private val gifts = ConcurrentHashMap()
- private val users = ConcurrentHashMap.newKeySet()
-
- @Synchronized internal fun record(event: RandomGiftEvent) {
- val v = values(event)
- boxCount += v.boxCount; cost += v.cost; value += v.value; profit += v.profit
- v.gift?.name?.takeIf(String::isNotBlank)?.let { gifts.merge(it, v.giftCount.toLong(), Long::plus) }
- event.sender?.let { (it.uid?.toString() ?: it.uname)?.takeIf(String::isNotBlank)?.let(users::add) }
- }
-
- @Synchronized internal fun add(json: JSONObject) {
- boxCount += json.getIntValue("boxCount", 1).coerceAtLeast(1)
- cost += json.getDoubleValue("cost"); value += json.getDoubleValue("value"); profit += json.getDoubleValue("profit")
- json.getString("giftName")?.takeIf(String::isNotBlank)?.let {
- gifts.merge(it, json.getIntValue("giftCount", 1).coerceAtLeast(1).toLong(), Long::plus)
- }
- (json.getString("senderUid")?.takeIf(String::isNotBlank) ?: json.getString("senderName"))
- ?.takeIf(String::isNotBlank)?.let(users::add)
- }
-
- @Synchronized internal fun copy() = RoomStats(LiveStreamerInfo(uid, uname, roomId)).also {
- it.boxCount = boxCount; it.cost = cost; it.value = value; it.profit = profit
- it.gifts.putAll(gifts); it.users.addAll(users)
- }
-
- val userCount: Int get() = users.size
- fun topGifts(limit: Int): String = gifts.entries.sortedByDescending { it.value }.take(limit.coerceAtLeast(1))
- .joinToString(", ") { "${it.key} x${it.value}" }.ifEmpty { "无" }
- }
-
- private fun GiftInfo?.countOrOne() = this?.count?.takeIf { it > 0 } ?: 1
- private fun GiftInfo?.total(count: Int) = (this?.price ?: 0.0) * count
-}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/BufferedLiveReportDataDriver.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/BufferedLiveReportDataDriver.kt
new file mode 100644
index 0000000..22565ed
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/BufferedLiveReportDataDriver.kt
@@ -0,0 +1,70 @@
+package com.starlwr.bot.bilibili.report
+
+import org.slf4j.LoggerFactory
+import java.util.concurrent.*
+
+class BufferedLiveReportDataDriver(
+ private val delegate: LiveReportDataDriver,
+ capacity: Int = 20_000,
+ private val batchSize: Int = 500,
+ flushMillis: Long = 1_000
+) : LiveReportDataDriver {
+ override val id = "buffered-${delegate.id}"
+ private data class Pending(val session: ReportSession, val eventId: String, val delta: ReportDelta)
+ private val log = LoggerFactory.getLogger(javaClass)
+ private val queue = ArrayBlockingQueue(capacity.coerceAtLeast(100))
+ private val snapshots = ConcurrentHashMap()
+ private val localEvents = ConcurrentHashMap.newKeySet()
+ private val scheduler = Executors.newSingleThreadScheduledExecutor { r -> Thread(r, "starbot-report-flush").apply { isDaemon = true } }
+ init { scheduler.scheduleWithFixedDelay({ runCatching { flushBatch() }.onFailure { log.error("直播报告批量刷新失败", it) } }, flushMillis, flushMillis, TimeUnit.MILLISECONDS) }
+ override fun initialize() = delegate.initialize()
+ override fun createOrResume(session: ReportSession): LiveReportSnapshot = snapshots.computeIfAbsent(session.sessionId) {
+ delegate.createOrResume(session)
+ }.copySafe()
+ override fun apply(session: ReportSession, eventId: String, delta: ReportDelta): Boolean {
+ val identity = "${session.sessionId}:$eventId"; if (!localEvents.add(identity)) return false
+ snapshots.computeIfAbsent(session.sessionId) { delegate.createOrResume(session) }.apply(delta)
+ val pending = Pending(session, eventId, delta)
+ if (!queue.offer(pending)) { flushBatch(); queue.put(pending) }
+ return true
+ }
+ override fun snapshot(sessionId: String): LiveReportSnapshot? = snapshots[sessionId]?.copySafe() ?: delegate.snapshot(sessionId)
+ override fun complete(sessionId: String, endedAt: Long): LiveReportSnapshot? {
+ return try {
+ flushSession(sessionId); val completed = delegate.complete(sessionId, endedAt)
+ if (completed != null) snapshots[sessionId] = completed
+ completed
+ } catch (e: Exception) {
+ log.error("持久层暂时不可用,会话 {} 保留在内存缓冲并生成临时报告", sessionId, e)
+ snapshots[sessionId]?.also { it.endedAt = endedAt }?.copySafe()
+ }
+ }
+ override fun recent(uid: Long, limit: Int): List { flushBatch(); return delegate.recent(uid, limit) }
+ override fun health(): DriverHealth { val health=delegate.health(); return if(health.healthy) DriverHealth(true,"queued=${queue.size}") else health }
+ @Synchronized private fun flushBatch() {
+ val batch = ArrayList(batchSize.coerceAtLeast(1)); queue.drainTo(batch, batchSize)
+ if (batch.isEmpty()) return
+ var index = 0
+ try { while (index < batch.size) { val p=batch[index]; delegate.apply(p.session,p.eventId,p.delta); index++ } }
+ catch (e: Exception) {
+ for (i in batch.lastIndex downTo index) queue.put(batch[i])
+ throw e
+ }
+ }
+ private fun flushSession(sessionId: String) {
+ while (queue.any { it.session.sessionId == sessionId }) flushBatch()
+ }
+ override fun close() {
+ scheduler.shutdown()
+ var failures = 0
+ while (queue.isNotEmpty() && failures < 3) try { flushBatch(); failures = 0 } catch (_: Exception) { failures++ }
+ if (queue.isNotEmpty()) {
+ val path = java.nio.file.Path.of(System.getProperty("user.dir"), "data", "live-report-recovery-${System.currentTimeMillis()}.pb")
+ java.nio.file.Files.createDirectories(path.parent)
+ java.nio.file.Files.newOutputStream(path).use { out -> snapshots.values.forEach { ReportArchive.write(it.copySafe(), out) } }
+ log.error("持久层关闭时仍不可用,已将 {} 个会话写入恢复文件 {}", snapshots.size, path)
+ queue.clear()
+ }
+ delegate.close()
+ }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/JdbcLiveReportDataDriver.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/JdbcLiveReportDataDriver.kt
new file mode 100644
index 0000000..f12ba77
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/JdbcLiveReportDataDriver.kt
@@ -0,0 +1,103 @@
+package com.starlwr.bot.bilibili.report
+
+import com.alibaba.fastjson2.JSON
+import java.sql.Connection
+import java.sql.DriverManager
+
+class JdbcLiveReportDataDriver(
+ override val id: String,
+ private val url: String,
+ private val username: String? = null,
+ private val password: String? = null
+) : LiveReportDataDriver {
+ private val mysql = id.equals("mysql", true)
+ private fun connection(): Connection = if (username.isNullOrBlank()) DriverManager.getConnection(url)
+ else DriverManager.getConnection(url, username, password ?: "")
+
+ override fun initialize() {
+ connection().use { c ->
+ if (!mysql) c.createStatement().use {
+ it.execute("PRAGMA journal_mode=WAL"); it.execute("PRAGMA busy_timeout=5000")
+ val version = it.executeQuery("PRAGMA user_version").use { rs -> if (rs.next()) rs.getInt(1) else 0 }
+ require(version <= DB_SCHEMA_VERSION) { "SQLite report schema $version is newer than supported $DB_SCHEMA_VERSION" }
+ if (version in 1 until DB_SCHEMA_VERSION) backupSqlite(version)
+ }
+ c.createStatement().use { s ->
+ s.execute("""CREATE TABLE IF NOT EXISTS starbot_report_session (
+ session_id VARCHAR(160) PRIMARY KEY, uid BIGINT NOT NULL, started_at BIGINT NOT NULL,
+ ended_at BIGINT NULL, schema_version INTEGER NOT NULL, payload ${if (mysql) "LONGTEXT" else "TEXT"} NOT NULL)""")
+ s.execute("""CREATE TABLE IF NOT EXISTS starbot_report_event (
+ session_id VARCHAR(160) NOT NULL, event_id VARCHAR(160) NOT NULL,
+ created_at BIGINT NOT NULL, PRIMARY KEY(session_id,event_id))""")
+ runCatching { s.execute("CREATE INDEX starbot_report_uid_time ON starbot_report_session(uid,started_at)") }
+ s.execute("""CREATE TABLE IF NOT EXISTS starbot_report_migration (
+ migration_id VARCHAR(160) PRIMARY KEY, completed_at BIGINT NOT NULL, details ${if (mysql) "LONGTEXT" else "TEXT"})""")
+ }
+ if (!mysql) c.createStatement().use { it.execute("PRAGMA user_version=$DB_SCHEMA_VERSION") }
+ }
+ }
+
+ override fun createOrResume(session: ReportSession): LiveReportSnapshot = transaction { c ->
+ load(c, session.sessionId, true) ?: session.snapshot().also { save(c, it) }
+ }
+
+ override fun apply(session: ReportSession, eventId: String, delta: ReportDelta): Boolean = transaction { c ->
+ val inserted = try {
+ c.prepareStatement("INSERT INTO starbot_report_event(session_id,event_id,created_at) VALUES(?,?,?)").use {
+ it.setString(1, session.sessionId); it.setString(2, eventId); it.setLong(3, System.currentTimeMillis()); it.executeUpdate() == 1
+ }
+ } catch (_: java.sql.SQLIntegrityConstraintViolationException) { false }
+ catch (e: java.sql.SQLException) {
+ if (e.sqlState?.startsWith("23") == true || (!mysql && e.errorCode == 19)) false else throw e
+ }
+ if (inserted) {
+ val snap = load(c, session.sessionId, true) ?: session.snapshot()
+ snap.apply(delta); save(c, snap)
+ }
+ inserted
+ }
+
+ override fun snapshot(sessionId: String): LiveReportSnapshot? = connection().use { load(it, sessionId, false)?.copySafe() }
+ override fun complete(sessionId: String, endedAt: Long): LiveReportSnapshot? = transaction { c ->
+ load(c, sessionId, true)?.also { it.endedAt = endedAt; save(c, it) }?.copySafe()
+ }
+ override fun recent(uid: Long, limit: Int): List = connection().use { c ->
+ c.prepareStatement("SELECT payload FROM starbot_report_session WHERE uid=? AND ended_at IS NOT NULL ORDER BY started_at DESC LIMIT ?").use { p ->
+ p.setLong(1, uid); p.setInt(2, limit.coerceIn(1, 100)); p.executeQuery().use { rs ->
+ buildList { while (rs.next()) add(decode(rs.getString(1))) }
+ }
+ }
+ }
+ override fun health() = runCatching { connection().use { it.isValid(2) } }.fold({ DriverHealth(it) }, { DriverHealth(false, it.message ?: "jdbc error") })
+
+ private fun load(c: Connection, id: String, lock: Boolean): LiveReportSnapshot? {
+ val suffix = if (lock && mysql) " FOR UPDATE" else ""
+ return c.prepareStatement("SELECT payload FROM starbot_report_session WHERE session_id=?$suffix").use { p ->
+ p.setString(1, id); p.executeQuery().use { if (it.next()) decode(it.getString(1)) else null }
+ }
+ }
+ private fun save(c: Connection, s: LiveReportSnapshot) {
+ val sql = if (mysql) """INSERT INTO starbot_report_session(session_id,uid,started_at,ended_at,schema_version,payload)
+ VALUES(?,?,?,?,?,?) ON DUPLICATE KEY UPDATE ended_at=VALUES(ended_at),schema_version=VALUES(schema_version),payload=VALUES(payload)"""
+ else """INSERT INTO starbot_report_session(session_id,uid,started_at,ended_at,schema_version,payload) VALUES(?,?,?,?,?,?)
+ ON CONFLICT(session_id) DO UPDATE SET ended_at=excluded.ended_at,schema_version=excluded.schema_version,payload=excluded.payload"""
+ c.prepareStatement(sql).use { p ->
+ p.setString(1, s.sessionId); p.setLong(2, s.uid); p.setLong(3, s.startedAt)
+ if (s.endedAt == null) p.setNull(4, java.sql.Types.BIGINT) else p.setLong(4, s.endedAt!!)
+ p.setInt(5, s.schemaVersion); p.setString(6, JSON.toJSONString(s)); p.executeUpdate()
+ }
+ }
+ private fun decode(json: String): LiveReportSnapshot = LiveReportSchemaMigration.migrate(
+ JSON.parseObject(json, LiveReportSnapshot::class.java))
+ private fun transaction(block: (Connection) -> T): T = connection().use { c ->
+ c.autoCommit = false
+ try { block(c).also { c.commit() } } catch (e: Exception) { c.rollback(); throw e }
+ }
+ private fun backupSqlite(version: Int) {
+ val raw = url.removePrefix("jdbc:sqlite:"); if (raw == url || raw == ":memory:") return
+ val source = java.nio.file.Path.of(raw); if (!java.nio.file.Files.exists(source)) return
+ val backup = source.resolveSibling("${source.fileName}.schema-$version-${System.currentTimeMillis()}.bak")
+ java.nio.file.Files.copy(source, backup)
+ }
+ companion object { const val DB_SCHEMA_VERSION = 1 }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/LegacyReportMigrator.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/LegacyReportMigrator.kt
new file mode 100644
index 0000000..b16f98e
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/LegacyReportMigrator.kt
@@ -0,0 +1,83 @@
+package com.starlwr.bot.bilibili.report
+
+import com.alibaba.fastjson2.JSON
+import com.starlwr.bot.core.datasource.AbstractDataSource
+import com.starlwr.bot.core.plugin.StarBotComponent
+import io.lettuce.core.RedisClient
+import org.slf4j.LoggerFactory
+import org.springframework.boot.ApplicationArguments
+import org.springframework.boot.ApplicationRunner
+import java.nio.file.Files
+import java.nio.file.Path
+import java.time.Instant
+
+@StarBotComponent
+class LegacyReportMigrator(
+ private val driver: LiveReportDataDriver,
+ private val properties: LiveReportStorageProperties,
+ private val dataSource: AbstractDataSource
+) : ApplicationRunner {
+ private val log = LoggerFactory.getLogger(javaClass)
+ override fun run(args: ApplicationArguments) {
+ if (!properties.migrateLegacy) return
+ runCatching { migrateCommunity() }.onFailure { log.warn("社区盲盒 JSONL 迁移失败,源文件未修改", it) }
+ if (!properties.type.equals("redis", true) || properties.v2RedisUri != properties.redisUri) {
+ runCatching { migrateV2Redis() }.onFailure { log.warn("v2 Redis 数据迁移失败,源数据未修改", it) }
+ }
+ }
+
+ private fun migrateCommunity() {
+ val candidates = listOfNotNull(properties.communityJsonl?.let(Path::of),
+ Path.of("blindbox-stats", "records.jsonl"), Path.of("..", "StarBot_v3_NoGit_CommunityThirdparty", "blindbox-stats", "records.jsonl"))
+ val path = candidates.firstOrNull(Files::isRegularFile) ?: return
+ var imported = 0
+ Files.newBufferedReader(path).useLines { lines -> lines.filter(String::isNotBlank).forEach { line ->
+ val j = JSON.parseObject(line); val uid = j.getLongValue("uid"); val room = j.getLongValue("roomId")
+ val time = runCatching { Instant.parse(j.getString("time")).toEpochMilli() }.getOrDefault(System.currentTimeMillis())
+ val session = ReportSession("legacy-community:$uid:${time / 86_400_000}", "bilibili", uid, room, j.getString("uname") ?: "", time)
+ driver.createOrResume(session)
+ val user = ReportUserDelta(j.getString("senderUid") ?: j.getString("senderName") ?: "unknown",
+ j.getString("senderName") ?: "", count = j.getLongValue("boxCount"), value = j.getDoubleValue("value"), profit = j.getDoubleValue("profit"))
+ if (driver.apply(session, "community:${sha(line)}", ReportDelta(ReportMetric.BOX,
+ j.getLongValue("boxCount"), j.getDoubleValue("value"), j.getDoubleValue("profit"), user, time,
+ label = j.getString("giftName")))) imported++
+ driver.complete(session.sessionId, time)
+ } }
+ log.info("已幂等导入社区盲盒记录 {} 条: {}", imported, path)
+ }
+
+ private fun migrateV2Redis() {
+ val client = RedisClient.create(properties.v2RedisUri); val connection = client.connect()
+ try {
+ val redis = connection.sync(); if (redis.ping() != "PONG") return
+ val users = dataSource.allUsers.associateBy { it.roomId }
+ val roomIds = listOf("RoomBoxTotal","RoomBoxCount","RoomDanmuTotal","RoomDanmuCount","RoomGiftTotal",
+ "RoomGiftProfit","RoomScTotal","RoomScProfit","RoomCaptainTotal","RoomCaptainCount","RoomCommanderTotal",
+ "RoomCommanderCount","RoomGovernorTotal","RoomGovernorCount").flatMap(redis::hkeys).toSet()
+ roomIds.forEach { roomText ->
+ val room = roomText.toLongOrNull() ?: return@forEach; val up = users[room]
+ val uid = up?.uid ?: room; val now = System.currentTimeMillis()
+ val session = ReportSession("legacy-v2:$uid:$room", "bilibili", uid, room, up?.uname ?: "legacy-v2", now)
+ driver.createOrResume(session)
+ val count = (redis.hget("RoomBoxTotal", roomText)?.toLongOrNull() ?: 0) + (redis.hget("RoomBoxCount", roomText)?.toLongOrNull() ?: 0)
+ val profit = (redis.hget("RoomBoxProfitTotal", roomText)?.toDoubleOrNull() ?: 0.0) + (redis.hget("RoomBoxProfit", roomText)?.toDoubleOrNull() ?: 0.0)
+ driver.apply(session, "v2:room:$room", ReportDelta(ReportMetric.BOX, count, profit, profit, occurredAt = now))
+ val danmu = hsum(redis, roomText, "RoomDanmuTotal", "RoomDanmuCount").toLong()
+ val gift = hsum(redis, roomText, "RoomGiftTotal", "RoomGiftProfit")
+ val sc = hsum(redis, roomText, "RoomScTotal", "RoomScProfit")
+ if (danmu != 0L) driver.apply(session, "v2:danmu:$room", ReportDelta(ReportMetric.DANMU, danmu, occurredAt = 0))
+ if (gift != 0.0) driver.apply(session, "v2:gift:$room", ReportDelta(ReportMetric.GIFT, value = gift, occurredAt = 0))
+ if (sc != 0.0) driver.apply(session, "v2:sc:$room", ReportDelta(ReportMetric.SC, value = sc, occurredAt = 0))
+ listOf("captain","commander","governor").forEach { level ->
+ val title = level.replaceFirstChar(Char::uppercase); val guard = hsum(redis, roomText, "Room${title}Total", "Room${title}Count").toLong()
+ if (guard != 0L) driver.apply(session, "v2:$level:$room", ReportDelta(ReportMetric.GUARD, guard, occurredAt = 0, label = level))
+ }
+ driver.complete(session.sessionId, now)
+ }
+ log.info("已扫描 v2 Redis 盲盒累计数据,房间数 {}", roomIds.size)
+ } finally { connection.close(); client.shutdown() }
+ }
+ private fun hsum(redis: io.lettuce.core.api.sync.RedisCommands, field: String, vararg keys: String) =
+ keys.sumOf { redis.hget(it, field)?.toDoubleOrNull() ?: 0.0 }
+ private fun sha(value: String) = java.security.MessageDigest.getInstance("SHA-256").digest(value.toByteArray()).take(12).joinToString("") { "%02x".format(it) }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportBaselineCollector.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportBaselineCollector.kt
new file mode 100644
index 0000000..ceb729d
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportBaselineCollector.kt
@@ -0,0 +1,24 @@
+package com.starlwr.bot.bilibili.report
+
+import com.starlwr.bot.bilibili.util.BilibiliApiUtil
+import com.starlwr.bot.core.event.live.common.LiveOnEvent
+import com.starlwr.bot.core.plugin.StarBotComponent
+import org.springframework.context.event.EventListener
+import org.springframework.scheduling.annotation.Async
+
+@StarBotComponent
+class LiveReportBaselineCollector(private val api: BilibiliApiUtil, private val collector: LiveReportCollector,
+ private val demandService: LiveReportDemandService) {
+ @Async("bilibiliLiveReportThreadPool") @EventListener
+ fun before(event: LiveOnEvent) { if (needsBaseline(event.source.uid)) collect(event.source.uid, event.source.roomId)?.let { collector.recordMetadata(event, "before", it) } }
+
+ private fun needsBaseline(uid: Long?) = demandService.forUid(uid).sections.any { it in setOf("fans", "fans_medal", "guard") }
+
+ private fun collect(uid: Long?, roomId: Long?): Map? {
+ if (uid == null || roomId == null) return null
+ val json = api.getLiveReportBaseStats(uid, roomId)
+ return listOf("fans", "fans_medal", "guard").mapNotNull { key ->
+ if (json.containsKey(key)) key to json.getLongValue(key) else null
+ }.toMap()
+ }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportCollector.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportCollector.kt
new file mode 100644
index 0000000..a6465c5
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportCollector.kt
@@ -0,0 +1,102 @@
+package com.starlwr.bot.bilibili.report
+
+import com.starlwr.bot.core.event.StarBotExternalBaseEvent
+import com.starlwr.bot.core.event.live.base.StarBotLiveInteractionEvent
+import com.starlwr.bot.core.event.live.common.*
+import com.starlwr.bot.core.plugin.StarBotComponent
+import org.springframework.context.event.EventListener
+import java.nio.charset.StandardCharsets
+import java.security.MessageDigest
+import java.util.concurrent.ConcurrentHashMap
+
+@StarBotComponent
+class LiveReportCollector(private val driver: LiveReportDataDriver, private val demandService: LiveReportDemandService) {
+ private val active = ConcurrentHashMap()
+ private val latest = ConcurrentHashMap()
+ private val completed = ConcurrentHashMap()
+
+ @EventListener fun onLive(event: LiveOnEvent) {
+ if (!demandService.forUid(event.source.uid).enabled) return
+ val key = roomKey(event)
+ if (event.isReconnect && active.containsKey(key)) return
+ val start = event.timestamp
+ val session = ReportSession("${event.platform}:${event.source.uid}:$start", event.platform,
+ event.source.uid ?: 0, event.source.roomId ?: 0, event.source.uname ?: "", start)
+ driver.createOrResume(session); active[key] = session; latest[key] = session
+ }
+
+ @EventListener fun onOff(event: LiveOffEvent) {
+ if (!demandService.forUid(event.source.uid).enabled && !active.containsKey(roomKey(event))) return
+ val key = roomKey(event); val session = active.remove(key) ?: sessionFor(event)
+ driver.complete(session.sessionId, event.timestamp)?.let { completed[key] = it }
+ }
+
+ @EventListener fun onDanmu(event: DanmuEvent) = commit(event, ReportDelta(
+ ReportMetric.DANMU, count = 1, user = user(event, 1), occurredAt = event.timestamp,
+ text = event.contentText?.take(500)?.takeIf { demandService.forUid(event.source.uid).wordCloud }))
+
+ @EventListener fun onGift(event: PaidGiftEvent) {
+ val gift = event.giftInfo; val count = (gift?.count ?: 1).coerceAtLeast(1)
+ val value = event.value ?: ((gift?.price ?: 0.0) * count)
+ commit(event, ReportDelta(ReportMetric.GIFT, count.toLong(), value,
+ user = user(event, count.toLong(), value), occurredAt = event.timestamp, label = gift?.name))
+ }
+
+ @EventListener fun onBox(event: RandomGiftEvent) {
+ val box = event.randomGiftInfo; val result = event.giftInfo
+ val count = (box?.count ?: 1).coerceAtLeast(1); val resultCount = (result?.count ?: 1).coerceAtLeast(1)
+ val cost = event.price ?: ((box?.price ?: 0.0) * count)
+ val profit = event.profit ?: ((result?.price ?: 0.0) * resultCount - cost)
+ commit(event, ReportDelta(ReportMetric.BOX, count.toLong(), cost + profit, profit,
+ user(event, count.toLong(), cost + profit, profit), event.timestamp, label = result?.name))
+ }
+
+ @EventListener fun onSc(event: SuperChatEvent) {
+ val value = event.value ?: 0.0
+ commit(event, ReportDelta(ReportMetric.SC, 1, value, user = user(event, 1, value),
+ occurredAt = event.timestamp, text = event.content?.take(500)))
+ }
+
+ @EventListener fun onGuard(event: MembershipEvent) {
+ val count = (event.count ?: 1).coerceAtLeast(1); val value = (event.value ?: event.price ?: 0.0) * count
+ val level = when { event.javaClass.simpleName.contains("Governor") -> "governor"
+ event.javaClass.simpleName.contains("Commander") -> "commander"
+ event.javaClass.simpleName.contains("Captain") -> "captain" else -> event.unit ?: "guard" }
+ commit(event, ReportDelta(ReportMetric.GUARD, count.toLong(), value,
+ user = user(event, count.toLong(), value), occurredAt = event.timestamp, label = level))
+ }
+
+ fun completed(event: StarBotExternalBaseEvent): LiveReportSnapshot? = completed[roomKey(event)]
+ ?: active[roomKey(event)]?.let { driver.snapshot(it.sessionId) }
+
+ fun recordMetadata(event: StarBotExternalBaseEvent, phase: String, values: Map) {
+ val session = active[roomKey(event)] ?: latest[roomKey(event)] ?: sessionFor(event)
+ driver.apply(session, "metadata:$phase:${event.timestamp}", ReportDelta(ReportMetric.DANMU,
+ occurredAt = 0, metadata = values.mapKeys { "${phase}_${it.key}" }))
+ if (phase == "after") driver.snapshot(session.sessionId)?.let { completed[roomKey(event)] = it }
+ }
+
+ private fun commit(event: StarBotLiveInteractionEvent, delta: ReportDelta) {
+ if (!demandService.forUid(event.source.uid).enabled) return
+ val session = active.computeIfAbsent(roomKey(event)) { sessionFor(event) }
+ val demand = demandService.forUid(event.source.uid)
+ val metricKey = delta.metric.name.lowercase()
+ val adjusted = if (metricKey in demand.charts || (delta.metric == ReportMetric.BOX && "box_profit" in demand.charts)) delta
+ else delta.copy(occurredAt = 0)
+ driver.apply(session, eventId(event, delta), adjusted)
+ }
+ private fun sessionFor(event: StarBotExternalBaseEvent): ReportSession {
+ val start = event.timestamp / 60_000 * 60_000
+ return ReportSession("${event.platform}:${event.source.uid}:$start", event.platform,
+ event.source.uid ?: 0, event.source.roomId ?: 0, event.source.uname ?: "", start)
+ }
+ private fun user(event: StarBotLiveInteractionEvent, count: Long, value: Double = 0.0, profit: Double = 0.0): ReportUserDelta? =
+ event.sender?.let { ReportUserDelta((it.uid ?: it.uname.hashCode().toLong()).toString(), it.uname ?: "", it.face, count, value, profit) }
+ private fun roomKey(event: StarBotExternalBaseEvent) = "${event.platform}:${event.source.uid}:${event.source.roomId}"
+ private fun eventId(event: StarBotExternalBaseEvent, delta: ReportDelta): String {
+ val raw = listOf(event.platform, event.source.uid, event.source.roomId, event.javaClass.name, event.timestamp,
+ delta.metric, delta.user?.uid, delta.count, delta.value, delta.profit, delta.text, delta.label).joinToString("|")
+ return MessageDigest.getInstance("SHA-256").digest(raw.toByteArray(StandardCharsets.UTF_8))
+ .take(16).joinToString("") { "%02x".format(it) }
+ }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportConfig.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportConfig.kt
new file mode 100644
index 0000000..aae1fd9
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportConfig.kt
@@ -0,0 +1,55 @@
+package com.starlwr.bot.bilibili.report
+
+import com.alibaba.fastjson2.JSONObject
+
+data class LiveReportTargetConfig(
+ val enabled: Boolean = true,
+ val output: String = "image",
+ val textFallback: Boolean = true,
+ val onlyWhenNonEmpty: Boolean = false,
+ val atAll: Boolean = false,
+ val sections: Map = DEFAULT_SECTIONS,
+ val rankings: Map = emptyMap(),
+ val charts: Map = emptyMap(),
+ val wordCloud: Boolean = false,
+ val maxWords: Int = 80,
+ val dictionary: String? = null,
+ val stopWords: String? = null,
+ val logo: String? = null,
+ val saveImage: Boolean = false,
+ val saveDirectory: String = "report"
+) {
+ fun section(name: String) = sections[name] == true
+ fun top(name: String) = rankings[name]?.coerceIn(0, 20) ?: 0
+ fun chart(name: String) = charts[name] == true
+ companion object {
+ val DEFAULT_SECTIONS = mapOf("time" to true, "danmu" to true, "box" to true,
+ "gift" to true, "sc" to true, "guard" to true, "fans" to false, "fans_medal" to false)
+ fun from(params: JSONObject?): LiveReportTargetConfig {
+ if (params == null) return LiveReportTargetConfig()
+ val sectionsJson = params.getJSONObject("sections")
+ val sections = DEFAULT_SECTIONS.mapValues { (key, default) ->
+ if (sectionsJson?.containsKey(key) == true) sectionsJson.getBooleanValue(key) else default
+ }
+ val rankingsJson = params.getJSONObject("rankings")
+ val rankings = ReportMetric.entries.associate { metric ->
+ val key = metric.name.lowercase(); val node = rankingsJson?.getJSONObject(key)
+ key to if (node?.getBooleanValue("enabled") == true) node.getIntValue("top", 3).coerceIn(1, 20) else 0
+ }
+ val chartsJson = params.getJSONObject("charts")
+ val charts = (ReportMetric.entries.map { it.name.lowercase() } + "box_profit").associate { key -> key to
+ (chartsJson?.getJSONObject(key)?.getBooleanValue("enabled") == true) }
+ val cloud = params.getJSONObject("word_cloud")
+ return LiveReportTargetConfig(
+ enabled = params.getBooleanValue("enabled", true), output = params.getString("output") ?: "image",
+ textFallback = params.getBooleanValue("text_fallback", true),
+ onlyWhenNonEmpty = params.getBooleanValue("only_when_non_empty", false),
+ atAll = params.getBooleanValue("at_all", false), sections = sections, rankings = rankings, charts = charts,
+ wordCloud = cloud?.getBooleanValue("enabled") == true,
+ maxWords = cloud?.getIntValue("max_words", 80)?.coerceIn(10, 300) ?: 80,
+ dictionary = cloud?.getString("dictionary"), stopWords = cloud?.getString("stop_words"),
+ logo = params.getString("logo"), saveImage = params.getBooleanValue("save_image", false),
+ saveDirectory = params.getString("save_directory") ?: "report")
+ }
+ }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportDataDriver.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportDataDriver.kt
new file mode 100644
index 0000000..bf7e47b
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportDataDriver.kt
@@ -0,0 +1,42 @@
+package com.starlwr.bot.bilibili.report
+
+import java.io.Closeable
+
+interface LiveReportDataDriver : Closeable {
+ val id: String
+ fun initialize()
+ fun createOrResume(session: ReportSession): LiveReportSnapshot
+ /** Returns false when eventId was already committed. */
+ fun apply(session: ReportSession, eventId: String, delta: ReportDelta): Boolean
+ fun snapshot(sessionId: String): LiveReportSnapshot?
+ fun complete(sessionId: String, endedAt: Long): LiveReportSnapshot?
+ fun recent(uid: Long, limit: Int = 10): List
+ fun health(): DriverHealth
+ override fun close() {}
+}
+
+data class DriverHealth(val healthy: Boolean, val message: String = "ok")
+
+class InMemoryLiveReportDataDriver(private val maxSessions: Int = 1_000, private val maxEvents: Int = 1_000_000) : LiveReportDataDriver {
+ override val id = "memory"
+ private val sessions = java.util.concurrent.ConcurrentHashMap()
+ private val events = java.util.concurrent.ConcurrentHashMap.newKeySet()
+ override fun initialize() = Unit
+ override fun createOrResume(session: ReportSession) = sessions.computeIfAbsent(session.sessionId) { session.snapshot() }.copySafe()
+ override fun apply(session: ReportSession, eventId: String, delta: ReportDelta): Boolean {
+ evictIfNeeded()
+ if (!events.add("${session.sessionId}:$eventId")) return false
+ sessions.computeIfAbsent(session.sessionId) { session.snapshot() }.apply(delta); return true
+ }
+ override fun snapshot(sessionId: String) = sessions[sessionId]?.copySafe()
+ override fun complete(sessionId: String, endedAt: Long) = sessions[sessionId]?.also { it.endedAt = endedAt }?.copySafe()
+ override fun recent(uid: Long, limit: Int) = sessions.values.filter { it.uid == uid && it.endedAt != null }
+ .sortedByDescending { it.startedAt }.take(limit).map { it.copySafe() }
+ override fun health() = DriverHealth(true)
+ private fun evictIfNeeded() {
+ if (sessions.size > maxSessions) sessions.values.filter { it.endedAt != null }.minByOrNull { it.endedAt ?: Long.MAX_VALUE }?.let { old ->
+ sessions.remove(old.sessionId); events.removeIf { it.startsWith("${old.sessionId}:") }
+ }
+ if (events.size > maxEvents) events.clear()
+ }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportDemandService.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportDemandService.kt
new file mode 100644
index 0000000..3294dd3
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportDemandService.kt
@@ -0,0 +1,33 @@
+package com.starlwr.bot.bilibili.report
+
+import com.starlwr.bot.core.datasource.AbstractDataSource
+import com.starlwr.bot.core.event.datasource.base.StarBotDataSourceChangeEvent
+import com.starlwr.bot.core.event.datasource.other.StarBotDataSourceLoadCompleteEvent
+import com.starlwr.bot.core.plugin.StarBotComponent
+import org.springframework.context.event.EventListener
+import java.util.concurrent.ConcurrentHashMap
+
+data class ReportDemand(val enabled: Boolean = false, val sections: Set = emptySet(),
+ val charts: Set = emptySet(), val wordCloud: Boolean = false)
+
+@StarBotComponent
+class LiveReportDemandService(private val dataSource: AbstractDataSource) {
+ private val demands = ConcurrentHashMap()
+ @EventListener(StarBotDataSourceLoadCompleteEvent::class)
+ fun reload() {
+ demands.clear()
+ dataSource.allUsers.forEach { user ->
+ val configs = user.targets.orEmpty().flatMap { it.messages.orEmpty() }
+ .filter { message -> message.enabled != false && listOf(LiveReportPushHandler::class.java.simpleName,
+ BlindBoxLiveOffReportHandler::class.java.simpleName, BlindBoxRecordHandler::class.java.simpleName)
+ .any { message.handler?.endsWith(it) == true } }
+ .map { LiveReportTargetConfig.from(it.paramsJsonObject) }.filter { it.enabled }
+ demands[user.uid] = ReportDemand(configs.isNotEmpty(),
+ configs.flatMap { c -> c.sections.filterValues { it }.keys }.toSet(),
+ configs.flatMap { c -> c.charts.filterValues { it }.keys }.toSet(),
+ configs.any { it.wordCloud })
+ }
+ }
+ @EventListener fun onChange(@Suppress("UNUSED_PARAMETER") event: StarBotDataSourceChangeEvent) = reload()
+ fun forUid(uid: Long?) = uid?.let { demands[it] } ?: ReportDemand()
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportModel.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportModel.kt
new file mode 100644
index 0000000..8ecccbd
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportModel.kt
@@ -0,0 +1,95 @@
+package com.starlwr.bot.bilibili.report
+
+import com.alibaba.fastjson2.JSON
+import java.time.Instant
+import java.util.concurrent.ConcurrentHashMap
+
+enum class ReportMetric { DANMU, BOX, GIFT, SC, GUARD }
+
+data class ReportUserDelta(
+ val uid: String, val uname: String = "", val face: String? = null,
+ val count: Long = 0, val value: Double = 0.0, val profit: Double = 0.0
+)
+
+data class ReportDelta(
+ val metric: ReportMetric,
+ val count: Long = 0,
+ val value: Double = 0.0,
+ val profit: Double = 0.0,
+ val user: ReportUserDelta? = null,
+ val occurredAt: Long = System.currentTimeMillis(),
+ val text: String? = null,
+ val label: String? = null,
+ val metadata: Map = emptyMap()
+)
+
+data class ReportUserStats(
+ var uname: String = "", var face: String? = null,
+ var count: Long = 0, var value: Double = 0.0, var profit: Double = 0.0
+)
+
+data class LiveReportSnapshot(
+ var schemaVersion: Int = CURRENT_SCHEMA,
+ var sessionId: String = "",
+ var platform: String = "bilibili",
+ var uid: Long = 0,
+ var roomId: Long = 0,
+ var uname: String = "",
+ var startedAt: Long = 0,
+ var endedAt: Long? = null,
+ var counts: MutableMap = ConcurrentHashMap(),
+ var values: MutableMap = ConcurrentHashMap(),
+ var profits: MutableMap = ConcurrentHashMap(),
+ var users: MutableMap> = ConcurrentHashMap(),
+ var buckets: MutableMap> = ConcurrentHashMap(),
+ var labels: MutableMap> = ConcurrentHashMap(),
+ var metadata: MutableMap = ConcurrentHashMap(),
+ var danmuTexts: MutableList = java.util.Collections.synchronizedList(mutableListOf())
+) {
+ @Synchronized fun apply(delta: ReportDelta, maxTexts: Int = 20_000) {
+ val key = delta.metric.name.lowercase()
+ counts.merge(key, delta.count, Long::plus)
+ values.merge(key, delta.value, Double::plus)
+ profits.merge(key, delta.profit, Double::plus)
+ delta.label?.let { labels.computeIfAbsent(key) { ConcurrentHashMap() }.merge(it, delta.count.coerceAtLeast(1), Long::plus) }
+ metadata.putAll(delta.metadata)
+ delta.user?.let { d ->
+ val stat = users.computeIfAbsent(key) { ConcurrentHashMap() }
+ .computeIfAbsent(d.uid) { ReportUserStats(d.uname, d.face) }
+ stat.uname = d.uname.ifBlank { stat.uname }; stat.face = d.face ?: stat.face
+ stat.count += d.count; stat.value += d.value; stat.profit += d.profit
+ }
+ if (delta.occurredAt > 0) {
+ val minute = delta.occurredAt / 60_000 * 60_000
+ buckets.computeIfAbsent(key) { ConcurrentHashMap() }.merge(minute,
+ if (delta.value != 0.0) delta.value else delta.count.toDouble(), Double::plus)
+ }
+ if (delta.metric == ReportMetric.BOX && delta.profit != 0.0 && delta.occurredAt > 0) {
+ buckets.computeIfAbsent("box_profit") { ConcurrentHashMap() }
+ .merge(delta.occurredAt / 60_000 * 60_000, delta.profit, Double::plus)
+ }
+ delta.text?.takeIf { it.isNotBlank() && danmuTexts.size < maxTexts }?.let(danmuTexts::add)
+ }
+
+ fun copySafe(): LiveReportSnapshot = JSON.parseObject(JSON.toJSONString(this), LiveReportSnapshot::class.java)
+ companion object { const val CURRENT_SCHEMA = 1 }
+}
+
+data class ReportSession(
+ val sessionId: String, val platform: String, val uid: Long, val roomId: Long,
+ val uname: String, val startedAt: Long = Instant.now().toEpochMilli()
+) {
+ fun snapshot() = LiveReportSnapshot(sessionId = sessionId, platform = platform, uid = uid,
+ roomId = roomId, uname = uname, startedAt = startedAt)
+}
+
+object LiveReportSchemaMigration {
+ fun migrate(snapshot: LiveReportSnapshot): LiveReportSnapshot {
+ require(snapshot.schemaVersion <= LiveReportSnapshot.CURRENT_SCHEMA) { "Unsupported future report schema ${snapshot.schemaVersion}" }
+ while (snapshot.schemaVersion < LiveReportSnapshot.CURRENT_SCHEMA) when (snapshot.schemaVersion) {
+ 0 -> snapshot.schemaVersion = 1
+ else -> error("No report migration from schema ${snapshot.schemaVersion}")
+ }
+ return snapshot
+ }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportPainter.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportPainter.kt
new file mode 100644
index 0000000..a15d811
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportPainter.kt
@@ -0,0 +1,128 @@
+package com.starlwr.bot.bilibili.report
+
+import com.starlwr.bot.core.factory.StarBotCommonPainterFactory
+import com.starlwr.bot.core.plugin.StarBotComponent
+import java.awt.*
+import java.awt.image.BufferedImage
+import java.time.Instant
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+import kotlin.math.max
+
+@StarBotComponent
+class LiveReportPainter(private val factory: StarBotCommonPainterFactory) {
+ fun paint(snapshot: LiveReportSnapshot, config: LiveReportTargetConfig): String {
+ val painter = factory.create(1000, 300, true).setRowSpace(18)
+ painter.drawChapter("直播报告").drawTip("${snapshot.uname} (${snapshot.roomId})")
+ config.logo?.let { path -> runCatching { javax.imageio.ImageIO.read(java.nio.file.Path.of(path).toFile()) }.getOrNull()?.let {
+ painter.drawImage(resize(it, 160), Point(800, 20))
+ } }
+ if (config.section("time")) {
+ val end = snapshot.endedAt ?: System.currentTimeMillis()
+ painter.drawSection("直播时间")
+ painter.drawText("${time(snapshot.startedAt)} ~ ${time(end)} (${duration(end - snapshot.startedAt)})")
+ }
+ if (config.section("fans") || config.section("fans_medal")) {
+ painter.drawSection("基础数据")
+ if (config.section("fans")) drawChange(painter, "粉丝", snapshot.metadata["before_fans"], snapshot.metadata["after_fans"])
+ if (config.section("fans_medal")) drawChange(painter, "粉丝团", snapshot.metadata["before_fans_medal"], snapshot.metadata["after_fans_medal"])
+ drawChange(painter, "大航海", snapshot.metadata["before_guard"], snapshot.metadata["after_guard"])
+ }
+ painter.drawSection("直播数据")
+ ReportMetric.entries.forEach { metric ->
+ val key = metric.name.lowercase(); if (!config.section(key)) return@forEach
+ val count = snapshot.counts[key] ?: 0; val value = snapshot.values[key] ?: 0.0
+ val users = snapshot.users[key]?.size ?: 0
+ when (metric) {
+ ReportMetric.DANMU -> painter.drawText("弹幕: $count 条 ($users 人)")
+ ReportMetric.BOX -> painter.drawText("盲盒: $count 个 ($users 人),价值 ${fmt(value)} 元,盈亏 ${fmt(snapshot.profits[key] ?: 0.0)} 元")
+ ReportMetric.GIFT -> painter.drawText("礼物: ${fmt(value)} 元 ($users 人)")
+ ReportMetric.SC -> painter.drawText("SC: ${fmt(value)} 元 ($users 人)")
+ ReportMetric.GUARD -> painter.drawText("大航海: $count 个,${fmt(value)} 元 ($users 人)")
+ }
+ }
+ if (config.section("guard")) snapshot.labels["guard"]?.takeIf { it.isNotEmpty() }?.let { levels ->
+ painter.drawTip("舰长 ${levels["captain"] ?: 0} / 提督 ${levels["commander"] ?: 0} / 总督 ${levels["governor"] ?: 0}")
+ }
+ if (config.chart("box_profit")) snapshot.buckets["box_profit"]?.takeIf { it.isNotEmpty() }?.let {
+ painter.drawSection("盲盒盈亏曲线").drawImageWithBorder(lineChart(it.toSortedMap().runningTotals(), 900, 360))
+ }
+ ReportMetric.entries.forEach { metric -> drawRanking(painter, snapshot, metric, config.top(metric.name.lowercase())) }
+ ReportMetric.entries.forEach { metric ->
+ if (config.chart(metric.name.lowercase())) snapshot.buckets[metric.name.lowercase()]?.takeIf { it.isNotEmpty() }?.let {
+ painter.drawSection("${title(metric)}互动曲线").drawImageWithBorder(lineChart(it, 900, 360))
+ }
+ }
+ if (config.wordCloud && snapshot.danmuTexts.isNotEmpty()) {
+ painter.drawSection("弹幕词云").drawImageWithBorder(wordCloud(snapshot.danmuTexts, config, 900, 420))
+ }
+ painter.drawCopyright(25)
+ return painter.base64().orElseThrow { IllegalStateException("直播报告图片编码失败") }
+ }
+
+ fun text(snapshot: LiveReportSnapshot, config: LiveReportTargetConfig): String = buildString {
+ appendLine("${snapshot.uname} 直播报告")
+ ReportMetric.entries.filter { config.section(it.name.lowercase()) }.forEach {
+ val key = it.name.lowercase(); appendLine("${title(it)}: ${snapshot.counts[key] ?: 0},金额 ${fmt(snapshot.values[key] ?: 0.0)}")
+ }
+ }.trim()
+
+ private fun drawRanking(p: com.starlwr.bot.core.painter.CommonPainter, s: LiveReportSnapshot, metric: ReportMetric, top: Int) {
+ if (top <= 0) return
+ val key = metric.name.lowercase(); val users = s.users[key]?.entries?.sortedByDescending {
+ if (metric == ReportMetric.BOX) it.value.profit else if (metric == ReportMetric.DANMU) it.value.count.toDouble() else it.value.value
+ }?.take(top).orEmpty()
+ if (users.isEmpty()) return
+ p.drawSection("${title(metric)}排行 (Top ${users.size})")
+ users.forEachIndexed { index, entry ->
+ val stat = entry.value; val score = when (metric) { ReportMetric.DANMU -> "${stat.count} 条"; ReportMetric.BOX -> "${fmt(stat.profit)} 元"; else -> "${fmt(stat.value)} 元" }
+ p.drawText("${index + 1}. ${stat.uname.ifBlank { entry.key }} $score")
+ }
+ }
+ private fun drawChange(p: com.starlwr.bot.core.painter.CommonPainter, title: String, before: Long?, after: Long?) {
+ if (before == null || after == null) return
+ val diff = after - before; p.drawText("$title: $before → $after (${if (diff >= 0) "+" else ""}$diff)")
+ }
+ private fun Map.runningTotals(): Map {
+ var total = 0.0
+ return entries.associate { entry -> total += entry.value; entry.key to total }
+ }
+
+ private fun lineChart(data: Map, width: Int, height: Int): BufferedImage {
+ val image = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); val g = image.createGraphics()
+ quality(g); g.color = Color.WHITE; g.fillRect(0, 0, width, height); g.color = Color(230,230,230)
+ repeat(6) { val y = 25 + it * (height - 50) / 5; g.drawLine(50, y, width - 25, y) }
+ val points = data.toSortedMap().values.toList(); val maxValue = max(1.0, points.maxOf { kotlin.math.abs(it) })
+ g.color = Color(251, 114, 153); g.stroke = BasicStroke(3f)
+ points.zipWithNext().forEachIndexed { i, (a,b) ->
+ val x1 = 50 + i * (width - 75) / max(1, points.size - 1); val x2 = 50 + (i+1) * (width - 75) / max(1, points.size - 1)
+ val y1 = height / 2 - (a / maxValue * (height / 2 - 30)).toInt(); val y2 = height / 2 - (b / maxValue * (height / 2 - 30)).toInt()
+ g.drawLine(x1,y1,x2,y2)
+ }; g.dispose(); return image
+ }
+
+ private fun wordCloud(texts: List, config: LiveReportTargetConfig, width: Int, height: Int): BufferedImage {
+ val counts = HashMap()
+ val customWords = readLines(config.dictionary)
+ val stopWords = STOP_WORDS + readLines(config.stopWords)
+ texts.asSequence().flatMap { Regex("[\\p{IsHan}]{2,}|[A-Za-z0-9_]{2,}").findAll(it).map { m -> m.value.lowercase() } }
+ .filterNot { it in stopWords }.forEach { counts.merge(it, 1, Int::plus) }
+ customWords.forEach { word -> val n=texts.sumOf { text -> Regex(Regex.escape(word)).findAll(text).count() }; if(n>0) counts[word]=n }
+ val words = counts.entries.sortedByDescending { it.value }.take(config.maxWords)
+ val image = BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB); val g=image.createGraphics(); quality(g)
+ g.color=Color.WHITE; g.fillRect(0,0,width,height); var x=20; var y=45
+ val maxCount = words.firstOrNull()?.value?.coerceAtLeast(1) ?: 1
+ words.forEachIndexed { i, e ->
+ val size = 18 + e.value * 42 / maxCount; g.font=Font("SansSerif",Font.PLAIN,size); val w=g.fontMetrics.stringWidth(e.key)
+ if (x+w>width-20) { x=20; y+=size+16 }; if (y = path?.let { runCatching { java.nio.file.Files.readAllLines(java.nio.file.Path.of(it)).map(String::trim).filter(String::isNotBlank).toSet() }.getOrDefault(emptySet()) } ?: emptySet()
+ private fun time(ms:Long)=FORMAT.format(Instant.ofEpochMilli(ms).atZone(ZoneId.systemDefault()))
+ private fun duration(ms:Long)="${ms/3_600_000} 小时 ${(ms/60_000)%60} 分钟 ${(ms/1000)%60} 秒"
+ private fun fmt(v:Double)=java.text.DecimalFormat("0.##").format(v)
+ private fun title(m:ReportMetric)=mapOf(ReportMetric.DANMU to "弹幕",ReportMetric.BOX to "盲盒",ReportMetric.GIFT to "礼物",ReportMetric.SC to "SC",ReportMetric.GUARD to "大航海").getValue(m)
+ companion object { val FORMAT:DateTimeFormatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); val COLORS=listOf(Color(251,114,153),Color(0,161,214),Color(126,87,194),Color(76,175,80)); val STOP_WORDS=setOf("这个","那个","就是","然后","但是","可以","不是","一个") }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportPushHandler.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportPushHandler.kt
new file mode 100644
index 0000000..c060d8b
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportPushHandler.kt
@@ -0,0 +1,76 @@
+package com.starlwr.bot.bilibili.report
+
+import com.alibaba.fastjson2.JSONObject
+import com.starlwr.bot.core.enums.PushTargetType
+import com.starlwr.bot.core.event.StarBotExternalBaseEvent
+import com.starlwr.bot.core.handler.StarBotEventHandler
+import com.starlwr.bot.core.model.Message
+import com.starlwr.bot.core.model.PushMessage
+import com.starlwr.bot.core.plugin.StarBotComponent
+import com.starlwr.bot.core.sender.StarBotMessageSender
+import org.slf4j.LoggerFactory
+import com.starlwr.bot.bilibili.util.BilibiliApiUtil
+import org.springframework.beans.factory.annotation.Qualifier
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
+
+@StarBotComponent
+class LiveReportPushHandler(
+ private val collector: LiveReportCollector,
+ private val painter: LiveReportPainter,
+ private val sender: StarBotMessageSender,
+ private val api: BilibiliApiUtil,
+ @param:Qualifier("bilibiliLiveReportThreadPool") private val executor: ThreadPoolTaskExecutor
+) : StarBotEventHandler {
+ private val log = LoggerFactory.getLogger(javaClass)
+ private val afterStats = java.util.concurrent.ConcurrentHashMap>()
+ override fun handle(event: StarBotExternalBaseEvent, pushMessage: PushMessage) {
+ try { executor.execute { handleAsync(event, pushMessage) } }
+ catch (e: java.util.concurrent.RejectedExecutionException) { log.error("直播报告队列已满,拒绝生成报告", e) }
+ }
+ private fun handleAsync(event: StarBotExternalBaseEvent, pushMessage: PushMessage) {
+ val config = LiveReportTargetConfig.from(pushMessage.paramsJsonObject)
+ if (!config.enabled) return
+ if (config.sections.any { it.value && it.key in setOf("fans", "fans_medal", "guard") }) {
+ val uid=event.source.uid; val room=event.source.roomId
+ if(uid!=null && room!=null) {
+ val cacheKey="${event.platform}:$uid:${event.timestamp}"
+ val values=afterStats.computeIfAbsent(cacheKey) {
+ val json=api.getLiveReportBaseStats(uid,room); listOf("fans","fans_medal","guard").mapNotNull {
+ key -> if(json.containsKey(key)) key to json.getLongValue(key) else null }.toMap()
+ }
+ if(afterStats.size>2048) afterStats.clear()
+ collector.recordMetadata(event,"after",values)
+ }
+ }
+ val snapshot = collector.completed(event) ?: return
+ if (config.onlyWhenNonEmpty && snapshot.counts.values.sum() == 0L) return
+ val target = pushMessage.target
+ val content = try {
+ if (config.output.equals("text", true)) painter.text(snapshot, config)
+ else {
+ val base64 = painter.paint(snapshot, config)
+ if (config.saveImage) saveImage(snapshot, config, base64)
+ "{image_base64=$base64}"
+ }
+ } catch (e: Exception) {
+ log.error("生成直播报告失败, session={}", snapshot.sessionId, e)
+ if (!config.textFallback) return else painter.text(snapshot, config)
+ }
+ val prefix = if (config.atAll && target.type == PushTargetType.GROUP) "{at=all}{next}" else ""
+ Message.create(target.platform, target.type, target.num, prefix + content).forEach(sender::send)
+ }
+
+ override fun getDefaultParams() = JSONObject.parseObject("""{
+ "enabled":true,"output":"image","text_fallback":true,"only_when_non_empty":false,"at_all":false,
+ "sections":{"time":true,"danmu":true,"box":true,"gift":true,"sc":true,"guard":true,"fans":false,"fans_medal":false},
+ "rankings":{"danmu":{"enabled":false,"top":3},"box":{"enabled":false,"top":3},"gift":{"enabled":false,"top":3},"sc":{"enabled":false,"top":3},"guard":{"enabled":false,"top":3}},
+ "charts":{"danmu":{"enabled":false},"box":{"enabled":false},"box_profit":{"enabled":false},"gift":{"enabled":false},"sc":{"enabled":false},"guard":{"enabled":false}},
+ "word_cloud":{"enabled":false,"max_words":80,"dictionary":null,"stop_words":null},
+ "logo":null,"save_image":false,"save_directory":"report"
+ }""")
+ private fun saveImage(snapshot: LiveReportSnapshot, config: LiveReportTargetConfig, base64: String) {
+ val dir = java.nio.file.Path.of(config.saveDirectory).toAbsolutePath(); java.nio.file.Files.createDirectories(dir)
+ val safe = snapshot.uname.replace(Regex("[^\\p{L}\\p{N}._-]"), "_").take(60)
+ java.nio.file.Files.write(dir.resolve("${safe}_${snapshot.roomId}_${snapshot.startedAt}.png"), java.util.Base64.getDecoder().decode(base64))
+ }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportStorageConfig.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportStorageConfig.kt
new file mode 100644
index 0000000..9758826
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/LiveReportStorageConfig.kt
@@ -0,0 +1,72 @@
+package com.starlwr.bot.bilibili.report
+
+import com.starlwr.bot.core.plugin.StarBotComponent
+import org.slf4j.LoggerFactory
+import org.springframework.boot.context.properties.ConfigurationProperties
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.context.annotation.Bean
+import org.springframework.core.env.Environment
+import java.nio.file.Files
+import java.nio.file.Path
+
+@ConfigurationProperties("starbot.bilibili.live-report.storage")
+class LiveReportStorageProperties {
+ var type: String = "sqlite"
+ var redisUri: String = "redis://localhost:6379/0"
+ var redisPrefix: String = "starbot:report:v1"
+ var jdbcUrl: String? = null
+ var username: String? = null
+ var password: String? = null
+ var sqliteFile: String? = null
+ var failFast: Boolean = false
+ var migrateLegacy: Boolean = true
+ var communityJsonl: String? = null
+ var v2RedisUri: String = "redis://localhost:6379/0"
+ var bufferCapacity: Int = 20_000
+ var batchSize: Int = 500
+ var flushMillis: Long = 1_000
+}
+
+@StarBotComponent
+@EnableConfigurationProperties(LiveReportStorageProperties::class)
+class LiveReportStorageConfig(private val environment: Environment) {
+ private val log = LoggerFactory.getLogger(javaClass)
+
+ @Bean(destroyMethod = "close")
+ fun liveReportDataDriver(properties: LiveReportStorageProperties): LiveReportDataDriver {
+ val configured = when (properties.type.lowercase()) {
+ "redis" -> RedisLiveReportDataDriver(properties.redisUri, properties.redisPrefix)
+ "mysql" -> JdbcLiveReportDataDriver("mysql",
+ requireNotNull(properties.jdbcUrl) { "jdbc-url is required for MySQL report storage" },
+ properties.username, properties.password)
+ "sqlite" -> {
+ val path = properties.sqliteFile?.let(Path::of) ?: defaultSqlitePath()
+ Files.createDirectories(path.toAbsolutePath().parent)
+ JdbcLiveReportDataDriver("sqlite", "jdbc:sqlite:${path.toAbsolutePath()}")
+ }
+ "memory" -> InMemoryLiveReportDataDriver()
+ else -> error("Unsupported live report storage type: ${properties.type}")
+ }
+ return try {
+ configured.initialize()
+ if (configured is InMemoryLiveReportDataDriver) configured else BufferedLiveReportDataDriver(configured,
+ properties.bufferCapacity, properties.batchSize, properties.flushMillis)
+ }
+ catch (e: Exception) {
+ configured.close()
+ if (properties.failFast) throw e
+ log.error("直播报告存储 {} 初始化失败,临时降级到有界进程内存;恢复前不会写入持久层", properties.type, e)
+ InMemoryLiveReportDataDriver().also { it.initialize() }
+ }
+ }
+
+ private fun defaultSqlitePath(): Path {
+ val explicit = environment.getProperty("spring.config.location")?.split(',')?.firstOrNull()
+ ?: environment.getProperty("spring.config.additional-location")?.split(',')?.firstOrNull()
+ ?: System.getProperty("spring.config.location")?.split(',')?.firstOrNull()
+ val configPath = explicit?.removePrefix("optional:")?.removePrefix("file:")?.let(Path::of)
+ val directory = configPath?.let { if (Files.isDirectory(it)) it else it.toAbsolutePath().parent }
+ ?: Path.of(System.getProperty("user.dir"), "config")
+ return directory.resolve("starbot-live-report.sqlite3")
+ }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/RedisLiveReportDataDriver.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/RedisLiveReportDataDriver.kt
new file mode 100644
index 0000000..123338b
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/RedisLiveReportDataDriver.kt
@@ -0,0 +1,101 @@
+package com.starlwr.bot.bilibili.report
+
+import com.alibaba.fastjson2.JSON
+import io.lettuce.core.RedisClient
+import io.lettuce.core.ScriptOutputType
+
+class RedisLiveReportDataDriver(uri: String, private val prefix: String = "starbot:report:v1") : LiveReportDataDriver {
+ override val id = "redis"
+ private val client = RedisClient.create(uri)
+ private val connection = client.connect()
+ private val redis get() = connection.sync()
+ override fun initialize() { check(redis.ping() == "PONG") { "Redis is unavailable" } }
+
+ override fun createOrResume(session: ReportSession): LiveReportSnapshot {
+ val key = sessionKey(session.sessionId)
+ redis.setnx(key, encode(session.snapshot()))
+ redis.expire(key, SESSION_TTL_SECONDS)
+ return decode(redis.get(key))
+ }
+
+ override fun apply(session: ReportSession, eventId: String, delta: ReportDelta): Boolean {
+ repeat(32) {
+ val snapshotKey = sessionKey(session.sessionId)
+ val expected = redis.get(snapshotKey) ?: encode(session.snapshot())
+ val next = decode(expected).also { it.apply(delta) }
+ val result = try { redis.eval(APPLY_LUA, ScriptOutputType.INTEGER,
+ arrayOf(snapshotKey, eventKey(session.sessionId)), expected, encode(next), eventId, SESSION_TTL_SECONDS.toString()) }
+ catch (e: io.lettuce.core.RedisCommandExecutionException) {
+ if (e.message?.contains("scripting support disabled", true) == true)
+ return applyWithoutLua(session, eventId, delta) else throw e
+ }
+ when (result) { 1L -> return true; 0L -> return false }
+ }
+ error("Concurrent report update retry limit exceeded for ${session.sessionId}")
+ }
+
+ override fun snapshot(sessionId: String): LiveReportSnapshot? = redis.get(sessionKey(sessionId))?.let(::decode)
+ override fun complete(sessionId: String, endedAt: Long): LiveReportSnapshot? {
+ repeat(32) {
+ val key = sessionKey(sessionId); val expected = redis.get(key) ?: return null
+ val next = decode(expected).also { it.endedAt = endedAt }; val encoded = encode(next)
+ val changed = try { redis.eval(COMPLETE_LUA, ScriptOutputType.INTEGER,
+ arrayOf(key, recentKey(next.uid)), expected, encoded, sessionId, next.startedAt.toString(), HISTORY_TTL_SECONDS.toString()) }
+ catch (e: io.lettuce.core.RedisCommandExecutionException) {
+ if (e.message?.contains("scripting support disabled", true) == true)
+ return completeWithoutLua(sessionId, endedAt) else throw e
+ }
+ if (changed == 1L) return next
+ }
+ error("Concurrent report completion retry limit exceeded for $sessionId")
+ }
+ override fun recent(uid: Long, limit: Int): List = redis.zrevrange(recentKey(uid), 0, limit.coerceIn(1, 100).toLong() - 1)
+ .mapNotNull { snapshot(it) }
+ override fun health() = runCatching { redis.ping() }.fold({ DriverHealth(it == "PONG") }, { DriverHealth(false, it.message ?: "redis error") })
+ override fun close() { connection.close(); client.shutdown() }
+
+ @Synchronized private fun applyWithoutLua(session: ReportSession, eventId: String, delta: ReportDelta): Boolean {
+ repeat(32) {
+ val key = sessionKey(session.sessionId); val events = eventKey(session.sessionId)
+ redis.watch(key, events)
+ if (redis.sismember(events, eventId)) { redis.unwatch(); return false }
+ val current = redis.get(key)?.let(::decode) ?: session.snapshot(); current.apply(delta)
+ redis.multi(); redis.setex(key, SESSION_TTL_SECONDS, encode(current)); redis.sadd(events, eventId)
+ redis.expire(events, SESSION_TTL_SECONDS)
+ if (!redis.exec().wasDiscarded()) return true
+ }
+ error("Concurrent report update retry limit exceeded for ${session.sessionId}")
+ }
+
+ @Synchronized private fun completeWithoutLua(sessionId: String, endedAt: Long): LiveReportSnapshot? {
+ repeat(32) {
+ val key = sessionKey(sessionId); redis.watch(key); val current = redis.get(key)?.let(::decode) ?: run { redis.unwatch(); return null }
+ current.endedAt = endedAt; redis.multi(); redis.setex(key, HISTORY_TTL_SECONDS, encode(current))
+ redis.zadd(recentKey(current.uid), current.startedAt.toDouble(), sessionId)
+ if (!redis.exec().wasDiscarded()) return current
+ }
+ error("Concurrent report completion retry limit exceeded for $sessionId")
+ }
+
+ private fun sessionKey(id: String) = "$prefix:{$id}:snapshot"
+ private fun eventKey(id: String) = "$prefix:{$id}:events"
+ private fun recentKey(uid: Long) = "$prefix:recent:$uid"
+ private fun encode(s: LiveReportSnapshot) = JSON.toJSONString(s)
+ private fun decode(s: String) = LiveReportSchemaMigration.migrate(JSON.parseObject(s, LiveReportSnapshot::class.java))
+ companion object {
+ private const val SESSION_TTL_SECONDS = 7 * 24 * 3600L
+ private const val HISTORY_TTL_SECONDS = 365 * 24 * 3600L
+ private const val APPLY_LUA = """
+ if redis.call('SISMEMBER', KEYS[2], ARGV[3]) == 1 then return 0 end
+ local current = redis.call('GET', KEYS[1])
+ if current and current ~= ARGV[1] then return -1 end
+ redis.call('SET', KEYS[1], ARGV[2], 'EX', ARGV[4])
+ redis.call('SADD', KEYS[2], ARGV[3]); redis.call('EXPIRE', KEYS[2], ARGV[4]); return 1
+ """
+ private const val COMPLETE_LUA = """
+ if redis.call('GET', KEYS[1]) ~= ARGV[1] then return 0 end
+ redis.call('SET', KEYS[1], ARGV[2], 'EX', ARGV[5])
+ redis.call('ZADD', KEYS[2], ARGV[4], ARGV[3]); return 1
+ """
+ }
+}
diff --git a/src/main/kotlin/com/starlwr/bot/bilibili/report/ReportArchive.kt b/src/main/kotlin/com/starlwr/bot/bilibili/report/ReportArchive.kt
new file mode 100644
index 0000000..423895d
--- /dev/null
+++ b/src/main/kotlin/com/starlwr/bot/bilibili/report/ReportArchive.kt
@@ -0,0 +1,37 @@
+package com.starlwr.bot.bilibili.report
+
+import com.alibaba.fastjson2.JSON
+import com.google.protobuf.CodedInputStream
+import com.google.protobuf.CodedOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.security.MessageDigest
+
+/** Versioned protobuf-TLV envelope. JSON payload keeps driver-independent snapshots readable. */
+object ReportArchive {
+ private const val FORMAT_VERSION = 1
+ fun write(snapshot: LiveReportSnapshot, output: OutputStream) {
+ val payload = JSON.toJSONBytes(snapshot); val checksum = MessageDigest.getInstance("SHA-256").digest(payload)
+ val size = CodedOutputStream.computeUInt32Size(1, FORMAT_VERSION) +
+ CodedOutputStream.computeUInt32Size(2, snapshot.schemaVersion) +
+ CodedOutputStream.computeByteArraySize(3, payload) + CodedOutputStream.computeByteArraySize(4, checksum)
+ val coded = CodedOutputStream.newInstance(output); coded.writeUInt32NoTag(size)
+ coded.writeUInt32(1, FORMAT_VERSION); coded.writeUInt32(2, snapshot.schemaVersion)
+ coded.writeByteArray(3, payload); coded.writeByteArray(4, checksum); coded.flush()
+ }
+ fun read(input: InputStream): Sequence = sequence {
+ val coded = CodedInputStream.newInstance(input)
+ while (!coded.isAtEnd) {
+ val length = coded.readUInt32(); val old = coded.pushLimit(length); var format = 0; var schema = 0
+ var payload = ByteArray(0); var checksum = ByteArray(0)
+ while (coded.bytesUntilLimit > 0) when (val tag = coded.readTag()) {
+ 8 -> format = coded.readUInt32(); 16 -> schema = coded.readUInt32()
+ 26 -> payload = coded.readByteArray(); 34 -> checksum = coded.readByteArray()
+ else -> if (!coded.skipField(tag)) break
+ }
+ coded.popLimit(old); require(format == FORMAT_VERSION); require(schema <= LiveReportSnapshot.CURRENT_SCHEMA)
+ require(MessageDigest.isEqual(checksum, MessageDigest.getInstance("SHA-256").digest(payload))) { "Archive checksum mismatch" }
+ yield(LiveReportSchemaMigration.migrate(JSON.parseObject(payload, LiveReportSnapshot::class.java)))
+ }
+ }
+}
diff --git a/src/main/resources/META-INF/native-image/com.starlwr/starbot-bilibili-agent/reachability-metadata.json b/src/main/resources/META-INF/native-image/com.starlwr/starbot-bilibili-agent/reachability-metadata.json
new file mode 100644
index 0000000..6f6e8e9
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/com.starlwr/starbot-bilibili-agent/reachability-metadata.json
@@ -0,0 +1,6852 @@
+{
+ "reflection": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "boolean[]"
+ },
+ {
+ "type": "ch.qos.logback.classic.BasicConfigurator",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "ch.qos.logback.classic.LoggerContext"
+ },
+ {
+ "type": "ch.qos.logback.classic.spi.LogbackServiceProvider"
+ },
+ {
+ "type": "ch.qos.logback.classic.util.DefaultJoranConfigurator",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.couchbase.client.java.Cluster"
+ },
+ {
+ "type": "com.fasterxml.jackson.databind.ObjectMapper"
+ },
+ {
+ "type": "com.fasterxml.jackson.dataformat.cbor.CBORFactory"
+ },
+ {
+ "type": "com.fasterxml.jackson.dataformat.smile.SmileFactory"
+ },
+ {
+ "type": "com.fasterxml.jackson.dataformat.xml.XmlMapper"
+ },
+ {
+ "type": "com.fasterxml.jackson.dataformat.yaml.YAMLFactory"
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.BLCHeader$DrainStatusRef",
+ "fields": [
+ {
+ "name": "drainStatus"
+ }
+ ]
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.BaseMpscLinkedArrayQueueColdProducerFields",
+ "fields": [
+ {
+ "name": "producerLimit"
+ }
+ ]
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.BaseMpscLinkedArrayQueueConsumerFields",
+ "fields": [
+ {
+ "name": "consumerIndex"
+ }
+ ]
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.BaseMpscLinkedArrayQueueProducerFields",
+ "fields": [
+ {
+ "name": "producerIndex"
+ }
+ ]
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.BoundedLocalCache",
+ "fields": [
+ {
+ "name": "refreshes"
+ }
+ ]
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.Caffeine"
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.PS",
+ "fields": [
+ {
+ "name": "key"
+ },
+ {
+ "name": "value"
+ }
+ ]
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.PSA",
+ "fields": [
+ {
+ "name": "accessTime"
+ }
+ ]
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.PSAMS",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.PSW",
+ "fields": [
+ {
+ "name": "writeTime"
+ }
+ ]
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.PSWMS",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.SSMSA",
+ "fields": [
+ {
+ "name": "FACTORY"
+ }
+ ]
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.SSMSW",
+ "fields": [
+ {
+ "name": "FACTORY"
+ }
+ ]
+ },
+ {
+ "type": "com.github.benmanes.caffeine.cache.StripedBuffer",
+ "fields": [
+ {
+ "name": "tableBusy"
+ }
+ ]
+ },
+ {
+ "type": "com.github.mxab.thymeleaf.extras.dataattribute.dialect.DataAttributeDialect"
+ },
+ {
+ "type": "com.google.common.base.Optional"
+ },
+ {
+ "type": "com.google.gson.Gson"
+ },
+ {
+ "type": "com.hazelcast.core.HazelcastInstance"
+ },
+ {
+ "type": "com.querydsl.core.types.Predicate"
+ },
+ {
+ "type": "com.rometools.rome.feed.WireFeed"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.StarBotBilibiliApplication",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.StarBotBilibiliApplicationKt",
+ "methods": [
+ {
+ "name": "main",
+ "parameterTypes": [
+ "java.lang.String[]"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.config.StarBotBilibiliCacheConfig",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.cache.caffeine.CaffeineCacheManager"
+ ]
+ },
+ {
+ "name": "init",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.config.StarBotBilibiliCacheKeyConfig"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.config.StarBotBilibiliCacheKeyConfig$$SpringCGLIB$$0",
+ "fields": [
+ {
+ "name": "$$beanFactory"
+ },
+ {
+ "name": "CGLIB$CALLBACK_FILTER"
+ },
+ {
+ "name": "CGLIB$FACTORY_DATA"
+ }
+ ],
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "CGLIB$SET_STATIC_CALLBACKS",
+ "parameterTypes": [
+ "org.springframework.cglib.proxy.Callback[]"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.config.StarBotBilibiliCacheKeyConfig$$SpringCGLIB$$FastClass$$0",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "java.lang.Class"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.config.StarBotBilibiliCacheKeyConfig$$SpringCGLIB$$FastClass$$1",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "java.lang.Class"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.config.StarBotBilibiliLogConfig",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "init",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.config.StarBotBilibiliProperties",
+ "methods": [
+ {
+ "name": "getDynamic",
+ "parameterTypes": []
+ },
+ {
+ "name": "getLive",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.config.StarBotBilibiliProperties$$SpringCGLIB$$0",
+ "fields": [
+ {
+ "name": "$$beanFactory"
+ },
+ {
+ "name": "CGLIB$CALLBACK_FILTER"
+ },
+ {
+ "name": "CGLIB$FACTORY_DATA"
+ }
+ ],
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "CGLIB$SET_STATIC_CALLBACKS",
+ "parameterTypes": [
+ "org.springframework.cglib.proxy.Callback[]"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.config.StarBotBilibiliProperties$Dynamic",
+ "methods": [
+ {
+ "name": "setAutoFollow",
+ "parameterTypes": [
+ "boolean"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.config.StarBotBilibiliProperties$Live",
+ "methods": [
+ {
+ "name": "setEnableConnectLiveRoom",
+ "parameterTypes": [
+ "boolean"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.config.StarBotBilibiliThreadPoolConfig",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "com.starlwr.bot.bilibili.config.StarBotBilibiliProperties"
+ ]
+ },
+ {
+ "name": "bilibiliThreadPool",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.config.StarBotBilibiliThymeleafConfig",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.thymeleaf.spring6.SpringTemplateEngine"
+ ]
+ },
+ {
+ "name": "registerPluginTemplateResolver",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.controller.BilibiliLoginController",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "com.starlwr.bot.bilibili.service.BilibiliAccountService"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.factory.BilibiliDynamicPainterFactory",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "com.starlwr.bot.bilibili.config.StarBotBilibiliProperties",
+ "com.starlwr.bot.core.util.FontUtil",
+ "com.starlwr.bot.bilibili.util.BilibiliApiUtil",
+ "com.starlwr.bot.core.factory.StarBotCommonPainterFactory"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.factory.BilibiliLiveRoomConnectorFactory"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.handler.BilibiliDynamicPushHandler"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.handler.BilibiliLiveOffPushHandler"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.handler.BilibiliLiveOnPushHandler"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.listener.BilibiliDataSourceEventListener"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.listener.BilibiliLiveDataListener"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.report.BlindBoxCommandController"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.report.BlindBoxLiveOffReportHandler"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.report.BlindBoxLiveOnResetHandler"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.report.BlindBoxRecordHandler"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.report.LegacyReportMigrator"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.report.LiveReportBaselineCollector"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.report.LiveReportCollector"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.report.LiveReportDataDriver"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.report.LiveReportDemandService"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.report.LiveReportPainter"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.report.LiveReportPushHandler"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.report.LiveReportStorageConfig"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.report.LiveReportStorageProperties"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.service.BilibiliAccountService",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.context.ApplicationContext",
+ "org.springframework.boot.web.server.context.WebServerApplicationContext",
+ "com.starlwr.bot.bilibili.util.BilibiliApiUtil"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.service.BilibiliBackupLivePushService"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.service.BilibiliDataSourceService"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.service.BilibiliDynamicService"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.service.BilibiliEventParser"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.service.BilibiliGiftService"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.service.BilibiliLiveRoomConnectTaskService"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.service.BilibiliLiveRoomService"
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.util.BilibiliApiUtil",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "com.starlwr.bot.bilibili.config.StarBotBilibiliProperties",
+ "com.starlwr.bot.core.util.HttpUtil"
+ ]
+ },
+ {
+ "name": "init",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.bilibili.util.BilibiliApiUtil$$SpringCGLIB$$0",
+ "fields": [
+ {
+ "name": "CGLIB$CALLBACK_FILTER"
+ },
+ {
+ "name": "CGLIB$FACTORY_DATA"
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.DataSourceConfig"
+ },
+ {
+ "type": "com.starlwr.bot.core.config.DataSourceConfig$$SpringCGLIB$$0",
+ "fields": [
+ {
+ "name": "CGLIB$CALLBACK_FILTER"
+ },
+ {
+ "name": "CGLIB$FACTORY_DATA"
+ }
+ ],
+ "methods": [
+ {
+ "name": "CGLIB$SET_STATIC_CALLBACKS",
+ "parameterTypes": [
+ "org.springframework.cglib.proxy.Callback[]"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.DataSourceConfig$$SpringCGLIB$$FastClass$$0",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "java.lang.Class"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.DataSourceConfig$$SpringCGLIB$$FastClass$$1",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "java.lang.Class"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.EventConfig",
+ "methods": [
+ {
+ "name": "applicationEventMulticaster",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.EventConfig$$SpringCGLIB$$0",
+ "fields": [
+ {
+ "name": "$$beanFactory"
+ },
+ {
+ "name": "CGLIB$CALLBACK_FILTER"
+ },
+ {
+ "name": "CGLIB$FACTORY_DATA"
+ }
+ ],
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "CGLIB$SET_STATIC_CALLBACKS",
+ "parameterTypes": [
+ "org.springframework.cglib.proxy.Callback[]"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.EventConfig$$SpringCGLIB$$FastClass$$0",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "java.lang.Class"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.EventConfig$$SpringCGLIB$$FastClass$$1",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "java.lang.Class"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.RestTemplateConfig",
+ "methods": [
+ {
+ "name": "restTemplate",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.RestTemplateConfig$$SpringCGLIB$$0",
+ "fields": [
+ {
+ "name": "$$beanFactory"
+ },
+ {
+ "name": "CGLIB$CALLBACK_FILTER"
+ },
+ {
+ "name": "CGLIB$FACTORY_DATA"
+ }
+ ],
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "com.starlwr.bot.core.config.StarBotCoreProperties"
+ ]
+ },
+ {
+ "name": "CGLIB$SET_STATIC_CALLBACKS",
+ "parameterTypes": [
+ "org.springframework.cglib.proxy.Callback[]"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.RestTemplateConfig$$SpringCGLIB$$FastClass$$0",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "java.lang.Class"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.RestTemplateConfig$$SpringCGLIB$$FastClass$$1",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "java.lang.Class"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.StarBotCoreProperties",
+ "methods": [
+ {
+ "name": "init",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.StarBotCoreProperties$$SpringCGLIB$$0",
+ "fields": [
+ {
+ "name": "$$beanFactory"
+ },
+ {
+ "name": "CGLIB$CALLBACK_FILTER"
+ },
+ {
+ "name": "CGLIB$FACTORY_DATA"
+ }
+ ],
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "CGLIB$SET_STATIC_CALLBACKS",
+ "parameterTypes": [
+ "org.springframework.cglib.proxy.Callback[]"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.StarBotCoreThreadPoolConfig",
+ "methods": [
+ {
+ "name": "networkThreadPool",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.StarBotCoreThreadPoolConfig$$SpringCGLIB$$0",
+ "fields": [
+ {
+ "name": "$$beanFactory"
+ },
+ {
+ "name": "CGLIB$CALLBACK_FILTER"
+ },
+ {
+ "name": "CGLIB$FACTORY_DATA"
+ }
+ ],
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "com.starlwr.bot.core.config.StarBotCoreProperties"
+ ]
+ },
+ {
+ "name": "CGLIB$SET_STATIC_CALLBACKS",
+ "parameterTypes": [
+ "org.springframework.cglib.proxy.Callback[]"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.StarBotCoreThreadPoolConfig$$SpringCGLIB$$FastClass$$0",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "java.lang.Class"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.config.StarBotCoreThreadPoolConfig$$SpringCGLIB$$FastClass$$1",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "java.lang.Class"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.converter.ColorConverter",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.converter.FontConverter",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.datasource.AbstractDataSource"
+ },
+ {
+ "type": "com.starlwr.bot.core.datasource.DataSource"
+ },
+ {
+ "type": "com.starlwr.bot.core.datasource.DataSourceServiceRegistry"
+ },
+ {
+ "type": "com.starlwr.bot.core.factory.StarBotCommonPainterFactory",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.boot.info.BuildProperties",
+ "com.starlwr.bot.core.config.StarBotCoreProperties",
+ "com.starlwr.bot.core.util.FontUtil"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.handler.DefaultHandlerForEvent"
+ },
+ {
+ "type": "com.starlwr.bot.core.listener.LoadDataSourceListener"
+ },
+ {
+ "type": "com.starlwr.bot.core.listener.StarBotDefaultDanmuEventListener"
+ },
+ {
+ "type": "com.starlwr.bot.core.listener.StarBotDefaultLiveOffEventListener"
+ },
+ {
+ "type": "com.starlwr.bot.core.listener.StarBotDefaultLiveOnEventListener"
+ },
+ {
+ "type": "com.starlwr.bot.core.listener.StarBotHandlerListener"
+ },
+ {
+ "type": "com.starlwr.bot.core.multicaster.InterruptibleEventMulticaster"
+ },
+ {
+ "type": "com.starlwr.bot.core.plugin.StarBotComponent"
+ },
+ {
+ "type": "com.starlwr.bot.core.plugin.StarBotPluginDependencyDownloader"
+ },
+ {
+ "type": "com.starlwr.bot.core.sender.StarBotMessageSender"
+ },
+ {
+ "type": "com.starlwr.bot.core.service.DataSourceServiceConfig"
+ },
+ {
+ "type": "com.starlwr.bot.core.service.DefaultLiveDataService",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "com.starlwr.bot.core.config.StarBotCoreProperties"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.service.LiveDataService"
+ },
+ {
+ "type": "com.starlwr.bot.core.service.StarBotEventHandlerService"
+ },
+ {
+ "type": "com.starlwr.bot.core.service.StarBotMailService"
+ },
+ {
+ "type": "com.starlwr.bot.core.service.StarBotSenderService"
+ },
+ {
+ "type": "com.starlwr.bot.core.util.FontUtil",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.core.io.ResourceLoader",
+ "com.starlwr.bot.core.config.StarBotCoreProperties"
+ ]
+ },
+ {
+ "name": "init",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.starlwr.bot.core.util.HttpUtil",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor",
+ "org.springframework.web.client.RestTemplate",
+ "com.starlwr.bot.core.config.StarBotCoreProperties"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "groovy.lang.MetaClass"
+ },
+ {
+ "type": "int[]"
+ },
+ {
+ "type": "io.micrometer.core.instrument.MeterRegistry"
+ },
+ {
+ "type": "io.micrometer.core.instrument.binder.tomcat.TomcatMetrics"
+ },
+ {
+ "type": "io.micrometer.observation.Observation"
+ },
+ {
+ "type": "io.micrometer.observation.ObservationRegistry"
+ },
+ {
+ "type": "io.reactivex.rxjava3.core.Flowable"
+ },
+ {
+ "type": "io.smallrye.mutiny.Multi"
+ },
+ {
+ "type": "io.vavr.control.Option"
+ },
+ {
+ "type": "jakarta.activation.MimeType"
+ },
+ {
+ "type": "jakarta.annotation.PostConstruct"
+ },
+ {
+ "type": "jakarta.annotation.PreDestroy"
+ },
+ {
+ "type": "jakarta.annotation.Resource"
+ },
+ {
+ "type": "jakarta.ejb.Asynchronous"
+ },
+ {
+ "type": "jakarta.ejb.EJB"
+ },
+ {
+ "type": "jakarta.enterprise.concurrent.Asynchronous"
+ },
+ {
+ "type": "jakarta.faces.context.FacesContext"
+ },
+ {
+ "type": "jakarta.inject.Inject"
+ },
+ {
+ "type": "jakarta.inject.Named"
+ },
+ {
+ "type": "jakarta.inject.Provider"
+ },
+ {
+ "type": "jakarta.inject.Qualifier"
+ },
+ {
+ "type": "jakarta.json.bind.Jsonb"
+ },
+ {
+ "type": "jakarta.mail.internet.MimeMessage"
+ },
+ {
+ "type": "jakarta.persistence.CheckConstraint"
+ },
+ {
+ "type": "jakarta.persistence.Entity"
+ },
+ {
+ "type": "jakarta.persistence.EntityManager"
+ },
+ {
+ "type": "jakarta.persistence.EntityManagerFactory"
+ },
+ {
+ "type": "jakarta.persistence.Index"
+ },
+ {
+ "type": "jakarta.persistence.PersistenceContext"
+ },
+ {
+ "type": "jakarta.persistence.Table"
+ },
+ {
+ "type": "jakarta.persistence.UniqueConstraint"
+ },
+ {
+ "type": "jakarta.servlet.Filter"
+ },
+ {
+ "type": "jakarta.servlet.GenericFilter"
+ },
+ {
+ "type": "jakarta.servlet.GenericServlet"
+ },
+ {
+ "type": "jakarta.servlet.MultipartConfigElement"
+ },
+ {
+ "type": "jakarta.servlet.Servlet"
+ },
+ {
+ "type": "jakarta.servlet.ServletConfig"
+ },
+ {
+ "type": "jakarta.servlet.ServletRegistration"
+ },
+ {
+ "type": "jakarta.servlet.ServletRequest"
+ },
+ {
+ "type": "jakarta.servlet.http.HttpServlet"
+ },
+ {
+ "type": "jakarta.transaction.Transaction"
+ },
+ {
+ "type": "jakarta.transaction.TransactionManager"
+ },
+ {
+ "type": "jakarta.validation.Validator"
+ },
+ {
+ "type": "jakarta.websocket.ClientEndpoint"
+ },
+ {
+ "type": "jakarta.xml.bind.Binder"
+ },
+ {
+ "type": "jakarta.xml.ws.WebServiceRef"
+ },
+ {
+ "type": "java.awt.Component",
+ "jniAccessible": true
+ },
+ {
+ "type": "java.awt.Font",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "name"
+ },
+ {
+ "name": "pData"
+ },
+ {
+ "name": "size"
+ },
+ {
+ "name": "style"
+ }
+ ],
+ "methods": [
+ {
+ "name": "getFont",
+ "parameterTypes": [
+ "java.lang.String"
+ ]
+ },
+ {
+ "name": "getFontPeer",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "java.awt.Insets",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "int",
+ "int",
+ "int",
+ "int"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "java.awt.Toolkit",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "getDefaultToolkit",
+ "parameterTypes": []
+ },
+ {
+ "name": "getFontMetrics",
+ "parameterTypes": [
+ "java.awt.Font"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "java.awt.desktop.UserSessionEvent$Reason",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "CONSOLE"
+ },
+ {
+ "name": "LOCK"
+ },
+ {
+ "name": "REMOTE"
+ },
+ {
+ "name": "UNSPECIFIED"
+ }
+ ]
+ },
+ {
+ "type": "java.awt.geom.GeneralPath",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "",
+ "parameterTypes": [
+ "int",
+ "byte[]",
+ "int",
+ "float[]",
+ "int"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "java.awt.geom.Point2D$Float",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "x"
+ },
+ {
+ "name": "y"
+ }
+ ],
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "float",
+ "float"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "java.awt.geom.Rectangle2D$Float",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "height"
+ },
+ {
+ "name": "width"
+ },
+ {
+ "name": "x"
+ },
+ {
+ "name": "y"
+ }
+ ],
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "",
+ "parameterTypes": [
+ "float",
+ "float",
+ "float",
+ "float"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "java.awt.image.ColorModel",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "colorSpace"
+ },
+ {
+ "name": "colorSpaceType"
+ },
+ {
+ "name": "isAlphaPremultiplied"
+ },
+ {
+ "name": "is_sRGB"
+ },
+ {
+ "name": "nBits"
+ },
+ {
+ "name": "numComponents"
+ },
+ {
+ "name": "supportsAlpha"
+ },
+ {
+ "name": "transparency"
+ }
+ ],
+ "methods": [
+ {
+ "name": "getRGBdefault",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "java.awt.image.IndexColorModel",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "allgrayopaque"
+ },
+ {
+ "name": "map_size"
+ },
+ {
+ "name": "rgb"
+ },
+ {
+ "name": "transparent_index"
+ }
+ ]
+ },
+ {
+ "type": "java.io.Closeable"
+ },
+ {
+ "type": "java.io.Serializable"
+ },
+ {
+ "type": "java.lang.AutoCloseable"
+ },
+ {
+ "type": "java.lang.Boolean",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "getBoolean",
+ "parameterTypes": [
+ "java.lang.String"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "java.lang.Class",
+ "methods": [
+ {
+ "name": "isSealed",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "java.lang.ClassLoader",
+ "fields": [
+ {
+ "name": "classLoaderValueMap"
+ }
+ ]
+ },
+ {
+ "type": "java.lang.ClassValue"
+ },
+ {
+ "type": "java.lang.Class[]"
+ },
+ {
+ "type": "java.lang.CloneNotSupportedException"
+ },
+ {
+ "type": "java.lang.Error"
+ },
+ {
+ "type": "java.lang.Iterable"
+ },
+ {
+ "type": "java.lang.Module"
+ },
+ {
+ "type": "java.lang.Object",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "java.lang.RuntimeException"
+ },
+ {
+ "type": "java.lang.String",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "toLowerCase",
+ "parameterTypes": [
+ "java.util.Locale"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "java.lang.String[]"
+ },
+ {
+ "type": "java.lang.Thread"
+ },
+ {
+ "type": "java.lang.Thread$Builder"
+ },
+ {
+ "type": "java.lang.Throwable"
+ },
+ {
+ "type": "java.lang.WrongThreadException"
+ },
+ {
+ "type": "java.lang.annotation.Annotation"
+ },
+ {
+ "type": "java.lang.annotation.Documented"
+ },
+ {
+ "type": "java.lang.annotation.Inherited"
+ },
+ {
+ "type": "java.lang.annotation.Repeatable"
+ },
+ {
+ "type": "java.lang.annotation.Retention"
+ },
+ {
+ "type": "java.lang.annotation.Target"
+ },
+ {
+ "type": "java.lang.constant.Constable"
+ },
+ {
+ "type": "java.lang.invoke.TypeDescriptor$OfField"
+ },
+ {
+ "type": "java.lang.reflect.AnnotatedElement"
+ },
+ {
+ "type": "java.lang.reflect.GenericDeclaration"
+ },
+ {
+ "type": "java.lang.reflect.ParameterizedType",
+ "methods": [
+ {
+ "name": "getActualTypeArguments",
+ "parameterTypes": []
+ },
+ {
+ "name": "getRawType",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "java.lang.reflect.Type"
+ },
+ {
+ "type": "java.lang.reflect.UndeclaredThrowableException"
+ },
+ {
+ "type": "java.nio.file.Path"
+ },
+ {
+ "type": "java.text.ListFormat"
+ },
+ {
+ "type": "java.util.ArrayList",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "int"
+ ]
+ },
+ {
+ "name": "add",
+ "parameterTypes": [
+ "java.lang.Object"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "java.util.EventListener"
+ },
+ {
+ "type": "java.util.EventObject"
+ },
+ {
+ "type": "java.util.HashMap",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "containsKey",
+ "parameterTypes": [
+ "java.lang.Object"
+ ]
+ },
+ {
+ "name": "put",
+ "parameterTypes": [
+ "java.lang.Object",
+ "java.lang.Object"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "java.util.SequencedCollection"
+ },
+ {
+ "type": "java.util.concurrent.Callable"
+ },
+ {
+ "type": "java.util.concurrent.Executor"
+ },
+ {
+ "type": "java.util.concurrent.RejectedExecutionHandler"
+ },
+ {
+ "type": "java.util.concurrent.ScheduledExecutorService"
+ },
+ {
+ "type": "java.util.concurrent.ThreadFactory"
+ },
+ {
+ "type": "java.util.logging.LogManager"
+ },
+ {
+ "type": "java.util.logging.SimpleFormatter",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "javax.cache.Cache"
+ },
+ {
+ "type": "javax.cache.Caching"
+ },
+ {
+ "type": "javax.money.MonetaryAmount"
+ },
+ {
+ "type": "javax.naming.InitialContext"
+ },
+ {
+ "type": "javax.net.ssl.SSLParameters"
+ },
+ {
+ "type": "javax.security.auth.Subject"
+ },
+ {
+ "type": "javax.sql.DataSource"
+ },
+ {
+ "type": "jdk.crac.management.CRaCMXBean"
+ },
+ {
+ "type": "jdk.internal.misc.Unsafe"
+ },
+ {
+ "type": "kotlin.Any"
+ },
+ {
+ "type": "kotlin.Boolean"
+ },
+ {
+ "type": "kotlin.Int"
+ },
+ {
+ "type": "kotlin.Metadata",
+ "methods": [
+ {
+ "name": "bv",
+ "parameterTypes": []
+ },
+ {
+ "name": "d1",
+ "parameterTypes": []
+ },
+ {
+ "name": "d2",
+ "parameterTypes": []
+ },
+ {
+ "name": "k",
+ "parameterTypes": []
+ },
+ {
+ "name": "mv",
+ "parameterTypes": []
+ },
+ {
+ "name": "pn",
+ "parameterTypes": []
+ },
+ {
+ "name": "xi",
+ "parameterTypes": []
+ },
+ {
+ "name": "xs",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "kotlin.SafePublicationLazyImpl"
+ },
+ {
+ "type": "kotlin.String"
+ },
+ {
+ "type": "kotlin.annotation.AnnotationRetention"
+ },
+ {
+ "type": "kotlin.annotation.AnnotationTarget"
+ },
+ {
+ "type": "kotlin.annotation.AnnotationTarget[]"
+ },
+ {
+ "type": "kotlin.annotation.MustBeDocumented"
+ },
+ {
+ "type": "kotlin.annotation.Retention"
+ },
+ {
+ "type": "kotlin.annotation.Target"
+ },
+ {
+ "type": "kotlin.coroutines.Continuation"
+ },
+ {
+ "type": "kotlin.coroutines.jvm.internal.DebugMetadata"
+ },
+ {
+ "type": "kotlin.jvm.JvmInline"
+ },
+ {
+ "type": "kotlin.jvm.internal.DefaultConstructorMarker"
+ },
+ {
+ "type": "kotlin.reflect.full.KClasses"
+ },
+ {
+ "type": "kotlin.reflect.jvm.internal.ReflectionFactoryImpl",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "kotlin.reflect.jvm.internal.impl.load.java.ErasedOverridabilityCondition"
+ },
+ {
+ "type": "kotlin.reflect.jvm.internal.impl.load.java.FieldOverridabilityCondition"
+ },
+ {
+ "type": "kotlin.reflect.jvm.internal.impl.load.java.JavaIncompatibilityRulesOverridabilityCondition"
+ },
+ {
+ "type": "kotlin.reflect.jvm.internal.impl.resolve.scopes.DescriptorKindFilter",
+ "fields": [
+ {
+ "name": "ALL"
+ },
+ {
+ "name": "CALLABLES"
+ },
+ {
+ "name": "CLASSIFIERS"
+ },
+ {
+ "name": "Companion"
+ },
+ {
+ "name": "FUNCTIONS"
+ },
+ {
+ "name": "NON_SINGLETON_CLASSIFIERS"
+ },
+ {
+ "name": "PACKAGES"
+ },
+ {
+ "name": "SINGLETON_CLASSIFIERS"
+ },
+ {
+ "name": "TYPE_ALIASES"
+ },
+ {
+ "name": "VALUES"
+ },
+ {
+ "name": "VARIABLES"
+ }
+ ]
+ },
+ {
+ "type": "kotlinx.coroutines.reactor.MonoKt"
+ },
+ {
+ "type": "kotlinx.serialization.Serializable"
+ },
+ {
+ "type": "kotlinx.serialization.cbor.Cbor"
+ },
+ {
+ "type": "kotlinx.serialization.json.Json"
+ },
+ {
+ "type": "kotlinx.serialization.protobuf.ProtoBuf"
+ },
+ {
+ "type": "nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect"
+ },
+ {
+ "type": "org.aopalliance.aop.Advice"
+ },
+ {
+ "type": "org.aopalliance.intercept.Interceptor"
+ },
+ {
+ "type": "org.aopalliance.intercept.MethodInterceptor"
+ },
+ {
+ "type": "org.apache.catalina.startup.Tomcat"
+ },
+ {
+ "type": "org.apache.catalina.util.CharsetMapper",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.apache.commons.logging.LogFactory"
+ },
+ {
+ "type": "org.apache.commons.logging.impl.Slf4jLogFactory",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.apache.commons.logging.impl.WeakHashtable",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.apache.coyote.AbstractProtocol",
+ "methods": [
+ {
+ "name": "getAddress",
+ "parameterTypes": []
+ },
+ {
+ "name": "getNameIndex",
+ "parameterTypes": []
+ },
+ {
+ "name": "getProperty",
+ "parameterTypes": [
+ "java.lang.String"
+ ]
+ },
+ {
+ "name": "setPort",
+ "parameterTypes": [
+ "int"
+ ]
+ },
+ {
+ "name": "setProperty",
+ "parameterTypes": [
+ "java.lang.String",
+ "java.lang.String"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.apache.coyote.UpgradeProtocol"
+ },
+ {
+ "type": "org.apache.coyote.http11.AbstractHttp11Protocol",
+ "methods": [
+ {
+ "name": "isSSLEnabled",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.apache.coyote.http11.Http11NioProtocol"
+ },
+ {
+ "type": "org.apache.jasper.compiler.JspConfig"
+ },
+ {
+ "type": "org.apache.jasper.servlet.JspServlet"
+ },
+ {
+ "type": "org.apache.logging.log4j.core.impl.Log4jContextFactory"
+ },
+ {
+ "type": "org.apache.logging.log4j.util.EnvironmentPropertySource"
+ },
+ {
+ "type": "org.apache.logging.log4j.util.SystemPropertiesPropertySource"
+ },
+ {
+ "type": "org.apache.logging.slf4j.SLF4JProvider"
+ },
+ {
+ "type": "org.apache.tomcat.util.net.AbstractEndpoint",
+ "methods": [
+ {
+ "name": "setBindOnInit",
+ "parameterTypes": [
+ "boolean"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.apache.tomcat.util.net.NioEndpoint"
+ },
+ {
+ "type": "org.apache.tomcat.websocket.server.WsFilter"
+ },
+ {
+ "type": "org.apache.tomcat.websocket.server.WsSci"
+ },
+ {
+ "type": "org.aspectj.weaver.Advice"
+ },
+ {
+ "type": "org.cache2k.Cache2kBuilder"
+ },
+ {
+ "type": "org.eclipse.core.runtime.FileLocator"
+ },
+ {
+ "type": "org.graalvm.nativeimage.ImageInfo",
+ "methods": [
+ {
+ "name": "inImageCode",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.hibernate.engine.spi.SessionImplementor"
+ },
+ {
+ "type": "org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager"
+ },
+ {
+ "type": "org.jboss.logging.Logger"
+ },
+ {
+ "type": "org.osgi.framework.FrameworkUtil"
+ },
+ {
+ "type": "org.reactivestreams.Publisher"
+ },
+ {
+ "type": "org.slf4j.Logger"
+ },
+ {
+ "type": "org.slf4j.bridge.SLF4JBridgeHandler"
+ },
+ {
+ "type": "org.slf4j.helpers.Log4jLoggerFactory"
+ },
+ {
+ "type": "org.springframework.aop.Advisor"
+ },
+ {
+ "type": "org.springframework.aop.IntroductionAdvisor"
+ },
+ {
+ "type": "org.springframework.aop.IntroductionInfo"
+ },
+ {
+ "type": "org.springframework.aop.PointcutAdvisor"
+ },
+ {
+ "type": "org.springframework.aop.SpringProxy"
+ },
+ {
+ "type": "org.springframework.aop.TargetClassAware"
+ },
+ {
+ "type": "org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator"
+ },
+ {
+ "type": "org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor"
+ },
+ {
+ "type": "org.springframework.aop.framework.Advised"
+ },
+ {
+ "type": "org.springframework.aop.framework.AopConfigException"
+ },
+ {
+ "type": "org.springframework.aop.framework.AopInfrastructureBean"
+ },
+ {
+ "type": "org.springframework.aop.framework.ProxyConfig",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "setExposeProxy",
+ "parameterTypes": [
+ "boolean"
+ ]
+ },
+ {
+ "name": "setProxyTargetClass",
+ "parameterTypes": [
+ "boolean"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.aop.framework.ProxyProcessorSupport",
+ "methods": [
+ {
+ "name": "setOrder",
+ "parameterTypes": [
+ "int"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator"
+ },
+ {
+ "type": "org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator"
+ },
+ {
+ "type": "org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor"
+ },
+ {
+ "type": "org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor"
+ },
+ {
+ "type": "org.springframework.aop.support.AbstractPointcutAdvisor"
+ },
+ {
+ "type": "org.springframework.aot.generate.Generated"
+ },
+ {
+ "type": "org.springframework.aot.hint.annotation.Reflective"
+ },
+ {
+ "type": "org.springframework.beans.factory.Aware"
+ },
+ {
+ "type": "org.springframework.beans.factory.BeanClassLoaderAware"
+ },
+ {
+ "type": "org.springframework.beans.factory.BeanFactoryAware"
+ },
+ {
+ "type": "org.springframework.beans.factory.BeanNameAware"
+ },
+ {
+ "type": "org.springframework.beans.factory.DisposableBean"
+ },
+ {
+ "type": "org.springframework.beans.factory.FactoryBean"
+ },
+ {
+ "type": "org.springframework.beans.factory.InitializingBean"
+ },
+ {
+ "type": "org.springframework.beans.factory.SmartInitializingSingleton"
+ },
+ {
+ "type": "org.springframework.beans.factory.annotation.Autowired"
+ },
+ {
+ "type": "org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.beans.factory.annotation.Qualifier"
+ },
+ {
+ "type": "org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor"
+ },
+ {
+ "type": "org.springframework.beans.factory.aot.BeanRegistrationAotProcessor"
+ },
+ {
+ "type": "org.springframework.beans.factory.config.BeanFactoryPostProcessor"
+ },
+ {
+ "type": "org.springframework.beans.factory.config.BeanPostProcessor"
+ },
+ {
+ "type": "org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor"
+ },
+ {
+ "type": "org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor"
+ },
+ {
+ "type": "org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor"
+ },
+ {
+ "type": "org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor"
+ },
+ {
+ "type": "org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor"
+ },
+ {
+ "type": "org.springframework.boot.ApplicationProperties"
+ },
+ {
+ "type": "org.springframework.boot.ClearCachesApplicationListener",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.SpringBootConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint"
+ },
+ {
+ "type": "org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties"
+ },
+ {
+ "type": "org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextFactory"
+ },
+ {
+ "type": "org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository"
+ },
+ {
+ "type": "org.springframework.boot.ansi.AnsiOutput$Enabled"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.AutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector$AutoConfigurationGroup",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.AutoConfigurationPackage"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.AutoConfigurationPackages$Registrar",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.AutoConfigureAfter"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.AutoConfigureBefore"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.AutoConfigureOrder"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.EnableAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.SpringBootApplication"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration$CglibAutoProxyConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.cache.CacheType"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnBean"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnClass"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnJndi"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingFilterBean"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnNotWarDeployment"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnProperty"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnResource"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnThreading"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication$Type"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.OnBeanCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.OnClassCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.OnJndiCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.OnPropertyCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.OnResourceCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.OnThreadingCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.OnWarDeploymentCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.condition.SearchStrategy"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.context.LifecycleProperties"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration$ResourceBundleCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration",
+ "methods": [
+ {
+ "name": "propertySourcesPlaceholderConfigurer",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.boot.autoconfigure.info.ProjectInfoProperties"
+ ]
+ },
+ {
+ "name": "buildProperties",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration$GitResourceAvailableCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.info.ProjectInfoProperties",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializingApplicationListener",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.preinitialize.CharsetsBackgroundPreinitializer",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.preinitialize.ConversionServiceBackgroundPreinitializer",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.ssl.FileWatcher"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.core.io.ResourceLoader",
+ "org.springframework.boot.autoconfigure.ssl.SslProperties"
+ ]
+ },
+ {
+ "name": "fileWatcher",
+ "parameterTypes": []
+ },
+ {
+ "name": "sslBundleRegistry",
+ "parameterTypes": [
+ "org.springframework.beans.factory.ObjectProvider"
+ ]
+ },
+ {
+ "name": "sslPropertiesSslBundleRegistrar",
+ "parameterTypes": [
+ "org.springframework.boot.autoconfigure.ssl.FileWatcher"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.ssl.SslBundleRegistrar"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.ssl.SslProperties",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.ssl.SslPropertiesBundleRegistrar"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.task.TaskExecutionProperties"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$BootstrapExecutorConfiguration",
+ "methods": [
+ {
+ "name": "bootstrapExecutorAliasPostProcessor",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$OnExecutorCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$SimpleAsyncTaskExecutorBuilderConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$TaskExecutorConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$ThreadPoolTaskExecutorBuilderConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$SimpleAsyncTaskSchedulerBuilderConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$TaskSchedulerConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "taskScheduler",
+ "parameterTypes": [
+ "org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$ThreadPoolTaskSchedulerBuilderConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "threadPoolTaskSchedulerBuilder",
+ "parameterTypes": [
+ "org.springframework.boot.autoconfigure.task.TaskSchedulingProperties",
+ "org.springframework.beans.factory.ObjectProvider",
+ "org.springframework.beans.factory.ObjectProvider"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingProperties",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain"
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.web.OnEnabledResourceChainCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.web.WebProperties",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.autoconfigure.web.WebResourcesRuntimeHints"
+ },
+ {
+ "type": "org.springframework.boot.availability.ApplicationAvailability"
+ },
+ {
+ "type": "org.springframework.boot.builder.ParentContextCloserApplicationListener",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.cache.autoconfigure.CacheAutoConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "cacheManagerCustomizers",
+ "parameterTypes": [
+ "org.springframework.beans.factory.ObjectProvider"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.cache.autoconfigure.CacheAutoConfiguration$CacheConfigurationImportSelector",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.cache.autoconfigure.CacheAutoConfiguration$CacheManagerEntityManagerFactoryDependsOnConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.cache.autoconfigure.CacheCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.cache.autoconfigure.CacheManagerCustomizers"
+ },
+ {
+ "type": "org.springframework.boot.cache.autoconfigure.CacheProperties",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.cache.autoconfigure.CaffeineCacheConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "cacheManager",
+ "parameterTypes": [
+ "org.springframework.boot.cache.autoconfigure.CacheProperties",
+ "org.springframework.boot.cache.autoconfigure.CacheManagerCustomizers",
+ "org.springframework.beans.factory.ObjectProvider",
+ "org.springframework.beans.factory.ObjectProvider",
+ "org.springframework.beans.factory.ObjectProvider"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.boot.logging.DeferredLogFactory"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.ContextIdApplicationContextInitializer",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.FileEncodingApplicationListener",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.boot.logging.DeferredLogFactory",
+ "org.springframework.boot.bootstrap.ConfigurableBootstrapContext"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.config.ConfigDataLocation[]"
+ },
+ {
+ "type": "org.springframework.boot.context.config.ConfigDataNotFoundAction"
+ },
+ {
+ "type": "org.springframework.boot.context.config.ConfigTreeConfigDataLoader",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.core.io.ResourceLoader"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.config.StandardConfigDataLoader",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.config.StandardConfigDataLocationResolver",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.boot.logging.DeferredLogFactory",
+ "org.springframework.boot.context.properties.bind.Binder",
+ "org.springframework.core.io.ResourceLoader"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.config.SystemEnvironmentConfigDataLoader",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.config.SystemEnvironmentConfigDataLocationResolver",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.event.EventPublishingRunListener",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.boot.SpringApplication",
+ "java.lang.String[]"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.logging.LoggingApplicationListener",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.properties.BoundConfigurationProperties",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.properties.ConfigurationProperties"
+ },
+ {
+ "type": "org.springframework.boot.context.properties.ConfigurationPropertiesBinder$ConfigurationPropertiesBinderFactory",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.properties.ConfigurationPropertiesBinding"
+ },
+ {
+ "type": "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.properties.ConfigurationPropertiesSource"
+ },
+ {
+ "type": "org.springframework.boot.context.properties.EnableConfigurationProperties"
+ },
+ {
+ "type": "org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.context.properties.NestedConfigurationProperty"
+ },
+ {
+ "type": "org.springframework.boot.context.properties.bind.Nested"
+ },
+ {
+ "type": "org.springframework.boot.convert.DurationUnit"
+ },
+ {
+ "type": "org.springframework.boot.data.autoconfigure.web.DataWebAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.data.autoconfigure.web.DataWebProperties"
+ },
+ {
+ "type": "org.springframework.boot.data.jpa.autoconfigure.DataJpaRepositoriesAutoConfiguration$JpaRepositoriesImportSelector",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.data.jpa.autoconfigure.DataJpaRepositoriesRegistrar",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.env.PropertiesPropertySourceLoader",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.env.YamlPropertySourceLoader",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.health.actuate.endpoint.HealthEndpoint"
+ },
+ {
+ "type": "org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator"
+ },
+ {
+ "type": "org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.hibernate.autoconfigure.HibernateJpaConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.http.converter.autoconfigure.GsonHttpMessageConvertersConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration$NotReactiveWebApplicationCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersProperties"
+ },
+ {
+ "type": "org.springframework.boot.http.converter.autoconfigure.Jackson2HttpMessageConvertersConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.http.converter.autoconfigure.Jackson2HttpMessageConvertersConfiguration$PreferJackson2OrJacksonUnavailableCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConverterConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.http.converter.autoconfigure.JsonbHttpMessageConvertersConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.http.converter.autoconfigure.KotlinSerializationHttpMessageConvertersConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.http.converter.autoconfigure.MessageConverterBackgroundPreinitializer",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.info.BuildProperties"
+ },
+ {
+ "type": "org.springframework.boot.info.InfoProperties"
+ },
+ {
+ "type": "org.springframework.boot.io.Base64ProtocolResolver",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.io.ClassPathResourceFilePathResolver",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.io.ProtocolResolverApplicationContextInitializer",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JacksonJsonMapperBuilderCustomizerConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JacksonMixinConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JsonProblemDetailsConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.jackson.autoconfigure.JacksonBackgroundPreinitializer",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.jackson.autoconfigure.JacksonProperties"
+ },
+ {
+ "type": "org.springframework.boot.jdbc.XADataSourceWrapper"
+ },
+ {
+ "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.jpa.autoconfigure.EntityManagerFactoryDependsOnPostProcessor"
+ },
+ {
+ "type": "org.springframework.boot.jpa.autoconfigure.JpaProperties"
+ },
+ {
+ "type": "org.springframework.boot.loader.launch.JarLauncher",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "main",
+ "parameterTypes": [
+ "java.lang.String[]"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.loader.net.protocol.nested.Handler",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.logging.java.JavaLoggingSystem$Factory",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem$Factory",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.logging.log4j2.SpringBootPropertySource"
+ },
+ {
+ "type": "org.springframework.boot.logging.logback.LogbackLoggingSystem$Factory",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.logging.logback.RootLogLevelConfigurator"
+ },
+ {
+ "type": "org.springframework.boot.mail.autoconfigure.MailSenderAutoConfiguration$MailSenderCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.persistence.autoconfigure.PersistenceExceptionTranslationAutoConfiguration",
+ "methods": [
+ {
+ "name": "persistenceExceptionTranslationPostProcessor",
+ "parameterTypes": [
+ "org.springframework.core.env.Environment"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.servlet.autoconfigure.HttpEncodingAutoConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "characterEncodingFilter",
+ "parameterTypes": [
+ "org.springframework.boot.servlet.autoconfigure.ServletEncodingProperties"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.servlet.autoconfigure.MultipartAutoConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.boot.servlet.autoconfigure.MultipartProperties"
+ ]
+ },
+ {
+ "name": "multipartConfigElement",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.servlet.autoconfigure.MultipartProperties",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.servlet.autoconfigure.ServletEncodingProperties",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.servlet.filter.OrderedCharacterEncodingFilter"
+ },
+ {
+ "type": "org.springframework.boot.servlet.filter.OrderedFilter"
+ },
+ {
+ "type": "org.springframework.boot.servlet.filter.OrderedFormContentFilter"
+ },
+ {
+ "type": "org.springframework.boot.servlet.filter.OrderedRequestContextFilter"
+ },
+ {
+ "type": "org.springframework.boot.sql.autoconfigure.init.ConditionalOnSqlInitialization"
+ },
+ {
+ "type": "org.springframework.boot.sql.autoconfigure.init.OnSqlInitializationCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.ssl.DefaultSslBundleRegistry"
+ },
+ {
+ "type": "org.springframework.boot.ssl.SslBundleRegistry"
+ },
+ {
+ "type": "org.springframework.boot.ssl.SslBundles"
+ },
+ {
+ "type": "org.springframework.boot.support.AnsiOutputApplicationListener",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.support.EnvironmentPostProcessorApplicationListener",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.support.RandomValuePropertySourceEnvironmentPostProcessor",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.boot.logging.DeferredLogFactory"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.support.SpringApplicationJsonEnvironmentPostProcessor",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.support.SystemEnvironmentPropertySourceEnvironmentPostProcessor",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder"
+ },
+ {
+ "type": "org.springframework.boot.thread.Threading"
+ },
+ {
+ "type": "org.springframework.boot.thymeleaf.autoconfigure.TemplateEngineConfigurations$DefaultTemplateEngineConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "templateEngine",
+ "parameterTypes": [
+ "org.springframework.boot.thymeleaf.autoconfigure.ThymeleafProperties",
+ "org.springframework.beans.factory.ObjectProvider",
+ "org.springframework.beans.factory.ObjectProvider"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.thymeleaf.autoconfigure.TemplateEngineConfigurations$ReactiveTemplateEngineConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.thymeleaf.autoconfigure.ThymeleafAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.thymeleaf.autoconfigure.ThymeleafAutoConfiguration$DefaultTemplateResolverConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.boot.thymeleaf.autoconfigure.ThymeleafProperties",
+ "org.springframework.context.ApplicationContext"
+ ]
+ },
+ {
+ "name": "defaultTemplateResolver",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.thymeleaf.autoconfigure.ThymeleafAutoConfiguration$ThymeleafWebMvcConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.thymeleaf.autoconfigure.ThymeleafAutoConfiguration$ThymeleafWebMvcConfiguration$ThymeleafViewResolverConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.thymeleaf.autoconfigure.ThymeleafProperties",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.thymeleaf.autoconfigure.ThymeleafTemplateAvailabilityProvider",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.tomcat.ConfigurableTomcatWebServerFactory"
+ },
+ {
+ "type": "org.springframework.boot.tomcat.TomcatWebServerFactory"
+ },
+ {
+ "type": "org.springframework.boot.tomcat.autoconfigure.TomcatBackgroundPreinitializer",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.tomcat.autoconfigure.TomcatWebServerConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "tomcatWebServerFactoryCustomizer",
+ "parameterTypes": [
+ "org.springframework.core.env.Environment",
+ "org.springframework.boot.web.server.autoconfigure.ServerProperties",
+ "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties",
+ "org.springframework.boot.autoconfigure.web.WebProperties"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.tomcat.autoconfigure.TomcatWebServerConfiguration$TomcatWebSocketConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "webSocketWebServerCustomizer",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.tomcat.autoconfigure.TomcatWebServerFactoryCustomizer"
+ },
+ {
+ "type": "org.springframework.boot.tomcat.autoconfigure.WebSocketTomcatWebServerFactoryCustomizer"
+ },
+ {
+ "type": "org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerAutoConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties"
+ ]
+ },
+ {
+ "name": "tomcatServletWebServerFactory",
+ "parameterTypes": [
+ "org.springframework.beans.factory.ObjectProvider",
+ "org.springframework.beans.factory.ObjectProvider",
+ "org.springframework.beans.factory.ObjectProvider"
+ ]
+ },
+ {
+ "name": "tomcatServletWebServerFactoryCustomizer",
+ "parameterTypes": [
+ "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerFactoryCustomizer"
+ },
+ {
+ "type": "org.springframework.boot.tomcat.reactive.TomcatReactiveWebServerFactory"
+ },
+ {
+ "type": "org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory"
+ },
+ {
+ "type": "org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizationAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizer"
+ },
+ {
+ "type": "org.springframework.boot.transaction.autoconfigure.TransactionProperties"
+ },
+ {
+ "type": "org.springframework.boot.transaction.jta.autoconfigure.JndiJtaConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.transaction.jta.autoconfigure.JtaAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter"
+ },
+ {
+ "type": "org.springframework.boot.web.context.reactive.FilteredReactiveWebContextResourceFilePathResolver",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.web.context.servlet.ServletContextResourceFilePathResolver",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.web.error.ErrorPageRegistrar"
+ },
+ {
+ "type": "org.springframework.boot.web.error.ErrorPageRegistrarBeanPostProcessor",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.web.error.ErrorPageRegistry"
+ },
+ {
+ "type": "org.springframework.boot.web.server.AbstractConfigurableWebServerFactory"
+ },
+ {
+ "type": "org.springframework.boot.web.server.ConfigurableWebServerFactory"
+ },
+ {
+ "type": "org.springframework.boot.web.server.WebServerFactory"
+ },
+ {
+ "type": "org.springframework.boot.web.server.WebServerFactoryCustomizer"
+ },
+ {
+ "type": "org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.web.server.autoconfigure.ServerProperties",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "setPort",
+ "parameterTypes": [
+ "java.lang.Integer"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "servletWebServerFactoryCustomizer",
+ "parameterTypes": [
+ "org.springframework.boot.web.server.autoconfigure.ServerProperties",
+ "org.springframework.beans.factory.ObjectProvider",
+ "org.springframework.beans.factory.ObjectProvider",
+ "org.springframework.beans.factory.ObjectProvider"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerConfiguration$BeanPostProcessorsRegistrar",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerFactoryCustomizer"
+ },
+ {
+ "type": "org.springframework.boot.web.server.context.ServerPortInfoApplicationContextInitializer",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.web.server.reactive.context.ReactiveWebServerApplicationContextFactory",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.web.server.servlet.ConfigurableServletWebServerFactory"
+ },
+ {
+ "type": "org.springframework.boot.web.server.servlet.ServletWebServerFactory"
+ },
+ {
+ "type": "org.springframework.boot.web.server.servlet.WebListenerRegistry"
+ },
+ {
+ "type": "org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContextFactory",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.web.servlet.DynamicRegistrationBean"
+ },
+ {
+ "type": "org.springframework.boot.web.servlet.FilterRegistrationBean"
+ },
+ {
+ "type": "org.springframework.boot.web.servlet.RegistrationBean"
+ },
+ {
+ "type": "org.springframework.boot.web.servlet.ServletContextInitializer"
+ },
+ {
+ "type": "org.springframework.boot.web.servlet.ServletRegistrationBean"
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DefaultDispatcherServletCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DispatcherServletConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "dispatcherServlet",
+ "parameterTypes": [
+ "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DispatcherServletRegistrationCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "dispatcherServletRegistration",
+ "parameterTypes": [
+ "org.springframework.web.servlet.DispatcherServlet",
+ "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties",
+ "org.springframework.beans.factory.ObjectProvider"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath"
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletRegistrationBean"
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.JspTemplateAvailabilityProvider",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "formContentFilter",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration$EnableWebMvcConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter",
+ "methods": [
+ {
+ "name": "requestContextFilter",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "org.springframework.boot.autoconfigure.web.WebProperties"
+ ]
+ },
+ {
+ "name": "errorPageCustomizer",
+ "parameterTypes": [
+ "org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath"
+ ]
+ },
+ {
+ "name": "preserveErrorControllerTargetClassPostProcessor",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$ErrorPageCustomizer"
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$ErrorTemplateMissingCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$PreserveErrorControllerTargetClassPostProcessor"
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorViewResolver"
+ },
+ {
+ "type": "org.springframework.boot.webmvc.error.ErrorAttributes"
+ },
+ {
+ "type": "org.springframework.boot.webmvc.error.ErrorController"
+ },
+ {
+ "type": "org.springframework.boot.websocket.autoconfigure.servlet.WebSocketMessagingAutoConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.websocket.autoconfigure.servlet.WebSocketMessagingAutoConfiguration$JacksonWebSocketMessageConverterConfiguration"
+ },
+ {
+ "type": "org.springframework.boot.websocket.autoconfigure.servlet.WebSocketMessagingAutoConfiguration$NoJacksonOrJackson2Preferred",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.cache.Cache"
+ },
+ {
+ "type": "org.springframework.cache.CacheManager"
+ },
+ {
+ "type": "org.springframework.cache.annotation.AbstractCachingConfiguration",
+ "methods": [
+ {
+ "name": "setConfigurers",
+ "parameterTypes": [
+ "org.springframework.beans.factory.ObjectProvider"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.cache.annotation.AnnotationCacheOperationSource"
+ },
+ {
+ "type": "org.springframework.cache.annotation.Cacheable"
+ },
+ {
+ "type": "org.springframework.cache.annotation.CachingConfigurationSelector",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.cache.annotation.EnableCaching"
+ },
+ {
+ "type": "org.springframework.cache.annotation.ProxyCachingConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "cacheAdvisor",
+ "parameterTypes": [
+ "org.springframework.cache.interceptor.CacheOperationSource",
+ "org.springframework.cache.interceptor.CacheInterceptor"
+ ]
+ },
+ {
+ "name": "cacheInterceptor",
+ "parameterTypes": [
+ "org.springframework.cache.interceptor.CacheOperationSource"
+ ]
+ },
+ {
+ "name": "cacheOperationSource",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.cache.caffeine.CaffeineCacheManager"
+ },
+ {
+ "type": "org.springframework.cache.interceptor.AbstractCacheInvoker"
+ },
+ {
+ "type": "org.springframework.cache.interceptor.AbstractFallbackCacheOperationSource"
+ },
+ {
+ "type": "org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor"
+ },
+ {
+ "type": "org.springframework.cache.interceptor.CacheAspectSupport"
+ },
+ {
+ "type": "org.springframework.cache.interceptor.CacheInterceptor"
+ },
+ {
+ "type": "org.springframework.cache.interceptor.CacheOperationSource"
+ },
+ {
+ "type": "org.springframework.cache.jcache.config.ProxyJCacheConfiguration"
+ },
+ {
+ "type": "org.springframework.cglib.proxy.Dispatcher"
+ },
+ {
+ "type": "org.springframework.cglib.proxy.Factory"
+ },
+ {
+ "type": "org.springframework.cglib.proxy.MethodInterceptor"
+ },
+ {
+ "type": "org.springframework.cglib.proxy.NoOp"
+ },
+ {
+ "type": "org.springframework.context.ApplicationContextAware"
+ },
+ {
+ "type": "org.springframework.context.ApplicationListener"
+ },
+ {
+ "type": "org.springframework.context.ApplicationStartupAware"
+ },
+ {
+ "type": "org.springframework.context.EmbeddedValueResolverAware"
+ },
+ {
+ "type": "org.springframework.context.EnvironmentAware"
+ },
+ {
+ "type": "org.springframework.context.Lifecycle"
+ },
+ {
+ "type": "org.springframework.context.MessageSourceAware"
+ },
+ {
+ "type": "org.springframework.context.Phased"
+ },
+ {
+ "type": "org.springframework.context.ResourceLoaderAware"
+ },
+ {
+ "type": "org.springframework.context.SmartLifecycle"
+ },
+ {
+ "type": "org.springframework.context.annotation.AnnotationScopeMetadataResolver",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.context.annotation.AspectJAutoProxyRegistrar",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.context.annotation.AutoProxyRegistrar",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.context.annotation.Bean"
+ },
+ {
+ "type": "org.springframework.context.annotation.CommonAnnotationBeanPostProcessor",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.context.annotation.ComponentScan"
+ },
+ {
+ "type": "org.springframework.context.annotation.ComponentScan$Filter"
+ },
+ {
+ "type": "org.springframework.context.annotation.Conditional"
+ },
+ {
+ "type": "org.springframework.context.annotation.Configuration"
+ },
+ {
+ "type": "org.springframework.context.annotation.ConfigurationClassEnhancer$EnhancedConfiguration"
+ },
+ {
+ "type": "org.springframework.context.annotation.ConfigurationClassPostProcessor",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "setMetadataReaderFactory",
+ "parameterTypes": [
+ "org.springframework.core.type.classreading.MetadataReaderFactory"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.context.annotation.EnableAspectJAutoProxy"
+ },
+ {
+ "type": "org.springframework.context.annotation.FilterType"
+ },
+ {
+ "type": "org.springframework.context.annotation.Import"
+ },
+ {
+ "type": "org.springframework.context.annotation.ImportAware"
+ },
+ {
+ "type": "org.springframework.context.annotation.ImportRuntimeHints"
+ },
+ {
+ "type": "org.springframework.context.annotation.Lazy"
+ },
+ {
+ "type": "org.springframework.context.annotation.Primary"
+ },
+ {
+ "type": "org.springframework.context.annotation.Profile"
+ },
+ {
+ "type": "org.springframework.context.annotation.ProfileCondition",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.context.annotation.Role"
+ },
+ {
+ "type": "org.springframework.context.annotation.Scope"
+ },
+ {
+ "type": "org.springframework.context.event.AbstractApplicationEventMulticaster"
+ },
+ {
+ "type": "org.springframework.context.event.ApplicationEventMulticaster"
+ },
+ {
+ "type": "org.springframework.context.event.DefaultEventListenerFactory",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.context.event.EventListener"
+ },
+ {
+ "type": "org.springframework.context.event.EventListenerMethodProcessor",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.context.event.SimpleApplicationEventMulticaster"
+ },
+ {
+ "type": "org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
+ },
+ {
+ "type": "org.springframework.core.Ordered"
+ },
+ {
+ "type": "org.springframework.core.PriorityOrdered"
+ },
+ {
+ "type": "org.springframework.core.annotation.AliasFor"
+ },
+ {
+ "type": "org.springframework.core.annotation.AnnotationAttributes[]"
+ },
+ {
+ "type": "org.springframework.core.annotation.MergedAnnotation[]"
+ },
+ {
+ "type": "org.springframework.core.annotation.Order"
+ },
+ {
+ "type": "org.springframework.core.convert.converter.Converter"
+ },
+ {
+ "type": "org.springframework.core.env.Environment"
+ },
+ {
+ "type": "org.springframework.core.env.EnvironmentCapable"
+ },
+ {
+ "type": "org.springframework.core.task.AsyncTaskExecutor"
+ },
+ {
+ "type": "org.springframework.core.task.TaskExecutor"
+ },
+ {
+ "type": "org.springframework.core.type.classreading.CachingMetadataReaderFactory"
+ },
+ {
+ "type": "org.springframework.core.type.classreading.MetadataReaderFactory"
+ },
+ {
+ "type": "org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"
+ },
+ {
+ "type": "org.springframework.data.envers.repository.config.EnableEnversRepositories"
+ },
+ {
+ "type": "org.springframework.data.jpa.repository.JpaRepository"
+ },
+ {
+ "type": "org.springframework.data.redis.connection.RedisConnectionFactory"
+ },
+ {
+ "type": "org.springframework.data.repository.NoRepositoryBean"
+ },
+ {
+ "type": "org.springframework.data.repository.Repository"
+ },
+ {
+ "type": "org.springframework.data.rest.webmvc.alps.AlpsJacksonJsonHttpMessageConverter"
+ },
+ {
+ "type": "org.springframework.data.util.KotlinBeanInfoFactory",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.data.web.PageableHandlerMethodArgumentResolver"
+ },
+ {
+ "type": "org.springframework.data.web.config.EnableSpringDataWebSupport"
+ },
+ {
+ "type": "org.springframework.data.web.config.EnableSpringDataWebSupport$QuerydslActivator",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.data.web.config.EnableSpringDataWebSupport$SpringDataWebConfigurationImportSelector",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.data.web.config.EnableSpringDataWebSupport$SpringDataWebSettingsRegistrar",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.data.web.config.ProjectingArgumentResolverRegistrar",
+ "methods": [
+ {
+ "name": "projectingArgumentResolverBeanPostProcessor",
+ "parameterTypes": [
+ "org.springframework.beans.factory.ObjectFactory"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.data.web.config.ProjectingArgumentResolverRegistrar$ProjectingArgumentResolverBeanPostProcessor"
+ },
+ {
+ "type": "org.springframework.data.web.config.SpringDataJackson3Configuration"
+ },
+ {
+ "type": "org.springframework.data.web.config.SpringDataJackson3Modules"
+ },
+ {
+ "type": "org.springframework.data.web.config.SpringDataWebConfiguration"
+ },
+ {
+ "type": "org.springframework.hateoas.Link"
+ },
+ {
+ "type": "org.springframework.hateoas.server.mvc.TypeConstrainedJacksonJsonHttpMessageConverter"
+ },
+ {
+ "type": "org.springframework.http.ProblemDetail"
+ },
+ {
+ "type": "org.springframework.http.ReactiveHttpInputMessage"
+ },
+ {
+ "type": "org.springframework.http.client.support.HttpAccessor"
+ },
+ {
+ "type": "org.springframework.http.client.support.InterceptingHttpAccessor"
+ },
+ {
+ "type": "org.springframework.http.converter.HttpMessageConverter"
+ },
+ {
+ "type": "org.springframework.http.converter.StringHttpMessageConverter"
+ },
+ {
+ "type": "org.springframework.jdbc.core.JdbcTemplate"
+ },
+ {
+ "type": "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"
+ },
+ {
+ "type": "org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType"
+ },
+ {
+ "type": "org.springframework.jdbc.datasource.init.DatabasePopulator"
+ },
+ {
+ "type": "org.springframework.jmx.export.MBeanExporter"
+ },
+ {
+ "type": "org.springframework.mail.MailSender"
+ },
+ {
+ "type": "org.springframework.mail.javamail.JavaMailSenderImpl"
+ },
+ {
+ "type": "org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration"
+ },
+ {
+ "type": "org.springframework.orm.jpa.AbstractEntityManagerFactoryBean"
+ },
+ {
+ "type": "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
+ },
+ {
+ "type": "org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.retry.annotation.EnableRetry"
+ },
+ {
+ "type": "org.springframework.retry.annotation.RetryConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.retry.annotation.RetryConfiguration$AnnotationClassOrMethodFilter"
+ },
+ {
+ "type": "org.springframework.retry.annotation.RetryConfiguration$AnnotationClassOrMethodPointcut"
+ },
+ {
+ "type": "org.springframework.retry.annotation.RetryConfiguration$AnnotationMethodsResolver"
+ },
+ {
+ "type": "org.springframework.scheduling.SchedulingTaskExecutor"
+ },
+ {
+ "type": "org.springframework.scheduling.TaskScheduler"
+ },
+ {
+ "type": "org.springframework.scheduling.annotation.AbstractAsyncConfiguration",
+ "methods": [
+ {
+ "name": "setConfigurers",
+ "parameterTypes": [
+ "org.springframework.beans.factory.ObjectProvider"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.scheduling.annotation.Async"
+ },
+ {
+ "type": "org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor"
+ },
+ {
+ "type": "org.springframework.scheduling.annotation.AsyncConfigurationSelector",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.scheduling.annotation.AsyncConfigurer"
+ },
+ {
+ "type": "org.springframework.scheduling.annotation.EnableAsync"
+ },
+ {
+ "type": "org.springframework.scheduling.annotation.EnableScheduling"
+ },
+ {
+ "type": "org.springframework.scheduling.annotation.ProxyAsyncConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "asyncAdvisor",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.scheduling.annotation.Scheduled"
+ },
+ {
+ "type": "org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor"
+ },
+ {
+ "type": "org.springframework.scheduling.annotation.Schedules"
+ },
+ {
+ "type": "org.springframework.scheduling.annotation.SchedulingConfiguration",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ },
+ {
+ "name": "scheduledAnnotationProcessor",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.scheduling.concurrent.CustomizableThreadFactory"
+ },
+ {
+ "type": "org.springframework.scheduling.concurrent.ExecutorConfigurationSupport"
+ },
+ {
+ "type": "org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"
+ },
+ {
+ "type": "org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler"
+ },
+ {
+ "type": "org.springframework.scheduling.config.ScheduledTaskHolder"
+ },
+ {
+ "type": "org.springframework.security.web.server.csrf.CsrfToken"
+ },
+ {
+ "type": "org.springframework.stereotype.Component"
+ },
+ {
+ "type": "org.springframework.stereotype.Controller"
+ },
+ {
+ "type": "org.springframework.stereotype.Indexed"
+ },
+ {
+ "type": "org.springframework.stereotype.Repository"
+ },
+ {
+ "type": "org.springframework.stereotype.Service"
+ },
+ {
+ "type": "org.springframework.transaction.PlatformTransactionManager"
+ },
+ {
+ "type": "org.springframework.transaction.ReactiveTransactionManager"
+ },
+ {
+ "type": "org.springframework.transaction.TransactionManager"
+ },
+ {
+ "type": "org.springframework.transaction.annotation.EnableTransactionManagement"
+ },
+ {
+ "type": "org.springframework.transaction.annotation.TransactionManagementConfigurationSelector",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.springframework.transaction.aspectj.AbstractTransactionAspect"
+ },
+ {
+ "type": "org.springframework.transaction.jta.JtaTransactionManager"
+ },
+ {
+ "type": "org.springframework.util.ConcurrentReferenceHashMap$Segment[]"
+ },
+ {
+ "type": "org.springframework.util.CustomizableThreadCreator"
+ },
+ {
+ "type": "org.springframework.web.bind.annotation.GetMapping"
+ },
+ {
+ "type": "org.springframework.web.bind.annotation.Mapping"
+ },
+ {
+ "type": "org.springframework.web.bind.annotation.PostMapping"
+ },
+ {
+ "type": "org.springframework.web.bind.annotation.RequestMapping"
+ },
+ {
+ "type": "org.springframework.web.bind.annotation.RequestMethod[]"
+ },
+ {
+ "type": "org.springframework.web.bind.annotation.ResponseBody"
+ },
+ {
+ "type": "org.springframework.web.bind.annotation.RestController"
+ },
+ {
+ "type": "org.springframework.web.client.RestOperations"
+ },
+ {
+ "type": "org.springframework.web.client.RestTemplate"
+ },
+ {
+ "type": "org.springframework.web.context.ConfigurableWebApplicationContext"
+ },
+ {
+ "type": "org.springframework.web.context.ServletContextAware"
+ },
+ {
+ "type": "org.springframework.web.context.request.RequestContextListener"
+ },
+ {
+ "type": "org.springframework.web.context.support.GenericWebApplicationContext"
+ },
+ {
+ "type": "org.springframework.web.context.support.ServletContextResource"
+ },
+ {
+ "type": "org.springframework.web.filter.CharacterEncodingFilter"
+ },
+ {
+ "type": "org.springframework.web.filter.FormContentFilter"
+ },
+ {
+ "type": "org.springframework.web.filter.GenericFilterBean"
+ },
+ {
+ "type": "org.springframework.web.filter.OncePerRequestFilter"
+ },
+ {
+ "type": "org.springframework.web.filter.RequestContextFilter"
+ },
+ {
+ "type": "org.springframework.web.multipart.MultipartResolver"
+ },
+ {
+ "type": "org.springframework.web.multipart.support.StandardServletMultipartResolver"
+ },
+ {
+ "type": "org.springframework.web.reactive.DispatcherHandler"
+ },
+ {
+ "type": "org.springframework.web.reactive.HandlerResult"
+ },
+ {
+ "type": "org.springframework.web.reactive.result.view.View"
+ },
+ {
+ "type": "org.springframework.web.servlet.DispatcherServlet"
+ },
+ {
+ "type": "org.springframework.web.servlet.FrameworkServlet"
+ },
+ {
+ "type": "org.springframework.web.servlet.HttpServletBean"
+ },
+ {
+ "type": "org.springframework.web.servlet.View"
+ },
+ {
+ "type": "org.springframework.web.servlet.ViewResolver"
+ },
+ {
+ "type": "org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration"
+ },
+ {
+ "type": "org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport"
+ },
+ {
+ "type": "org.springframework.web.servlet.config.annotation.WebMvcConfigurer"
+ },
+ {
+ "type": "org.springframework.web.servlet.view.AbstractCachingViewResolver"
+ },
+ {
+ "type": "org.springframework.web.servlet.view.ContentNegotiatingViewResolver"
+ },
+ {
+ "type": "org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration"
+ },
+ {
+ "type": "org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer"
+ },
+ {
+ "type": "org.thymeleaf.ITemplateEngine"
+ },
+ {
+ "type": "org.thymeleaf.TemplateEngine"
+ },
+ {
+ "type": "org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect"
+ },
+ {
+ "type": "org.thymeleaf.spring6.ISpringTemplateEngine"
+ },
+ {
+ "type": "org.thymeleaf.spring6.SpringTemplateEngine"
+ },
+ {
+ "type": "org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver"
+ },
+ {
+ "type": "org.thymeleaf.templatemode.TemplateMode"
+ },
+ {
+ "type": "org.thymeleaf.templateresolver.AbstractConfigurableTemplateResolver"
+ },
+ {
+ "type": "org.thymeleaf.templateresolver.AbstractTemplateResolver"
+ },
+ {
+ "type": "org.thymeleaf.templateresolver.ITemplateResolver"
+ },
+ {
+ "type": "org.webjars.WebJarAssetLocator"
+ },
+ {
+ "type": "org.webjars.WebJarVersionLocator"
+ },
+ {
+ "type": "reactor.core.publisher.Flux"
+ },
+ {
+ "type": "scala.Option"
+ },
+ {
+ "type": "sun.awt.Win32GraphicsEnvironment",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "dwmCompositionChanged",
+ "parameterTypes": [
+ "boolean"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.awt.image.SunVolatileImage",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "volSurfaceManager"
+ }
+ ]
+ },
+ {
+ "type": "sun.awt.image.VolatileSurfaceManager",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "sdCurrent"
+ }
+ ]
+ },
+ {
+ "type": "sun.awt.windows.WDesktopPeer",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "systemSleepCallback",
+ "parameterTypes": [
+ "boolean"
+ ]
+ },
+ {
+ "name": "userSessionCallback",
+ "parameterTypes": [
+ "boolean",
+ "java.awt.desktop.UserSessionEvent$Reason"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.awt.windows.WToolkit",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "displayChanged",
+ "parameterTypes": []
+ },
+ {
+ "name": "windowsSettingChange",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "sun.font.CharToGlyphMapper",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "charToGlyph",
+ "parameterTypes": [
+ "int"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.font.Font2D",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "canDisplay",
+ "parameterTypes": [
+ "char"
+ ]
+ },
+ {
+ "name": "charToGlyphRaw",
+ "parameterTypes": [
+ "int"
+ ]
+ },
+ {
+ "name": "charToVariationGlyphRaw",
+ "parameterTypes": [
+ "int",
+ "int"
+ ]
+ },
+ {
+ "name": "getMapper",
+ "parameterTypes": []
+ },
+ {
+ "name": "getTableBytes",
+ "parameterTypes": [
+ "int"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.font.FontStrike",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "getGlyphMetrics",
+ "parameterTypes": [
+ "int"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.font.GlyphList",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "gposx"
+ },
+ {
+ "name": "gposy"
+ },
+ {
+ "name": "images"
+ },
+ {
+ "name": "lcdRGBOrder"
+ },
+ {
+ "name": "lcdSubPixPos"
+ },
+ {
+ "name": "len"
+ },
+ {
+ "name": "positions"
+ },
+ {
+ "name": "usePositions"
+ }
+ ]
+ },
+ {
+ "type": "sun.font.PhysicalStrike",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "pScalerContext"
+ }
+ ],
+ "methods": [
+ {
+ "name": "adjustPoint",
+ "parameterTypes": [
+ "java.awt.geom.Point2D$Float"
+ ]
+ },
+ {
+ "name": "getGlyphPoint",
+ "parameterTypes": [
+ "int",
+ "int"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.font.StrikeMetrics",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "float",
+ "float",
+ "float",
+ "float",
+ "float",
+ "float",
+ "float",
+ "float",
+ "float",
+ "float"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.font.TrueTypeFont",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "readBlock",
+ "parameterTypes": [
+ "java.nio.ByteBuffer",
+ "int",
+ "int"
+ ]
+ },
+ {
+ "name": "readBytes",
+ "parameterTypes": [
+ "int",
+ "int"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.font.Type1Font",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "readFile",
+ "parameterTypes": [
+ "java.nio.ByteBuffer"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.java2d.Disposer",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "addRecord",
+ "parameterTypes": [
+ "java.lang.Object",
+ "long",
+ "long"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.java2d.InvalidPipeException",
+ "jniAccessible": true
+ },
+ {
+ "type": "sun.java2d.NullSurfaceData",
+ "jniAccessible": true
+ },
+ {
+ "type": "sun.java2d.SurfaceData",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "pData"
+ },
+ {
+ "name": "valid"
+ }
+ ]
+ },
+ {
+ "type": "sun.java2d.marlin.DMarlinRenderingEngine",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "sun.java2d.pipe.Region",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "bands"
+ },
+ {
+ "name": "endIndex"
+ },
+ {
+ "name": "hix"
+ },
+ {
+ "name": "hiy"
+ },
+ {
+ "name": "lox"
+ },
+ {
+ "name": "loy"
+ }
+ ]
+ },
+ {
+ "type": "sun.java2d.pipe.RegionIterator",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "curIndex"
+ },
+ {
+ "name": "numXbands"
+ },
+ {
+ "name": "region"
+ }
+ ]
+ },
+ {
+ "type": "sun.java2d.windows.WindowsFlags",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "d3dEnabled"
+ },
+ {
+ "name": "d3dSet"
+ },
+ {
+ "name": "setHighDPIAware"
+ }
+ ]
+ },
+ {
+ "type": "sun.launcher.LauncherHelper",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "isStaticMain"
+ },
+ {
+ "name": "noArgMain"
+ }
+ ],
+ "methods": [
+ {
+ "name": "getApplicationClass",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "sun.net.www.protocol.jar.Handler",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "sun.reflect.ReflectionFactory",
+ "methods": [
+ {
+ "name": "getReflectionFactory",
+ "parameterTypes": []
+ },
+ {
+ "name": "newConstructorForSerialization",
+ "parameterTypes": [
+ "java.lang.Class",
+ "java.lang.reflect.Constructor"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.security.provider.DRBG",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ "java.security.SecureRandomParameters"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.security.provider.SHA",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "sun.security.provider.SHA2$SHA256",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "sun.text.resources.cldr.FormatData"
+ },
+ {
+ "type": "sun.text.resources.cldr.FormatData_en"
+ },
+ {
+ "type": "sun.text.resources.cldr.FormatData_en_US"
+ },
+ {
+ "type": "sun.util.resources.cldr.CalendarData"
+ },
+ {
+ "type": "sun.util.resources.cldr.TimeZoneNames"
+ },
+ {
+ "type": "sun.util.resources.cldr.TimeZoneNames_en"
+ },
+ {
+ "type": "sun.util.resources.cldr.TimeZoneNames_en_US"
+ },
+ {
+ "type": "tools.jackson.databind.ObjectMapper"
+ },
+ {
+ "type": "tools.jackson.databind.deser.Deserializers[]"
+ },
+ {
+ "type": "tools.jackson.databind.deser.KeyDeserializers[]"
+ },
+ {
+ "type": "tools.jackson.databind.deser.ValueInstantiators[]"
+ },
+ {
+ "type": "tools.jackson.databind.json.JsonMapper"
+ },
+ {
+ "type": "tools.jackson.databind.ser.Serializers[]"
+ },
+ {
+ "type": "tools.jackson.dataformat.cbor.CBORMapper"
+ },
+ {
+ "type": "tools.jackson.dataformat.smile.SmileMapper"
+ },
+ {
+ "type": "tools.jackson.dataformat.xml.XmlMapper"
+ },
+ {
+ "type": "tools.jackson.dataformat.yaml.YAMLMapper"
+ },
+ {
+ "type": {
+ "proxy": [
+ "java.lang.reflect.ParameterizedType",
+ "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy",
+ "java.io.Serializable"
+ ]
+ }
+ },
+ {
+ "type": {
+ "proxy": [
+ "java.lang.reflect.TypeVariable",
+ "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy",
+ "java.io.Serializable"
+ ]
+ }
+ },
+ {
+ "type": {
+ "proxy": [
+ "org.springframework.beans.factory.annotation.Qualifier"
+ ]
+ }
+ },
+ {
+ "type": {
+ "proxy": [
+ "org.springframework.boot.context.properties.ConfigurationProperties"
+ ]
+ }
+ },
+ {
+ "type": {
+ "proxy": [
+ "org.springframework.cache.annotation.Cacheable"
+ ]
+ }
+ },
+ {
+ "type": {
+ "lambda": {
+ "declaringClass": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$BootstrapExecutorConfiguration",
+ "interfaces": [
+ "org.springframework.beans.factory.config.BeanFactoryPostProcessor"
+ ]
+ }
+ }
+ }
+ ],
+ "resources": [
+ {
+ "glob": "META-INF/build-info.properties"
+ },
+ {
+ "glob": "META-INF/services/ch.qos.logback.classic.spi.Configurator"
+ },
+ {
+ "glob": "META-INF/services/java.net.spi.URLStreamHandlerProvider"
+ },
+ {
+ "glob": "META-INF/services/java.time.zone.ZoneRulesProvider"
+ },
+ {
+ "glob": "META-INF/services/java.util.spi.ResourceBundleControlProvider"
+ },
+ {
+ "glob": "META-INF/services/javax.xml.transform.TransformerFactory"
+ },
+ {
+ "glob": "META-INF/services/kotlin.reflect.jvm.internal.impl.resolve.ExternalOverridabilityCondition"
+ },
+ {
+ "glob": "META-INF/services/kotlin.reflect.jvm.internal.impl.util.ModuleVisibilityHelper"
+ },
+ {
+ "glob": "META-INF/services/org.apache.commons.logging.LogFactory"
+ },
+ {
+ "glob": "META-INF/services/org.apache.juli.logging.Log"
+ },
+ {
+ "glob": "META-INF/services/org.apache.logging.log4j.util.PropertySource"
+ },
+ {
+ "glob": "META-INF/services/org.slf4j.spi.SLF4JServiceProvider"
+ },
+ {
+ "glob": "META-INF/services/tools.jackson.databind.JacksonModule"
+ },
+ {
+ "glob": "META-INF/spring-autoconfigure-metadata.properties"
+ },
+ {
+ "glob": "META-INF/spring.components"
+ },
+ {
+ "glob": "META-INF/spring.factories"
+ },
+ {
+ "glob": "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"
+ },
+ {
+ "glob": "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements"
+ },
+ {
+ "glob": "application-default.properties"
+ },
+ {
+ "glob": "application-default.xml"
+ },
+ {
+ "glob": "application-default.yaml"
+ },
+ {
+ "glob": "application-default.yml"
+ },
+ {
+ "glob": "application.properties"
+ },
+ {
+ "glob": "application.xml"
+ },
+ {
+ "glob": "application.yaml"
+ },
+ {
+ "glob": "application.yml"
+ },
+ {
+ "glob": "banner.txt"
+ },
+ {
+ "glob": "ch/qos/logback/core/Appender.class"
+ },
+ {
+ "glob": "ch/qos/logback/core/AppenderBase.class"
+ },
+ {
+ "glob": "ch/qos/logback/core/spi/ContextAware.class"
+ },
+ {
+ "glob": "ch/qos/logback/core/spi/ContextAwareBase.class"
+ },
+ {
+ "glob": "ch/qos/logback/core/spi/FilterAttachable.class"
+ },
+ {
+ "glob": "ch/qos/logback/core/spi/LifeCycle.class"
+ },
+ {
+ "glob": "com/starlwr/bot"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/config/StarBotBilibiliCacheConfig.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/config/StarBotBilibiliCacheKeyConfig.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/config/StarBotBilibiliLogConfig.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/config/StarBotBilibiliProperties$BilibiliThread.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/config/StarBotBilibiliProperties$Debug.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/config/StarBotBilibiliProperties$Dynamic.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/config/StarBotBilibiliProperties$Live.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/config/StarBotBilibiliProperties$Network.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/config/StarBotBilibiliProperties.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/config/StarBotBilibiliThreadPoolConfig$BilibiliWithLogCallerRunsPolicy.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/config/StarBotBilibiliThreadPoolConfig.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/config/StarBotBilibiliThymeleafConfig.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/controller/BilibiliLoginController.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/factory/BilibiliDynamicPainterFactory.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/factory/BilibiliLiveRoomConnectorFactory.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/handler/BilibiliDynamicPushHandler.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/handler/BilibiliLiveOffPushHandler.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/handler/BilibiliLiveOnPushHandler.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/listener/BilibiliDataSourceEventListener.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/listener/BilibiliLiveDataListener.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/BlindBoxCommandController$Companion.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/BlindBoxCommandController$Range.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/BlindBoxCommandController.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/BlindBoxLiveOffReportHandler.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/BlindBoxLiveOnResetHandler.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/BlindBoxRecordHandler.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/LegacyReportMigrator.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/LiveReportBaselineCollector.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/LiveReportCollector.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/LiveReportDataDriver.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/LiveReportDemandService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/LiveReportPainter$Companion.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/LiveReportPainter$WhenMappings.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/LiveReportPainter.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/LiveReportPushHandler.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/report/LiveReportStorageConfig.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/service/BilibiliAccountService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/service/BilibiliBackupLivePushService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/service/BilibiliDataSourceService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/service/BilibiliDynamicService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/service/BilibiliEventParser.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/service/BilibiliGiftService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/service/BilibiliLiveRoomConnectTaskService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/service/BilibiliLiveRoomService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/bilibili/util/BilibiliApiUtil.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/DataSourceConfig.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/EventConfig.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/RestTemplateConfig.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/StarBotCoreProperties$DataSource.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/StarBotCoreProperties$Live.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/StarBotCoreProperties$Log.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/StarBotCoreProperties$Mail.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/StarBotCoreProperties$Network.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/StarBotCoreProperties$NetworkThread.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/StarBotCoreProperties$Paint.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/StarBotCoreProperties$Plugin.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/StarBotCoreProperties.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/StarBotCoreThreadPoolConfig$NetworkWithLogCallerRunsPolicy.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/config/StarBotCoreThreadPoolConfig.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/converter/ColorConverter.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/converter/FontConverter.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/datasource/AbstractDataSource.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/datasource/DataSourceServiceRegistry.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/StarBotBaseEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/StarBotExternalBaseEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/StarBotInternalBaseEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/datasource/StarBotBaseDataSourceEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/datasource/base/StarBotDataSourceChangeEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/dynamic/StarBotBaseDynamicEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/StarBotBaseLiveEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/base/StarBotLiveConnectionEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/base/StarBotLiveGiftEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/base/StarBotLiveInfoUpdateEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/base/StarBotLiveInteractionEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/base/StarBotLiveMessageEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/base/StarBotLiveOperationEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/base/StarBotLivePurchaseEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/base/StarBotLiveStatusChangeEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/ConnectedEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/DanmuEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/DisconnectedEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/EmojiEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/EnterRoomEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/FollowEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/FreeGiftEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/LikeEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/LikeUpdateEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/LiveOffEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/LiveOnEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/MembershipEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/PaidGiftEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/RandomGiftEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/ShareEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/event/live/common/SuperChatEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/factory/StarBotCommonPainterFactory.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/handler/DefaultHandlerForEvent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/handler/StarBotEventHandler.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/listener/LoadDataSourceListener.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/listener/StarBotDefaultDanmuEventListener.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/listener/StarBotDefaultLiveOffEventListener.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/listener/StarBotDefaultLiveOnEventListener.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/listener/StarBotHandlerListener.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/model/EmojiInfo.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/model/LiveStreamerInfo.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/model/UserInfo.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/plugin/Dependency.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/plugin/StarBotComponent.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/plugin/StarBotPluginDependencyDownloader.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/sender/StarBotMessageSender.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/service/DataSourceService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/service/DataSourceServiceConfig.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/service/DefaultLiveDataService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/service/LiveDataService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/service/StarBotEventHandlerService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/service/StarBotMailService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/service/StarBotSenderService.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/util/FontUtil.class"
+ },
+ {
+ "glob": "com/starlwr/bot/core/util/HttpUtil.class"
+ },
+ {
+ "glob": "commons-logging.properties"
+ },
+ {
+ "glob": "config/application-default.properties"
+ },
+ {
+ "glob": "config/application-default.xml"
+ },
+ {
+ "glob": "config/application-default.yaml"
+ },
+ {
+ "glob": "config/application-default.yml"
+ },
+ {
+ "glob": "config/application.properties"
+ },
+ {
+ "glob": "config/application.xml"
+ },
+ {
+ "glob": "config/application.yaml"
+ },
+ {
+ "glob": "config/application.yml"
+ },
+ {
+ "glob": "fonts/font.ttf"
+ },
+ {
+ "glob": "git.properties"
+ },
+ {
+ "glob": "jakarta/servlet/http/LocalStrings.properties"
+ },
+ {
+ "glob": "jakarta/servlet/http/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "jakarta/servlet/http/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "jakarta/servlet/http/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "jakarta/servlet/http/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "jndi.properties"
+ },
+ {
+ "glob": "kotlin/Function.class"
+ },
+ {
+ "glob": "kotlin/Metadata.class"
+ },
+ {
+ "glob": "kotlin/annotation/MustBeDocumented.class"
+ },
+ {
+ "glob": "kotlin/annotation/Retention.class"
+ },
+ {
+ "glob": "kotlin/annotation/Target.class"
+ },
+ {
+ "glob": "kotlin/coroutines/Continuation.class"
+ },
+ {
+ "glob": "kotlin/coroutines/jvm/internal/BaseContinuationImpl.class"
+ },
+ {
+ "glob": "kotlin/coroutines/jvm/internal/CoroutineStackFrame.class"
+ },
+ {
+ "glob": "kotlin/coroutines/jvm/internal/RestrictedContinuationImpl.class"
+ },
+ {
+ "glob": "kotlin/coroutines/jvm/internal/RestrictedSuspendLambda.class"
+ },
+ {
+ "glob": "kotlin/coroutines/jvm/internal/SuspendFunction.class"
+ },
+ {
+ "glob": "kotlin/jvm/functions/Function1.class"
+ },
+ {
+ "glob": "kotlin/jvm/functions/Function2.class"
+ },
+ {
+ "glob": "kotlin/jvm/internal/CallableReference.class"
+ },
+ {
+ "glob": "kotlin/jvm/internal/FunctionBase.class"
+ },
+ {
+ "glob": "kotlin/jvm/internal/FunctionReference.class"
+ },
+ {
+ "glob": "kotlin/jvm/internal/FunctionReferenceImpl.class"
+ },
+ {
+ "glob": "kotlin/reflect/KAnnotatedElement.class"
+ },
+ {
+ "glob": "kotlin/reflect/KCallable.class"
+ },
+ {
+ "glob": "kotlin/reflect/KFunction.class"
+ },
+ {
+ "glob": "log4j2.StatusLogger.properties"
+ },
+ {
+ "glob": "log4j2.component.properties"
+ },
+ {
+ "glob": "log4j2.system.properties"
+ },
+ {
+ "glob": "logback-spring.groovy"
+ },
+ {
+ "glob": "logback-spring.xml"
+ },
+ {
+ "glob": "logback-test-spring.groovy"
+ },
+ {
+ "glob": "logback-test-spring.xml"
+ },
+ {
+ "glob": "logback-test.groovy"
+ },
+ {
+ "glob": "logback-test.xml"
+ },
+ {
+ "glob": "logback.groovy"
+ },
+ {
+ "glob": "logback.xml"
+ },
+ {
+ "glob": "messages.properties"
+ },
+ {
+ "glob": "org/apache/catalina/authenticator/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/catalina/authenticator/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/catalina/authenticator/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/authenticator/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/catalina/authenticator/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/connector/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/catalina/connector/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/catalina/connector/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/connector/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/catalina/connector/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/core/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/catalina/core/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/catalina/core/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/core/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/catalina/core/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/core/RestrictedFilters.properties"
+ },
+ {
+ "glob": "org/apache/catalina/core/RestrictedListeners.properties"
+ },
+ {
+ "glob": "org/apache/catalina/core/RestrictedServlets.properties"
+ },
+ {
+ "glob": "org/apache/catalina/deploy/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/catalina/deploy/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/catalina/deploy/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/deploy/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/catalina/deploy/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/loader/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/catalina/loader/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/catalina/loader/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/loader/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/catalina/loader/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/mapper/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/catalina/mapper/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/catalina/mapper/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/mapper/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/catalina/mapper/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/mbeans/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/catalina/mbeans/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/catalina/mbeans/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/mbeans/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/catalina/mbeans/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/realm/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/catalina/realm/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/catalina/realm/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/realm/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/catalina/realm/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/session/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/catalina/session/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/catalina/session/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/session/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/catalina/session/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/startup/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/catalina/startup/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/catalina/startup/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/startup/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/catalina/startup/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/util/CharsetMapperDefault.properties"
+ },
+ {
+ "glob": "org/apache/catalina/util/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/catalina/util/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/catalina/util/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/util/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/catalina/util/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/util/ServerInfo.properties"
+ },
+ {
+ "glob": "org/apache/catalina/valves/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/catalina/valves/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/catalina/valves/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/valves/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/catalina/valves/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/webresources/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/catalina/webresources/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/catalina/webresources/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/catalina/webresources/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/catalina/webresources/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/coyote/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/coyote/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/coyote/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/coyote/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/coyote/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/coyote/http11/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/coyote/http11/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/coyote/http11/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/coyote/http11/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/coyote/http11/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/naming/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/naming/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/naming/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/naming/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/naming/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/buf/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/buf/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/buf/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/buf/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/buf/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/compat/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/compat/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/compat/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/compat/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/compat/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/http/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/http/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/http/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/http/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/http/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/http/parser/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/http/parser/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/http/parser/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/http/parser/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/http/parser/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/modeler/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/modeler/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/modeler/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/modeler/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/modeler/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/net/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/net/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/net/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/net/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/net/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/scan/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/scan/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/scan/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/scan/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/scan/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/threads/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/threads/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/threads/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/threads/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/util/threads/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/websocket/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/websocket/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/websocket/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/websocket/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/websocket/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/websocket/server/LocalStrings.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/websocket/server/LocalStrings_zh.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/websocket/server/LocalStrings_zh_CN.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/websocket/server/LocalStrings_zh_Hans.properties"
+ },
+ {
+ "glob": "org/apache/tomcat/websocket/server/LocalStrings_zh_Hans_CN.properties"
+ },
+ {
+ "glob": "org/springframework/aot/hint/RuntimeHintsRegistrar.class"
+ },
+ {
+ "glob": "org/springframework/aot/hint/annotation/Reflective.class"
+ },
+ {
+ "glob": "org/springframework/beans/factory/Aware.class"
+ },
+ {
+ "glob": "org/springframework/beans/factory/BeanClassLoaderAware.class"
+ },
+ {
+ "glob": "org/springframework/beans/factory/BeanFactoryAware.class"
+ },
+ {
+ "glob": "org/springframework/beans/factory/annotation/Qualifier.class"
+ },
+ {
+ "glob": "org/springframework/beans/factory/config/BeanFactoryPostProcessor.class"
+ },
+ {
+ "glob": "org/springframework/boot/ApplicationRunner.class"
+ },
+ {
+ "glob": "org/springframework/boot/Runner.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/AbstractDependsOnBeanFactoryPostProcessor.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/AutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/AutoConfigurationPackage.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/AutoConfigurationPackages$Registrar.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/AutoConfigureAfter.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/AutoConfigureBefore.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/AutoConfigureOrder.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/EnableAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/admin/SpringApplicationAdminJmxAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration$AspectJAutoProxyingConfiguration$CglibAutoProxyConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration$AspectJAutoProxyingConfiguration$JdkDynamicAutoProxyConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration$AspectJAutoProxyingConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration$ClassProxyingConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnBean.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnBooleanProperty.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnClass.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnMissingFilterBean.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnNotWarDeployment.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnProperty.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidate.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnWebApplication.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/context/ConfigurationPropertiesAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/context/LifecycleAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/context/MessageSourceAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/context/PropertyPlaceholderAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/data/AbstractRepositoryConfigurationSourceSupport.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration$GitResourceAvailableCondition.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$AsyncConfigurerConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$AsyncConfigurerWrapperConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$BootstrapExecutorConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$OnExecutorCondition$ExecutorBeanCondition.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$OnExecutorCondition$ModelCondition.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$OnExecutorCondition.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$SimpleAsyncTaskExecutorBuilderConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$TaskExecutorConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$ThreadPoolTaskExecutorBuilderConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations$SimpleAsyncTaskSchedulerBuilderConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations$TaskSchedulerConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations$ThreadPoolTaskSchedulerBuilderConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/cache/autoconfigure/CacheAutoConfiguration$CacheConfigurationImportSelector.class"
+ },
+ {
+ "glob": "org/springframework/boot/cache/autoconfigure/CacheAutoConfiguration$CacheManagerEntityManagerFactoryDependsOnConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/cache/autoconfigure/CacheAutoConfiguration$CacheManagerEntityManagerFactoryDependsOnPostProcessor.class"
+ },
+ {
+ "glob": "org/springframework/boot/cache/autoconfigure/CacheAutoConfiguration$CacheManagerValidator.class"
+ },
+ {
+ "glob": "org/springframework/boot/cache/autoconfigure/CacheAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/cache/autoconfigure/CaffeineCacheConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/cache/autoconfigure/GenericCacheConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/cache/autoconfigure/NoOpCacheConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/cache/autoconfigure/SimpleCacheConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/context/annotation/DeterminableImports.class"
+ },
+ {
+ "glob": "org/springframework/boot/context/properties/ConfigurationProperties.class"
+ },
+ {
+ "glob": "org/springframework/boot/context/properties/ConfigurationPropertiesBinding.class"
+ },
+ {
+ "glob": "org/springframework/boot/context/properties/EnableConfigurationProperties.class"
+ },
+ {
+ "glob": "org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrar.class"
+ },
+ {
+ "glob": "org/springframework/boot/data/autoconfigure/web/DataWebAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/data/couchbase/autoconfigure$DataCouchbaseAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/data/couchbase/autoconfigure/DataCouchbaseAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/data/jpa/autoconfigure/DataJpaRepositoriesAutoConfiguration$BootstrapExecutorCondition.class"
+ },
+ {
+ "glob": "org/springframework/boot/data/jpa/autoconfigure/DataJpaRepositoriesAutoConfiguration$JpaRepositoriesImportSelector.class"
+ },
+ {
+ "glob": "org/springframework/boot/data/jpa/autoconfigure/DataJpaRepositoriesAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/data/jpa/autoconfigure/DataJpaRepositoriesRegistrar.class"
+ },
+ {
+ "glob": "org/springframework/boot/data/redis/autoconfigure$DataRedisAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/data/redis/autoconfigure/DataRedisAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/gson/autoconfigure$GsonAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/gson/autoconfigure/GsonAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/hazelcast/autoconfigure$HazelcastAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/hazelcast/autoconfigure/HazelcastAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/hibernate/autoconfigure/HibernateJpaConfiguration$HibernateRuntimeHints.class"
+ },
+ {
+ "glob": "org/springframework/boot/hibernate/autoconfigure/HibernateJpaConfiguration$NamingStrategiesHibernatePropertiesCustomizer.class"
+ },
+ {
+ "glob": "org/springframework/boot/hibernate/autoconfigure/HibernateJpaConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$NotReactiveWebApplicationCondition$ReactiveWebApplication.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$NotReactiveWebApplicationCondition.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$MappingJackson2XmlHttpMessageConverterConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$PreferJackson2OrJacksonUnavailableCondition.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConverterConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonXmlHttpMessageConverterConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/http/converter/autoconfigure/KotlinSerializationHttpMessageConvertersConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$AbstractMapperBuilderCustomizer.class"
+ },
+ {
+ "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$CborConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonAutoConfigurationRuntimeHints.class"
+ },
+ {
+ "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonJsonMapperBuilderCustomizerConfiguration$StandardJsonMapperBuilderCustomizer.class"
+ },
+ {
+ "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonJsonMapperBuilderCustomizerConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonMixinConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JsonProblemDetailsConfiguration$ProblemDetailJsonMapperBuilderCustomizer.class"
+ },
+ {
+ "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JsonProblemDetailsConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$XmlConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jackson2/autoconfigure$Jackson2AutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jackson2/autoconfigure/Jackson2AutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceInitializationAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceTransactionManagerAutoConfiguration$JdbcTransactionManagerConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceTransactionManagerAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jdbc/autoconfigure/JdbcClientAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jdbc/autoconfigure/JdbcTemplateAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jdbc/autoconfigure/JdbcTemplateConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jdbc/autoconfigure/JndiDataSourceAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jdbc/autoconfigure/NamedParameterJdbcTemplateConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jdbc/autoconfigure/XADataSourceAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jpa/autoconfigure/EntityManagerFactoryDependsOnPostProcessor.class"
+ },
+ {
+ "glob": "org/springframework/boot/jpa/autoconfigure/JpaBaseConfiguration$JpaWebConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jpa/autoconfigure/JpaBaseConfiguration$PersistenceManagedTypesConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jpa/autoconfigure/JpaBaseConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jsonb/autoconfigure$JsonbAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/jsonb/autoconfigure/JsonbAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/kotlinx/serialization/json/autoconfigure$KotlinxSerializationJsonAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/mail/autoconfigure/MailSenderAutoConfiguration$MailSenderCondition$HostProperty.class"
+ },
+ {
+ "glob": "org/springframework/boot/mail/autoconfigure/MailSenderAutoConfiguration$MailSenderCondition$JndiNameProperty.class"
+ },
+ {
+ "glob": "org/springframework/boot/mail/autoconfigure/MailSenderAutoConfiguration$MailSenderCondition.class"
+ },
+ {
+ "glob": "org/springframework/boot/mail/autoconfigure/MailSenderAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/mail/autoconfigure/MailSenderValidatorAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/micrometer/metrics/autoconfigure$CompositeMeterRegistryAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/micrometer/metrics/autoconfigure$MetricsAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/micrometer/metrics/autoconfigure/CompositeMeterRegistryAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/micrometer/metrics/autoconfigure/MetricsAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/micrometer/metrics/autoconfigure/export/simple$SimpleMetricsExportAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/micrometer/metrics/autoconfigure/export/simple/SimpleMetricsExportAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/micrometer/observation/autoconfigure$ObservationAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/micrometer/observation/autoconfigure/ObservationAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/persistence/autoconfigure/PersistenceExceptionTranslationAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/servlet/autoconfigure/HttpEncodingAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/servlet/autoconfigure/MultipartAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/sql/autoconfigure/init/ConditionalOnSqlInitialization.class"
+ },
+ {
+ "glob": "org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurer.class"
+ },
+ {
+ "glob": "org/springframework/boot/thymeleaf/autoconfigure/TemplateEngineConfigurations$DefaultTemplateEngineConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/thymeleaf/autoconfigure/TemplateEngineConfigurations$ReactiveTemplateEngineConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/thymeleaf/autoconfigure/ThymeleafAutoConfiguration$DataAttributeDialectConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/thymeleaf/autoconfigure/ThymeleafAutoConfiguration$DefaultTemplateResolverConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/thymeleaf/autoconfigure/ThymeleafAutoConfiguration$ThymeleafSecurityDialectConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/thymeleaf/autoconfigure/ThymeleafAutoConfiguration$ThymeleafWebFluxConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/thymeleaf/autoconfigure/ThymeleafAutoConfiguration$ThymeleafWebLayoutConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/thymeleaf/autoconfigure/ThymeleafAutoConfiguration$ThymeleafWebMvcConfiguration$ThymeleafViewResolverConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/thymeleaf/autoconfigure/ThymeleafAutoConfiguration$ThymeleafWebMvcConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/thymeleaf/autoconfigure/ThymeleafAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/tomcat/autoconfigure/TomcatWebServerConfiguration$TomcatWebSocketConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/tomcat/autoconfigure/TomcatWebServerConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$AspectJTransactionManagementConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$EnableTransactionManagementConfiguration$CglibAutoProxyConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$EnableTransactionManagementConfiguration$JdkDynamicAutoProxyConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$EnableTransactionManagementConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$TransactionTemplateConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/transaction/autoconfigure/TransactionManagerCustomizationAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/transaction/jta/autoconfigure/JndiJtaConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/transaction/jta/autoconfigure/JtaAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/validation/autoconfigure$ValidationAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/validation/autoconfigure/ValidationAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/web/server/autoconfigure/servlet/ServletWebServerConfiguration$BeanPostProcessorsRegistrar.class"
+ },
+ {
+ "glob": "org/springframework/boot/web/server/autoconfigure/servlet/ServletWebServerConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webflux/autoconfigure$WebFluxAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DefaultDispatcherServletCondition.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DispatcherServletConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DispatcherServletRegistrationCondition.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ProblemDetailsErrorHandlingConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ResourceChainCustomizerConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ResourceChainResourceHandlerRegistrationCustomizer.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ResourceHandlerRegistrationCustomizer.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$WelcomePageHandlerMappingFactory.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcObservationAutoConfiguration$MeterFilterConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcObservationAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$ErrorPageCustomizer.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$ErrorTemplateMissingCondition.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$PreserveErrorControllerTargetClassPostProcessor.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$StaticView.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/websocket/autoconfigure/servlet/WebSocketMessagingAutoConfiguration$Jackson2WebSocketMessageConverterConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/websocket/autoconfigure/servlet/WebSocketMessagingAutoConfiguration$JacksonWebSocketMessageConverterConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/websocket/autoconfigure/servlet/WebSocketMessagingAutoConfiguration$NoJacksonOrJackson2Preferred.class"
+ },
+ {
+ "glob": "org/springframework/boot/websocket/autoconfigure/servlet/WebSocketMessagingAutoConfiguration$SpringBootWebSocketMessageBrokerConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/boot/websocket/autoconfigure/servlet/WebSocketMessagingAutoConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/cache/annotation/AbstractCachingConfiguration$CachingConfigurerSupplier.class"
+ },
+ {
+ "glob": "org/springframework/cache/annotation/AbstractCachingConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/cache/annotation/ProxyCachingConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/context/ApplicationContextAware.class"
+ },
+ {
+ "glob": "org/springframework/context/ApplicationContextInitializer.class"
+ },
+ {
+ "glob": "org/springframework/context/ApplicationEvent.class"
+ },
+ {
+ "glob": "org/springframework/context/EnvironmentAware.class"
+ },
+ {
+ "glob": "org/springframework/context/ResourceLoaderAware.class"
+ },
+ {
+ "glob": "org/springframework/context/annotation/AdviceModeImportSelector.class"
+ },
+ {
+ "glob": "org/springframework/context/annotation/AspectJAutoProxyRegistrar.class"
+ },
+ {
+ "glob": "org/springframework/context/annotation/AutoProxyRegistrar.class"
+ },
+ {
+ "glob": "org/springframework/context/annotation/Conditional.class"
+ },
+ {
+ "glob": "org/springframework/context/annotation/Configuration.class"
+ },
+ {
+ "glob": "org/springframework/context/annotation/DeferredImportSelector.class"
+ },
+ {
+ "glob": "org/springframework/context/annotation/EnableAspectJAutoProxy.class"
+ },
+ {
+ "glob": "org/springframework/context/annotation/Import.class"
+ },
+ {
+ "glob": "org/springframework/context/annotation/ImportAware.class"
+ },
+ {
+ "glob": "org/springframework/context/annotation/ImportBeanDefinitionRegistrar.class"
+ },
+ {
+ "glob": "org/springframework/context/annotation/ImportRuntimeHints.class"
+ },
+ {
+ "glob": "org/springframework/context/annotation/Profile.class"
+ },
+ {
+ "glob": "org/springframework/context/annotation/Role.class"
+ },
+ {
+ "glob": "org/springframework/context/event/AbstractApplicationEventMulticaster.class"
+ },
+ {
+ "glob": "org/springframework/context/event/ApplicationEventMulticaster.class"
+ },
+ {
+ "glob": "org/springframework/context/event/SimpleApplicationEventMulticaster.class"
+ },
+ {
+ "glob": "org/springframework/core/Ordered.class"
+ },
+ {
+ "glob": "org/springframework/core/annotation/Order.class"
+ },
+ {
+ "glob": "org/springframework/core/convert/converter/Converter.class"
+ },
+ {
+ "glob": "org/springframework/data/jpa/repository/JpaRepository.class"
+ },
+ {
+ "glob": "org/springframework/data/repository/CrudRepository.class"
+ },
+ {
+ "glob": "org/springframework/data/repository/ListCrudRepository.class"
+ },
+ {
+ "glob": "org/springframework/data/repository/ListPagingAndSortingRepository.class"
+ },
+ {
+ "glob": "org/springframework/data/repository/PagingAndSortingRepository.class"
+ },
+ {
+ "glob": "org/springframework/data/repository/Repository.class"
+ },
+ {
+ "glob": "org/springframework/data/repository/query/QueryByExampleExecutor.class"
+ },
+ {
+ "glob": "org/springframework/data/web/config/EnableSpringDataWebSupport$QuerydslActivator.class"
+ },
+ {
+ "glob": "org/springframework/data/web/config/EnableSpringDataWebSupport$SpringDataWebConfigurationImportSelector.class"
+ },
+ {
+ "glob": "org/springframework/data/web/config/EnableSpringDataWebSupport$SpringDataWebSettingsRegistrar.class"
+ },
+ {
+ "glob": "org/springframework/data/web/config/EnableSpringDataWebSupport.class"
+ },
+ {
+ "glob": "org/springframework/data/web/config/ProjectingArgumentResolverRegistrar$ProjectingArgumentResolverBeanPostProcessor.class"
+ },
+ {
+ "glob": "org/springframework/data/web/config/ProjectingArgumentResolverRegistrar.class"
+ },
+ {
+ "glob": "org/springframework/data/web/config/SpringDataJackson3Configuration.class"
+ },
+ {
+ "glob": "org/springframework/data/web/config/SpringDataJackson3Modules.class"
+ },
+ {
+ "glob": "org/springframework/data/web/config/SpringDataWebConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/scheduling/annotation/AbstractAsyncConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/scheduling/annotation/AsyncConfigurationSelector.class"
+ },
+ {
+ "glob": "org/springframework/scheduling/annotation/EnableAsync.class"
+ },
+ {
+ "glob": "org/springframework/scheduling/annotation/ProxyAsyncConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/transaction/annotation/EnableTransactionManagement.class"
+ },
+ {
+ "glob": "org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/transaction/annotation/TransactionManagementConfigurationSelector.class"
+ },
+ {
+ "glob": "org/springframework/web/bind/annotation/Mapping.class"
+ },
+ {
+ "glob": "org/springframework/web/bind/annotation/RequestMapping.class"
+ },
+ {
+ "glob": "org/springframework/web/bind/annotation/ResponseBody.class"
+ },
+ {
+ "glob": "org/springframework/web/bind/annotation/RestController.class"
+ },
+ {
+ "glob": "org/springframework/web/context/ServletContextAware.class"
+ },
+ {
+ "glob": "org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class"
+ },
+ {
+ "glob": "org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport$NoOpValidator.class"
+ },
+ {
+ "glob": "org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.class"
+ },
+ {
+ "glob": "org/springframework/web/servlet/config/annotation/WebMvcConfigurer.class"
+ },
+ {
+ "glob": "org/springframework/web/socket/WebSocketHandler.class"
+ },
+ {
+ "glob": "org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurer.class"
+ },
+ {
+ "glob": "spring.properties"
+ },
+ {
+ "glob": "templates"
+ },
+ {
+ "glob": "templates/error.html"
+ },
+ {
+ "module": "java.desktop",
+ "glob": "sun/awt/resources/awt_zh.properties"
+ },
+ {
+ "module": "java.desktop",
+ "glob": "sun/awt/resources/awt_zh_Hans.properties"
+ },
+ {
+ "module": "java.desktop",
+ "glob": "sun/awt/resources/awt_zh_Hans_CN.properties"
+ },
+ {
+ "bundle": "jakarta.servlet.http.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.catalina.authenticator.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.catalina.connector.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.catalina.core.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.catalina.deploy.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.catalina.loader.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.catalina.mapper.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.catalina.mbeans.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.catalina.realm.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.catalina.session.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.catalina.startup.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.catalina.util.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.catalina.valves.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.catalina.webresources.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.coyote.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.coyote.http11.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.naming.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.tomcat.util.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.tomcat.util.buf.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.tomcat.util.compat.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.tomcat.util.descriptor.web.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.tomcat.util.http.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.tomcat.util.http.parser.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.tomcat.util.modeler.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.tomcat.util.net.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.tomcat.util.scan.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.tomcat.util.threads.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.tomcat.websocket.LocalStrings"
+ },
+ {
+ "bundle": "org.apache.tomcat.websocket.server.LocalStrings"
+ },
+ {
+ "bundle": "sun.awt.resources.awt"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index f2361c1..58a98fe 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -11,7 +11,25 @@ spring:
dialect: org.hibernate.dialect.MySQLDialect
starbot:
+ bilibili:
+ live-report:
+ storage:
+ # sqlite (default), redis, mysql, or memory
+ type: sqlite
+ # SQLite defaults to the directory containing the external config file.
+ # sqlite-file: ./config/starbot-live-report.sqlite3
+ redis-uri: redis://localhost:6379/0
+ redis-prefix: starbot:report:v1
+ # jdbc-url: jdbc:mysql://localhost:3306/starbot
+ # username: starbot
+ # password: change-me
+ fail-fast: false
+ migrate-legacy: true
+ v2-redis-uri: redis://localhost:6379/0
+ buffer-capacity: 20000
+ batch-size: 500
+ flush-millis: 1000
core:
log:
console: INFO
- file: INFO
\ No newline at end of file
+ file: INFO
diff --git a/src/test/kotlin/com/starlwr/bot/bilibili/report/LiveReportDataDriverTest.kt b/src/test/kotlin/com/starlwr/bot/bilibili/report/LiveReportDataDriverTest.kt
new file mode 100644
index 0000000..e6b2680
--- /dev/null
+++ b/src/test/kotlin/com/starlwr/bot/bilibili/report/LiveReportDataDriverTest.kt
@@ -0,0 +1,49 @@
+package com.starlwr.bot.bilibili.report
+
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.io.TempDir
+import java.nio.file.Path
+
+class LiveReportDataDriverTest {
+ @TempDir lateinit var temp: Path
+ private val session = ReportSession("bilibili:1:1000", "bilibili", 1, 2, "tester", 1000)
+ private val delta = ReportDelta(ReportMetric.BOX, 2, 30.0, 5.0,
+ ReportUserDelta("9", "sender", count = 2, value = 30.0, profit = 5.0), 60_001, label = "gift")
+
+ @Test fun `memory driver is idempotent`() {
+ val driver = InMemoryLiveReportDataDriver(); driver.initialize(); driver.createOrResume(session)
+ assertTrue(driver.apply(session, "same", delta)); assertFalse(driver.apply(session, "same", delta))
+ val result = driver.snapshot(session.sessionId)!!
+ assertEquals(2, result.counts["box"]); assertEquals(5.0, result.profits["box"])
+ assertEquals(2, result.users["box"]?.get("9")?.count)
+ }
+
+ @Test fun `sqlite survives reopen and rejects duplicate event`() {
+ val url = "jdbc:sqlite:${temp.resolve("report.db").toAbsolutePath()}"
+ JdbcLiveReportDataDriver("sqlite", url).use { first ->
+ first.initialize(); first.createOrResume(session); assertTrue(first.apply(session, "same", delta))
+ }
+ JdbcLiveReportDataDriver("sqlite", url).use { second ->
+ second.initialize(); assertFalse(second.apply(session, "same", delta))
+ assertEquals(2, second.snapshot(session.sessionId)?.counts?.get("box"))
+ }
+ }
+
+ @Test fun `explicit false does not enable unrelated features`() {
+ val json = com.alibaba.fastjson2.JSON.parseObject("""{"sections":{"box":false,"gift":true},"charts":{"box":{"enabled":true}},"word_cloud":{"enabled":false}}""")
+ val config = LiveReportTargetConfig.from(json)
+ assertFalse(config.section("box")); assertTrue(config.section("gift")); assertTrue(config.chart("box"))
+ assertFalse(config.wordCloud); assertFalse(config.chart("gift"))
+ }
+
+ @Test fun `buffer flushes before completion`() {
+ val delegate = InMemoryLiveReportDataDriver()
+ val driver = BufferedLiveReportDataDriver(delegate, capacity = 100, batchSize = 10, flushMillis = 60_000)
+ driver.initialize(); driver.createOrResume(session)
+ repeat(25) { driver.apply(session, "e$it", ReportDelta(ReportMetric.DANMU, 1)) }
+ assertEquals(25, driver.snapshot(session.sessionId)?.counts?.get("danmu"))
+ assertEquals(25, driver.complete(session.sessionId, 2000)?.counts?.get("danmu"))
+ driver.close()
+ }
+}
diff --git a/src/test/kotlin/com/starlwr/bot/bilibili/report/RedisLiveReportDataDriverTest.kt b/src/test/kotlin/com/starlwr/bot/bilibili/report/RedisLiveReportDataDriverTest.kt
new file mode 100644
index 0000000..8660b15
--- /dev/null
+++ b/src/test/kotlin/com/starlwr/bot/bilibili/report/RedisLiveReportDataDriverTest.kt
@@ -0,0 +1,28 @@
+package com.starlwr.bot.bilibili.report
+
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.junit.jupiter.api.Assumptions.assumeTrue
+import org.junit.jupiter.api.Test
+import java.util.UUID
+import java.util.concurrent.Executors
+
+class RedisLiveReportDataDriverTest {
+ @Test fun `two instances atomically deduplicate an event`() {
+ val prefix = "starbot:test:${UUID.randomUUID()}"
+ val first = runCatching { RedisLiveReportDataDriver("redis://localhost:6379/0", prefix).also { it.initialize() } }.getOrNull()
+ assumeTrue(first != null, "Redis 7 is not available on localhost:6379")
+ val second = RedisLiveReportDataDriver("redis://localhost:6379/0", prefix).also { it.initialize() }
+ try {
+ val session = ReportSession("redis-test", "bilibili", 1, 2, "test", 1)
+ first!!.createOrResume(session)
+ val pool = Executors.newFixedThreadPool(2)
+ val results = listOf(first, second).map { driver -> pool.submit {
+ driver.apply(session, "event", ReportDelta(ReportMetric.DANMU, 1))
+ } }.map { it.get() }
+ pool.shutdown()
+ assertEquals(1, results.count { it }); assertEquals(1, first.snapshot(session.sessionId)?.counts?.get("danmu"))
+ assertFalse(first.apply(session, "event", ReportDelta(ReportMetric.DANMU, 1)))
+ } finally { first?.close(); second.close() }
+ }
+}
diff --git a/src/test/kotlin/com/starlwr/bot/bilibili/report/ReportArchiveTest.kt b/src/test/kotlin/com/starlwr/bot/bilibili/report/ReportArchiveTest.kt
new file mode 100644
index 0000000..5cfa751
--- /dev/null
+++ b/src/test/kotlin/com/starlwr/bot/bilibili/report/ReportArchiveTest.kt
@@ -0,0 +1,15 @@
+package com.starlwr.bot.bilibili.report
+
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+
+class ReportArchiveTest {
+ @Test fun `protobuf TLV archive round trips multiple snapshots`() {
+ val output = ByteArrayOutputStream()
+ repeat(2) { ReportArchive.write(ReportSession("s$it", "bilibili", it.toLong(), 3, "u$it", 4).snapshot(), output) }
+ val result = ReportArchive.read(ByteArrayInputStream(output.toByteArray())).toList()
+ assertEquals(listOf("s0", "s1"), result.map { it.sessionId })
+ }
+}
From d93f056d35b5949e0f418f10c74b24c271907a0b Mon Sep 17 00:00:00 2001
From: HanaHime <62001729+HanaKDev@users.noreply.github.com>
Date: Tue, 14 Jul 2026 08:49:34 +0800
Subject: [PATCH 03/17] =?UTF-8?q?=E5=BC=B9=E5=B9=95=E7=BB=9F=E8=AE=A1=20an?=
=?UTF-8?q?d=20=E7=BB=98=E5=9B=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
...一顿操作猛如虎,先测试了才知道好不好
这个就当是BETA 8.1 GA 1 喵w
---
pom.xml | 20 ++
.../StarBotBilibiliThreadPoolConfig.java | 2 -
.../handler/BilibiliDynamicPushHandler.java | 2 +
.../handler/BilibiliLiveOffPushHandler.java | 2 +
.../handler/BilibiliLiveOnPushHandler.java | 2 +
.../starlwr/bot/bilibili/model/Cookies.java | 61 ++++++
.../service/BilibiliAccountService.java | 172 ++++++++---------
.../bot/bilibili/util/BilibiliApiUtil.java | 9 +-
.../credential/BilibiliBrowserIdentity.kt | 54 ++++++
.../credential/BilibiliCredentialService.kt | 140 ++++++++++++--
.../bilibili/onebot/OneBotCommandClient.kt | 169 +++++++++++++++++
.../bilibili/report/LiveReportCollector.kt | 61 +++++-
.../bot/bilibili/report/LiveReportConfig.kt | 2 +
.../report/LiveReportDemandService.kt | 4 +
.../bot/bilibili/report/LiveReportModel.kt | 11 +-
.../bot/bilibili/report/LiveReportPainter.kt | 90 +++++----
.../bilibili/report/LiveReportPushHandler.kt | 7 +-
.../report/V2InteractionChartRenderer.kt | 173 ++++++++++++++++++
.../bilibili/report/V2WordCloudRenderer.kt | 165 +++++++++++++++++
src/main/resources/application.yml | 28 +++
src/main/resources/fonts/cloud.ttf | Bin 0 -> 2098640 bytes
.../report/BilibiliCredentialServiceTest.kt | 64 +++++++
.../report/LiveReportCollectorTest.kt | 40 ++++
.../report/LiveReportDataDriverTest.kt | 12 +-
.../bilibili/report/LiveReportPainterTest.kt | 54 ++++++
25 files changed, 1161 insertions(+), 183 deletions(-)
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/credential/BilibiliBrowserIdentity.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/onebot/OneBotCommandClient.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/V2InteractionChartRenderer.kt
create mode 100644 src/main/kotlin/com/starlwr/bot/bilibili/report/V2WordCloudRenderer.kt
create mode 100644 src/main/resources/fonts/cloud.ttf
create mode 100644 src/test/kotlin/com/starlwr/bot/bilibili/report/BilibiliCredentialServiceTest.kt
create mode 100644 src/test/kotlin/com/starlwr/bot/bilibili/report/LiveReportCollectorTest.kt
create mode 100644 src/test/kotlin/com/starlwr/bot/bilibili/report/LiveReportPainterTest.kt
diff --git a/pom.xml b/pom.xml
index caa5aab..5fa1709 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,12 @@
+
+