diff --git a/build.gradle.kts b/build.gradle.kts index a44ad8d..7875a68 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -97,7 +97,10 @@ subprojects { apply { // plugin(libs.plugins.java.get().pluginId) // plugin(libs.plugins.javaLibrary.get().pluginId) - plugin(libs.plugins.maven.publish.get().pluginId) + if (!project.name.contains("demo")) { + plugin(libs.plugins.maven.publish.get().pluginId) + } + plugin(libs.plugins.kotlin.plugin.spring.get().pluginId) plugin(libs.plugins.kotlin.jvm.get().pluginId) plugin(libs.plugins.axionRelease.get().pluginId) plugin(libs.plugins.kotlin.kapt.get().pluginId) @@ -111,85 +114,86 @@ subprojects { // signAllPublications() // } // 发布配置 - publishing { - repositories { - // 本地仓库 - maven { - name = "localRepo" - url = uri("file://D:/Config/Jrebel/.jrebel/.m2/repository") - } - // GitHub Packages 仓库 - maven { - name = "GitHubPackages" - url = uri("https://maven.pkg.github.com/GeWuYou/forgeboot") - credentials { - username = System.getenv("GITHUB_USERNAME") - password = System.getenv("GITHUB_TOKEN") - } - } - maven { - name = "GitLab" - url = uri("https://gitlab.snow-lang.com/api/v4/projects/18/packages/maven") - credentials(HttpHeaderCredentials::class) { - name = "Private-Token" - value = System.getenv("PIPELINE_BOT_TOKEN") // 存储为 GitLab CI/CD Secret - } - authentication { - create("header", HttpHeaderAuthentication::class) - } - } - // Gitea 仓库 - val host = System.getenv("GITEA_HOST") - host?.let { + if (!project.name.contains("demo")) { + publishing { + repositories { + // 本地仓库 maven { - name = "Gitea" - url = uri("${it}/api/packages/gewuyou/maven") - credentials(HttpHeaderCredentials::class.java) { - name = "Authorization" - value = "token ${System.getenv("GITEA_TOKEN")}" + name = "localRepo" + url = uri("file://D:/Config/Jrebel/.jrebel/.m2/repository") + } + // GitHub Packages 仓库 + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/GeWuYou/forgeboot") + credentials { + username = System.getenv("GITHUB_USERNAME") + password = System.getenv("GITHUB_TOKEN") + } + } + maven { + name = "GitLab" + url = uri("https://gitlab.snow-lang.com/api/v4/projects/18/packages/maven") + credentials(HttpHeaderCredentials::class) { + name = "Private-Token" + value = System.getenv("PIPELINE_BOT_TOKEN") // 存储为 GitLab CI/CD Secret } authentication { - create("header", HttpHeaderAuthentication::class.java) + create("header", HttpHeaderAuthentication::class) + } + } + // Gitea 仓库 + val host = System.getenv("GITEA_HOST") + host?.let { + maven { + name = "Gitea" + url = uri("${it}/api/packages/gewuyou/maven") + credentials(HttpHeaderCredentials::class.java) { + name = "Authorization" + value = "token ${System.getenv("GITEA_TOKEN")}" + } + authentication { + create("header", HttpHeaderAuthentication::class.java) + } } } } - } - publications { - create("mavenJava") { - val projectName = project.name - from(components["java"]) - groupId = project.group.toString() - artifactId = projectName - version = project.version.toString() + publications { + create("mavenJava") { + val projectName = project.name + from(components["java"]) + groupId = project.group.toString() + artifactId = projectName + version = project.version.toString() - pom { - name.set(projectName) - description.set("Part of Forgeboot Starters") - url.set("https://github.com/GeWuYou/forgeboot") - - licenses { - license { - name.set("Apache-2.0") - url.set("https://www.apache.org/licenses/LICENSE-2.0") - } - } - developers { - developer { - id.set("gewuyou") - name.set("gewuyou") - email.set("gewuyou1024@gmail.com") - } - } - scm { - connection.set("scm:git:git://github.com/GeWuYou/forgeboot.git") - developerConnection.set("scm:git:ssh://github.com/GeWuYou/forgeboot.git") + pom { + name.set(projectName) + description.set("Part of Forgeboot Starters") url.set("https://github.com/GeWuYou/forgeboot") + + licenses { + license { + name.set("Apache-2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0") + } + } + developers { + developer { + id.set("gewuyou") + name.set("gewuyou") + email.set("gewuyou1024@gmail.com") + } + } + scm { + connection.set("scm:git:git://github.com/GeWuYou/forgeboot.git") + developerConnection.set("scm:git:ssh://github.com/GeWuYou/forgeboot.git") + url.set("https://github.com/GeWuYou/forgeboot") + } } } } } } - // 依赖配置 dependencies { @@ -217,8 +221,6 @@ subprojects { tasks.named("test") { useJUnitPlatform() } - - } /** * 注册一个 Gradle 任务用于清理项目中的无用文件。 diff --git a/buildSrc/src/main/kotlin/Modules.kt b/buildSrc/src/main/kotlin/Modules.kt index 1610149..267e7aa 100644 --- a/buildSrc/src/main/kotlin/Modules.kt +++ b/buildSrc/src/main/kotlin/Modules.kt @@ -117,4 +117,7 @@ object Modules { "${AUTHORIZE}:forgeboot-security-authorize-autoconfigure" } } + object Demo{ + const val ROOT = ":forgeboot-demo" + } } \ No newline at end of file diff --git a/forgeboot-context/forgeboot-context-api/build.gradle.kts b/forgeboot-context/forgeboot-context-api/build.gradle.kts index 52ab83a..ee3ec29 100644 --- a/forgeboot-context/forgeboot-context-api/build.gradle.kts +++ b/forgeboot-context/forgeboot-context-api/build.gradle.kts @@ -1,4 +1,4 @@ dependencies { - + compileOnly(libs.kotlinxCoroutines.reactor) } diff --git a/forgeboot-context/forgeboot-context-api/src/main/kotlin/com/gewuyou/forgeboot/context/api/AbstractContext.kt b/forgeboot-context/forgeboot-context-api/src/main/kotlin/com/gewuyou/forgeboot/context/api/AbstractContext.kt index f403f04..a817045 100644 --- a/forgeboot-context/forgeboot-context-api/src/main/kotlin/com/gewuyou/forgeboot/context/api/AbstractContext.kt +++ b/forgeboot-context/forgeboot-context-api/src/main/kotlin/com/gewuyou/forgeboot/context/api/AbstractContext.kt @@ -47,11 +47,16 @@ abstract class AbstractContext: Context { } /** - * 从上下文中移除指定的键值对。 + * 从上下文中移除指定的键值对并返回被移除的值。 * - * @param key 要移除的键 + * 此方法用于在上下文中删除与指定键关联的条目。如果该键存在, + * 则将其从上下文中移除,并返回与之关联的值;如果该键不存在, + * 则返回 null。 + * + * @param key 要移除的键,不能为空 + * @return 与指定键关联的值,如果键不存在则返回 null */ - override fun remove(key: K) { - local.get().remove(key) + override fun remove(key: K) : V? { + return local.get().remove(key) } } \ No newline at end of file diff --git a/forgeboot-context/forgeboot-context-api/src/main/kotlin/com/gewuyou/forgeboot/context/api/Context.kt b/forgeboot-context/forgeboot-context-api/src/main/kotlin/com/gewuyou/forgeboot/context/api/Context.kt index 8d153cf..80ccb29 100644 --- a/forgeboot-context/forgeboot-context-api/src/main/kotlin/com/gewuyou/forgeboot/context/api/Context.kt +++ b/forgeboot-context/forgeboot-context-api/src/main/kotlin/com/gewuyou/forgeboot/context/api/Context.kt @@ -45,9 +45,14 @@ interface Context { fun clear() /** - * 从上下文中移除指定的键值对。 + * 从上下文中移除指定的键值对并返回被移除的值。 * - * @param key 要移除的键 + * 此方法用于在上下文中删除与指定键关联的条目。如果该键存在, + * 则将其从上下文中移除,并返回与之关联的值;如果该键不存在, + * 则返回 null。 + * + * @param key 要移除的键,不能为空 + * @return 与指定键关联的值,如果键不存在则返回 null */ - fun remove(key: K) + fun remove(key: K): V? } \ No newline at end of file diff --git a/forgeboot-context/forgeboot-context-autoconfigure/src/main/kotlin/com/gewuyou/forgeboot/context/autoconfigure/ContextWebFluxAutoConfiguration.kt b/forgeboot-context/forgeboot-context-autoconfigure/src/main/kotlin/com/gewuyou/forgeboot/context/autoconfigure/ContextWebFluxAutoConfiguration.kt index ff8f01f..11c368b 100644 --- a/forgeboot-context/forgeboot-context-autoconfigure/src/main/kotlin/com/gewuyou/forgeboot/context/autoconfigure/ContextWebFluxAutoConfiguration.kt +++ b/forgeboot-context/forgeboot-context-autoconfigure/src/main/kotlin/com/gewuyou/forgeboot/context/autoconfigure/ContextWebFluxAutoConfiguration.kt @@ -3,6 +3,7 @@ package com.gewuyou.forgeboot.context.autoconfigure import com.gewuyou.forgeboot.context.api.ContextProcessor import com.gewuyou.forgeboot.context.impl.ContextHolder import com.gewuyou.forgeboot.context.impl.filter.ContextWebFilter +import com.gewuyou.forgeboot.context.impl.filter.CoroutineMdcWebFilter import com.gewuyou.forgeboot.context.impl.processor.ReactorProcessor import org.springframework.boot.autoconfigure.condition.ConditionalOnClass import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean @@ -46,4 +47,17 @@ class ContextWebFluxAutoConfiguration { reactorProcessor: ReactorProcessor, contextHolder: ContextHolder ) = ContextWebFilter(chain, reactorProcessor, contextHolder) + /** + * 注册 CoroutineMdcWebFilter Bean,用于在协程环境中传播 MDC 上下文信息。 + * + * MDC(Mapped Diagnostic Context)通常用于存储线程上下文数据,在异步或协程编程模型中, + * 需要特殊的处理以确保上下文能够在不同协程之间正确传递。 + * 该过滤器通过注册为 Spring WebFlux 的 WebFilter,确保请求链路中的 MDC 数据一致性。 + * + * @return 构建完成的 CoroutineMdcWebFilter 实例 + */ + @Bean + @ConditionalOnMissingBean + @Order(Ordered.HIGHEST_PRECEDENCE + 11) // 稍晚于 ContextWebFilter 执行 + fun coroutineMdcWebFilter(contextHolder: ContextHolder): CoroutineMdcWebFilter = CoroutineMdcWebFilter(contextHolder) } \ No newline at end of file diff --git a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/ContextHolder.kt b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/ContextHolder.kt index 1373d55..397bbc7 100644 --- a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/ContextHolder.kt +++ b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/ContextHolder.kt @@ -33,4 +33,12 @@ open class ContextHolder( override fun retrieveByType(key: String, type: Class): T? { return retrieve(key)?.let { valueSerializer.deserialize(it, type) } } + /** + * 将指定的映射中的所有键值对存储到上下文中。 + * + * @param map 包含键值对的映射,键为字符串类型,值可以为任意类型或 null + */ + fun putAll(map: Map) { + map.forEach { (k, v) -> put(k, v) } + } } \ No newline at end of file diff --git a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/coroutine/ContextAwareCoroutineScope.kt b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/coroutine/ContextAwareCoroutineScope.kt new file mode 100644 index 0000000..cc7393e --- /dev/null +++ b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/coroutine/ContextAwareCoroutineScope.kt @@ -0,0 +1,102 @@ +package com.gewuyou.forgeboot.context.impl.coroutine + +import com.gewuyou.forgeboot.context.api.ContextProcessor +import com.gewuyou.forgeboot.context.impl.ContextHolder +import com.gewuyou.forgeboot.context.impl.element.CoroutineContextMapElement +import kotlinx.coroutines.* +import kotlin.coroutines.CoroutineContext + +/** + * 上下文感知的协程作用域实现类 + * 用于在协程中保持上下文信息的传递和一致性 + * + * @property contextHolder 上下文持有者实例,用于获取和存储上下文快照 + * @property processors 上下文处理器列表,用于在协程中注入和清理上下文数据 + * @since 2025-07-17 17:31:31 + * @author gewuyou + */ +class ContextAwareCoroutineScope( + private val contextHolder: ContextHolder, + private val processors: List, +) : CoroutineScope { + + /** + * 协程上下文配置,包含SupervisorJob和默认调度器 + * SupervisorJob确保子协程的独立生命周期 + * Dispatchers.Default适用于计算密集型任务 + */ + override val coroutineContext: CoroutineContext = SupervisorJob() + Dispatchers.Default + + /** + * 启动一个带有上下文传播的协程 + * + * @param dispatcher 协程调度器,默认使用Dispatchers.Default + * @param block 协程执行的具体逻辑 + * @return 返回Job对象用于管理协程生命周期 + */ + fun launchWithContext( + dispatcher: CoroutineDispatcher = Dispatchers.Default, + block: suspend CoroutineScope.() -> Unit, + ): Job { + val snapshot = contextHolder.snapshot().toMutableMap() + return launch(dispatcher + CoroutineContextMapElement(contextHolder, snapshot)) { + try { + // 注入上下文数据到当前协程 + processors.forEach { it.inject(Unit, snapshot) } + block() + } finally { + // 清理上下文数据并释放资源 + processors.forEach { it.inject(Unit, mutableMapOf()) } + contextHolder.clear() + } + } + } + + /** + * 异步启动一个带有上下文传播的协程 + * + * @param dispatcher 协程调度器,默认使用Dispatchers.Default + * @param block 协程执行的具体逻辑 + * @return 返回Deferred对象用于获取异步执行结果 + */ + fun asyncWithContext( + dispatcher: CoroutineDispatcher = Dispatchers.Default, + block: suspend CoroutineScope.() -> T, + ): Deferred { + val snapshot = contextHolder.snapshot().toMutableMap() + return async(dispatcher + CoroutineContextMapElement(contextHolder, snapshot)) { + try { + // 注入上下文数据到当前协程 + processors.forEach { it.inject(Unit, snapshot) } + block() + } finally { + // 清理上下文数据并释放资源 + processors.forEach { it.inject(Unit, mutableMapOf()) } + contextHolder.clear() + } + } + } + + /** + * 在指定上下文中执行挂起代码块 + * + * @param block 需要执行的挂起代码块 + * @return 返回执行结果 + */ + suspend fun runWithContext( + block: suspend () -> T, + ): T { + val snapshot = contextHolder.snapshot().toMutableMap() + return withContext(CoroutineContextMapElement(contextHolder, snapshot)) { + try { + // 注入上下文数据到当前协程 + processors.forEach { it.inject(Unit, snapshot) } + block() + } finally { + // 清理上下文数据并释放资源 + processors.forEach { it.inject(Unit, mutableMapOf()) } + contextHolder.clear() + } + } + } +} \ No newline at end of file diff --git a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/element/CoroutineContextMapElement.kt b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/element/CoroutineContextMapElement.kt new file mode 100644 index 0000000..954e1d9 --- /dev/null +++ b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/element/CoroutineContextMapElement.kt @@ -0,0 +1,80 @@ +package com.gewuyou.forgeboot.context.impl.element + +import com.gewuyou.forgeboot.context.impl.ContextHolder +import kotlinx.coroutines.ThreadContextElement +import kotlinx.coroutines.withContext +import kotlin.coroutines.AbstractCoroutineContextElement +import kotlin.coroutines.CoroutineContext + +/** + * 协程上下文键值对元素,用于在协程上下文中保存和恢复一个Map结构的数据 + * + * @property contextMap 保存协程上下文数据的Map,键为字符串,值为可空的任意类型对象 + * @since 2025-07-17 14:27:05 + * @author gewuyou + */ +class CoroutineContextMapElement( + private val contextHolder: ContextHolder, + private val contextMap: Map, +) : ThreadContextElement>, + AbstractCoroutineContextElement(Key) { + + companion object Key : CoroutineContext.Key { + + /** + * 在统一的协程上下文快照中执行指定的挂起代码块。 + * + * 该方法会将给定的上下文快照设置为新的协程上下文,并在其中执行传入的代码块。 + * 使用 [withContext] 切换到新的协程上下文,同时通过 [ContextHolder.putAll] 更新上下文数据。 + * + * @param contextHolder 用于存储和更新上下文数据的容器 + * @param snapshot 提供初始上下文数据的键值对映射 + * @param block 需要在新上下文中执行的挂起函数 + * @return 返回执行完成后的结果 + */ + suspend fun withUnifiedContextSnapshot( + contextHolder: ContextHolder, + snapshot: Map, + block: suspend () -> T, + ): T { + return withContext(CoroutineContextMapElement(contextHolder, snapshot)) { + contextHolder.putAll(snapshot) + block() + } + } + } + + /** + * 更新当前协程上下文时调用,保存当前上下文数据 + * + * @param context 当前协程上下文 + * @return 返回一个空Map,表示保存的上下文状态 + */ + override fun updateThreadContext(context: CoroutineContext): Map { + val oldContext = contextHolder.snapshot() // 原始 ThreadLocal 快照 + contextMap.forEach { (k, v) -> + if (v != null) contextHolder.put(k, v.toString()) + } + return oldContext + } + + /** + * 恢复协程上下文时调用,用于将之前保存的上下文状态还原 + * + * @param context 当前协程上下文 + * @param oldState 之前保存的上下文状态 + */ + override fun restoreThreadContext(context: CoroutineContext, oldState: Map) { + contextHolder.clear() + oldState.forEach { (k, v): Map.Entry -> contextHolder.put(k, v) } + } + + /** + * 获取当前协程上下文中保存的Map数据 + * + * @return 返回包含当前上下文数据的Map + */ + fun getContext(): Map = contextMap + + +} \ No newline at end of file diff --git a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/element/MdcContextElement.kt b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/element/MdcContextElement.kt deleted file mode 100644 index ae9d022..0000000 --- a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/element/MdcContextElement.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.gewuyou.forgeboot.context.impl.element - -import kotlinx.coroutines.ThreadContextElement -import org.slf4j.MDC -import kotlin.coroutines.AbstractCoroutineContextElement -import kotlin.coroutines.CoroutineContext - -/** - * MDC上下文元素,用于在协程中传播MDC(Mapped Diagnostic Context)上下文。 - * - * MDC 是 SLF4J 提供的一个日志诊断工具,允许将特定信息绑定到当前线程的上下文中, - * 以便在日志输出时能够包含这些信息。由于协程可能在线程间切换,因此需要此实现来保证上下文一致性。 - * - * @param contextMap 包含 MDC 上下文键值对的不可变 Map,用于保存和恢复诊断上下文。 - * @since 2025-07-16 11:05:47 - * @author gewuyou - */ -class MdcContextElement( - private val contextMap: Map -) : ThreadContextElement>, - AbstractCoroutineContextElement(Key) { - - /** - * 协程上下文键对象,用于标识此类上下文元素的唯一性。 - */ - companion object Key : CoroutineContext.Key - - /** - * 更新当前线程的 MDC 上下文为协程指定的上下文,并返回旧的上下文状态。 - * - * 此方法会在协程切换至新线程时调用,以确保目标线程的 MDC 上下文与协程一致。 - * - * @param context 当前协程的上下文,不直接使用但保留用于扩展。 - * @return 返回更新前的 MDC 上下文状态,用于后续恢复。 - */ - override fun updateThreadContext(context: CoroutineContext): Map { - val oldState = MDC.getCopyOfContextMap() ?: emptyMap() - MDC.setContextMap(contextMap) - return oldState - } - - /** - * 恢复当前线程的 MDC 上下文至先前保存的状态。 - * - * 此方法在协程完成执行并释放线程资源时调用,确保线程可以还原其原始 MDC 上下文。 - * - * @param context 当前协程的上下文,不直接使用但保留用于扩展。 - * @param oldState 需要恢复的先前 MDC 上下文状态。 - */ - override fun restoreThreadContext(context: CoroutineContext, oldState: Map) { - MDC.setContextMap(oldState) - } -} \ No newline at end of file diff --git a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/filter/CoroutineMdcWebFilter.kt b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/filter/CoroutineMdcWebFilter.kt index 2b71165..6751638 100644 --- a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/filter/CoroutineMdcWebFilter.kt +++ b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/filter/CoroutineMdcWebFilter.kt @@ -1,6 +1,7 @@ package com.gewuyou.forgeboot.context.impl.filter -import com.gewuyou.forgeboot.context.impl.element.MdcContextElement +import com.gewuyou.forgeboot.context.impl.ContextHolder +import com.gewuyou.forgeboot.context.impl.element.CoroutineContextMapElement import kotlinx.coroutines.reactive.awaitFirstOrNull import kotlinx.coroutines.reactor.mono import org.springframework.web.server.ServerWebExchange @@ -15,21 +16,32 @@ import java.util.stream.Collectors * 用于在响应式编程环境下,将上下文(如 MDC)从 Reactor Context 传递到 Kotlin 协程中, * 确保日志上下文信息能够正确传播。 * + * @property contextHolder 上下文持有者实例,用于存储和管理 MDC 上下文数据 + * * @since 2025-07-16 11:07:44 * @author gewuyou */ -class CoroutineMdcWebFilter : WebFilter { +class CoroutineMdcWebFilter( + private val contextHolder: ContextHolder +) : WebFilter { /** * 执行过滤操作的方法 * + * 在响应式流执行过程中拦截并提取 Reactor Context 中的 MDC 数据, + * 将其封装为协程上下文并在新的协程环境中继续执行过滤链, + * 以确保日志上下文信息在异步非阻塞处理流程中正确传播。 + * * @param exchange 表示当前的服务器 Web 交换信息,包含请求和响应 * @param chain 当前的过滤器链,用于继续执行后续的过滤器或目标处理器 * @return 返回一个 Mono,表示异步完成的过滤操作 */ override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono { return Mono.deferContextual { ctxView -> - // 从 Reactor 上下文中提取键值对,筛选出 key 和 value 均为 String 类型的条目 + /** + * 从 Reactor 上下文中提取键值对,筛选出 key 和 value 均为 String 类型的条目 + * 构建 MDC 数据映射表,用于传递日志上下文信息 + */ val mdcMap = ctxView.stream() .filter { it.key is String && it.value is String } .collect(Collectors.toMap( @@ -37,8 +49,14 @@ class CoroutineMdcWebFilter : WebFilter { { it.value as String } )) - // 在带有 MDC 上下文的协程中执行过滤链 - mono(MdcContextElement(mdcMap)) { + /** + * 创建带有 MDC 上下文的协程环境并执行过滤链 + * 1. 将 MDC 数据注入协程上下文 + * 2. 将上下文数据同步到 ContextHolder + * 3. 执行后续过滤器链并等待结果 + */ + mono(CoroutineContextMapElement(contextHolder,mdcMap)) { + contextHolder.putAll(mdcMap) chain.filter(exchange).awaitFirstOrNull() } } diff --git a/forgeboot-demo/.gitattributes b/forgeboot-demo/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/forgeboot-demo/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/forgeboot-demo/.gitignore b/forgeboot-demo/.gitignore new file mode 100644 index 0000000..5a979af --- /dev/null +++ b/forgeboot-demo/.gitignore @@ -0,0 +1,40 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Kotlin ### +.kotlin diff --git a/forgeboot-demo/build.gradle.kts b/forgeboot-demo/build.gradle.kts new file mode 100644 index 0000000..dc48148 --- /dev/null +++ b/forgeboot-demo/build.gradle.kts @@ -0,0 +1,5 @@ + +dependencies { + +} + diff --git a/forgeboot-demo/forgeboot-trace-demo/.gitattributes b/forgeboot-demo/forgeboot-trace-demo/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/forgeboot-demo/forgeboot-trace-demo/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/forgeboot-demo/forgeboot-trace-demo/.gitignore b/forgeboot-demo/forgeboot-trace-demo/.gitignore new file mode 100644 index 0000000..5a979af --- /dev/null +++ b/forgeboot-demo/forgeboot-trace-demo/.gitignore @@ -0,0 +1,40 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Kotlin ### +.kotlin diff --git a/forgeboot-demo/forgeboot-trace-demo/build.gradle.kts b/forgeboot-demo/forgeboot-trace-demo/build.gradle.kts new file mode 100644 index 0000000..bbb864c --- /dev/null +++ b/forgeboot-demo/forgeboot-trace-demo/build.gradle.kts @@ -0,0 +1,9 @@ + +dependencies { + implementation(libs.springBootStarter.web) + implementation(project(Modules.TRACE.STARTER)) + implementation(project(Modules.Context.STARTER)) + implementation(libs.kotlinxCoroutines.reactor) + implementation(libs.kotlinxCoroutines.core) +} + diff --git a/forgeboot-demo/forgeboot-trace-demo/src/main/kotlin/com/gewuyou/forgeboot/trace/demo/ForgebootTraceDemoApplication.kt b/forgeboot-demo/forgeboot-trace-demo/src/main/kotlin/com/gewuyou/forgeboot/trace/demo/ForgebootTraceDemoApplication.kt new file mode 100644 index 0000000..70ea493 --- /dev/null +++ b/forgeboot-demo/forgeboot-trace-demo/src/main/kotlin/com/gewuyou/forgeboot/trace/demo/ForgebootTraceDemoApplication.kt @@ -0,0 +1,11 @@ +package com.gewuyou.forgeboot.trace.demo + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication +class ForgebootTraceDemoApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/forgeboot-demo/forgeboot-trace-demo/src/main/kotlin/com/gewuyou/forgeboot/trace/demo/controller/TraceTestController.kt b/forgeboot-demo/forgeboot-trace-demo/src/main/kotlin/com/gewuyou/forgeboot/trace/demo/controller/TraceTestController.kt new file mode 100644 index 0000000..855d3d5 --- /dev/null +++ b/forgeboot-demo/forgeboot-trace-demo/src/main/kotlin/com/gewuyou/forgeboot/trace/demo/controller/TraceTestController.kt @@ -0,0 +1,44 @@ +package com.gewuyou.forgeboot.trace.demo.controller + +import com.gewuyou.forgeboot.context.api.ContextProcessor +import com.gewuyou.forgeboot.context.impl.ContextHolder +import com.gewuyou.forgeboot.context.impl.coroutine.ContextAwareCoroutineScope +import com.gewuyou.forgeboot.core.extension.log +import com.gewuyou.forgeboot.trace.api.RequestIdProvider +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +/** + *MVC控制器 + * + * @since 2025-07-17 17:13:44 + * @author gewuyou + */ +@RestController +@RequestMapping("/trace") +class TraceTestController( + private val requestIdProvider: RequestIdProvider, + private val contextHolder: ContextHolder, + private val processors: List, +) { + @GetMapping("/coroutine") + suspend fun coroutine(): String { + val requestId = requestIdProvider.getRequestId() + log.info("→ Controller RequestId: $requestId") + + // 模拟内部异步任务(3秒后完成) + val scope = ContextAwareCoroutineScope(contextHolder,processors) + scope.launchWithContext { + log.info("RID: ${requestIdProvider.getRequestId()}") + } + return "Main coroutine returned immediately with requestId: $requestId" + } + + @GetMapping("/servlet") + fun servlet(): String { + val requestId = requestIdProvider.getRequestId() + log.info("Servlet requestId: $requestId") + return "Servlet OK: $requestId" + } +} \ No newline at end of file diff --git a/forgeboot-demo/forgeboot-trace-demo/src/main/resources/application-dev.yml b/forgeboot-demo/forgeboot-trace-demo/src/main/resources/application-dev.yml new file mode 100644 index 0000000..e69de29 diff --git a/forgeboot-demo/forgeboot-trace-demo/src/main/resources/application.yml b/forgeboot-demo/forgeboot-trace-demo/src/main/resources/application.yml new file mode 100644 index 0000000..4adf629 --- /dev/null +++ b/forgeboot-demo/forgeboot-trace-demo/src/main/resources/application.yml @@ -0,0 +1,5 @@ +spring: + application: + name: forgeboot-trace-demo + profiles: + active: dev \ No newline at end of file diff --git a/forgeboot-demo/forgeboot-trace-demo/src/main/resources/logback-spring.xml b/forgeboot-demo/forgeboot-trace-demo/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..1e330de --- /dev/null +++ b/forgeboot-demo/forgeboot-trace-demo/src/main/resources/logback-spring.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + ${CONSOLE_PATTERN} + + + + + + + + + diff --git a/settings.gradle.kts b/settings.gradle.kts index 6c04b5f..51e3d31 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -136,6 +136,14 @@ project(":forgeboot-trace:forgeboot-trace-impl").name = "forgeboot-trace-impl" project(":forgeboot-trace:forgeboot-trace-autoconfigure").name = "forgeboot-trace-autoconfigure" //endregion +////region module demo +include( + "forgeboot-demo", + ":forgeboot-demo:forgeboot-trace-demo" +) + + + ////region module security ///** // * Includes and configures projects related to 'forgeboot-security'