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 85662f1..f403f04 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 @@ -26,7 +26,7 @@ abstract class AbstractContext: Context { * @param key 要查找的键 * @return 对应的值,如果不存在则返回 null */ - override fun get(key: K): V? { + override fun retrieve(key: K): V? { return local.get()[key] } 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 b8700dc..8d153cf 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 @@ -21,7 +21,16 @@ interface Context { * @param key 要查找的键 * @return 对应的值,如果不存在则返回 null */ - fun get(key: K): V? + fun retrieve(key: K): V? + + /** + * 根据指定的键和类型从上下文中获取对应的值。 + * + * @param key 要查找的键 + * @param type 要转换的目标类型 + * @return 对应类型的值,如果不存在或类型不匹配则返回 null + */ + fun retrieveByType(key: K, type: Class): T? /** * 获取当前上下文的一个快照,包含所有键值对。 diff --git a/forgeboot-context/forgeboot-context-api/src/main/kotlin/com/gewuyou/forgeboot/context/api/extension/ContextExtensions.kt b/forgeboot-context/forgeboot-context-api/src/main/kotlin/com/gewuyou/forgeboot/context/api/extension/ContextExtensions.kt new file mode 100644 index 0000000..031483b --- /dev/null +++ b/forgeboot-context/forgeboot-context-api/src/main/kotlin/com/gewuyou/forgeboot/context/api/extension/ContextExtensions.kt @@ -0,0 +1,34 @@ +package com.gewuyou.forgeboot.context.api.extension + +import com.gewuyou.forgeboot.context.api.Context + +/** + * 通过操作符重载实现 Context 的 get 方法,用于根据指定的键获取对应的值。 + * + * @param key 键,用于查找上下文中的值 + * @return 返回与键对应的值,如果不存在则返回 null + */ +operator fun Context.get(key: K): V? { + return this.retrieve(key) +} + +/** + * 通过操作符重载实现 Context 的 set 方法,用于将键值对存储到上下文中。 + * + * @param key 键,用于标识要存储的值 + * @param value 要存储的值,可以为 null + */ +operator fun Context.set(key: K, value: V?) { + this.put(key, value) +} + +/** + * 通过操作符重载实现 Context 的 get 方法,用于根据指定的键和类型获取对应的值。 + * + * @param key 键,用于查找上下文中的值 + * @param type 指定的值类型,用于类型安全地获取值 + * @return 返回与键和类型对应的值,如果不存在则返回 null + */ +operator fun Context.get(key: K, type: Class) { + this.retrieveByType(key, type) +} diff --git a/forgeboot-context/forgeboot-context-autoconfigure/build.gradle.kts b/forgeboot-context/forgeboot-context-autoconfigure/build.gradle.kts index 556613a..6f85f11 100644 --- a/forgeboot-context/forgeboot-context-autoconfigure/build.gradle.kts +++ b/forgeboot-context/forgeboot-context-autoconfigure/build.gradle.kts @@ -10,4 +10,5 @@ dependencies { compileOnly(libs.springBootStarter.web) compileOnly(libs.springBootStarter.webflux) compileOnly(libs.springCloudStarter.openfeign) + api(project(Modules.Core.SERIALIZATION)) } diff --git a/forgeboot-context/forgeboot-context-autoconfigure/src/main/kotlin/com/gewuyou/forgeboot/context/autoconfigure/ForgeContextAutoConfiguration.kt b/forgeboot-context/forgeboot-context-autoconfigure/src/main/kotlin/com/gewuyou/forgeboot/context/autoconfigure/ForgeContextAutoConfiguration.kt index 57ad333..49d1854 100644 --- a/forgeboot-context/forgeboot-context-autoconfigure/src/main/kotlin/com/gewuyou/forgeboot/context/autoconfigure/ForgeContextAutoConfiguration.kt +++ b/forgeboot-context/forgeboot-context-autoconfigure/src/main/kotlin/com/gewuyou/forgeboot/context/autoconfigure/ForgeContextAutoConfiguration.kt @@ -1,17 +1,26 @@ package com.gewuyou.forgeboot.context.autoconfigure -import com.gewuyou.forgeboot.context.api.* -import com.gewuyou.forgeboot.context.impl.* +import com.fasterxml.jackson.databind.ObjectMapper +import com.gewuyou.forgeboot.context.api.ContextFieldContributor +import com.gewuyou.forgeboot.context.api.ContextProcessor +import com.gewuyou.forgeboot.context.api.FieldRegistry +import com.gewuyou.forgeboot.context.impl.ContextHolder +import com.gewuyou.forgeboot.context.impl.DefaultFieldRegistry import com.gewuyou.forgeboot.context.impl.filter.ContextServletFilter import com.gewuyou.forgeboot.context.impl.filter.ContextWebFilter -import com.gewuyou.forgeboot.context.impl.processor.* - - +import com.gewuyou.forgeboot.context.impl.processor.GeneratorProcessor +import com.gewuyou.forgeboot.context.impl.processor.HeaderProcessor +import com.gewuyou.forgeboot.context.impl.processor.MdcProcessor +import com.gewuyou.forgeboot.context.impl.processor.ReactorProcessor +import com.gewuyou.forgeboot.core.serialization.serializer.ValueSerializer +import com.gewuyou.forgeboot.core.serialization.serializer.impl.serializer.JacksonValueSerializer +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.boot.web.reactive.function.client.WebClientCustomizer import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.boot.autoconfigure.condition.* -import org.springframework.boot.web.reactive.function.client.WebClientCustomizer import org.springframework.core.task.TaskDecorator import org.springframework.web.reactive.function.client.ClientRequest @@ -26,6 +35,17 @@ import org.springframework.web.reactive.function.client.ClientRequest */ @Configuration class ForgeContextAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + fun valueSerializer(objectMapper: ObjectMapper): ValueSerializer{ + return JacksonValueSerializer(objectMapper) + } + @Bean + @ConditionalOnMissingBean + fun contextHolder(valueSerializer: ValueSerializer): ContextHolder { + return ContextHolder(valueSerializer) + } /* ─────────────────────────────────────────────────────────────── 0️⃣ 通用 Bean:不依赖 Web / Feign / Reactor 等外部包 ─────────────────────────────────────────────────────────────── */ @@ -171,13 +191,14 @@ class ForgeContextAutoConfiguration { * 拦截器会在每次 Feign 请求发起前,将当前上下文字段写入 HTTP 请求头。 * * @param registry 字段注册表 + * @param contextHolder 上下文持有者 * @return 构建完成的 feign.RequestInterceptor 实例 */ @Bean @ConditionalOnMissingBean - fun feignInterceptor(registry: FieldRegistry) = + fun feignInterceptor(registry: FieldRegistry,contextHolder: ContextHolder) = feign.RequestInterceptor { tpl -> - val ctx = StringContextHolder.snapshot() + val ctx = contextHolder.snapshot() registry.all().forEach { def -> ctx[def.key]?.let { tpl.header(def.header, it) } } @@ -200,20 +221,21 @@ class ForgeContextAutoConfiguration { * 通过装饰线程池任务,确保异步任务继承调用线程的上下文状态。 * * @param processors 所有处理器列表 + * @param contextHolder 上下文持有者 * @return 构建完成的 TaskDecorator 实例 */ @Bean - fun contextTaskDecorator(processors: List) = + fun contextTaskDecorator(processors: List,contextHolder: ContextHolder) = TaskDecorator { delegate -> - val snap = StringContextHolder.snapshot() + val snap = contextHolder.snapshot() Runnable { try { - snap.forEach(StringContextHolder::put) + snap.forEach(contextHolder::put) processors.forEach { it.inject(Unit, snap.toMutableMap()) } delegate.run() } finally { processors.forEach { it.inject(Unit, mutableMapOf()) } - StringContextHolder.clear() + contextHolder.clear() } } } @@ -234,13 +256,13 @@ class ForgeContextAutoConfiguration { * 注册 WebClientCustomizer,用于定制 WebClient 的请求行为。 * * 在每次请求发出前,将当前上下文字段写入 HTTP 请求头。 - * + *@param contextHolder 上下文持有者 * @return 构建完成的 WebClientCustomizer 实例 */ @Bean - fun contextWebClientCustomizer() = WebClientCustomizer { builder -> + fun contextWebClientCustomizer(contextHolder: ContextHolder) = WebClientCustomizer { builder -> builder.filter { req, next -> - val ctx = StringContextHolder.snapshot() + val ctx = contextHolder.snapshot() val mutated = ClientRequest.from(req).apply { registry.all().forEach { def -> ctx[def.key]?.let { value -> header(def.header, value) } diff --git a/forgeboot-context/forgeboot-context-impl/build.gradle.kts b/forgeboot-context/forgeboot-context-impl/build.gradle.kts index 4d14916..2b189da 100644 --- a/forgeboot-context/forgeboot-context-impl/build.gradle.kts +++ b/forgeboot-context/forgeboot-context-impl/build.gradle.kts @@ -1,8 +1,9 @@ - dependencies { compileOnly(project(Modules.Context.API)) + compileOnly(project(Modules.Core.SERIALIZATION)) compileOnly(libs.springBootStarter.web) compileOnly(libs.springBootStarter.webflux) implementation(platform(libs.springBootDependencies.bom)) implementation(libs.springBoot.autoconfigure) + implementation(libs.jackson.databind) } 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 new file mode 100644 index 0000000..1373d55 --- /dev/null +++ b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/ContextHolder.kt @@ -0,0 +1,36 @@ +package com.gewuyou.forgeboot.context.impl + +import com.gewuyou.forgeboot.context.api.AbstractContext +import com.gewuyou.forgeboot.core.serialization.serializer.ValueSerializer + +/** + * 上下文容器,用于存储和获取键值对形式的字符串数据。 + * + * @since 2025-06-04 15:05:43 + * @author gewuyou + */ +open class ContextHolder( + private val valueSerializer: ValueSerializer +) : AbstractContext() { + + /** + * 存储键值对到上下文中。如果值为 null,则不会存储。 + * + * @param key 要存储的键 + * @param value 要存储的值,可以为 null + */ + open fun put(key: String, value: Any?) { + super.put(key, value?.let { valueSerializer.serialize(it) }) + } + + /** + * 根据指定的键和类型从上下文中获取对应的值。 + * + * @param key 要查找的键 + * @param type 要转换的目标类型 + * @return 对应类型的值,如果不存在或类型不匹配则返回 null + */ + override fun retrieveByType(key: String, type: Class): T? { + return retrieve(key)?.let { valueSerializer.deserialize(it, type) } + } +} \ No newline at end of file diff --git a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/StringContextHolder.kt b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/StringContextHolder.kt deleted file mode 100644 index 1b882d0..0000000 --- a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/StringContextHolder.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.gewuyou.forgeboot.context.impl - -import com.gewuyou.forgeboot.context.api.AbstractContext - -/** - *字符串上下文容器 - * - * @since 2025-06-04 15:05:43 - * @author gewuyou - */ -object StringContextHolder: AbstractContext() \ No newline at end of file diff --git a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/extension/ContextExtensions.kt b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/extension/ContextExtensions.kt new file mode 100644 index 0000000..cba5748 --- /dev/null +++ b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/extension/ContextExtensions.kt @@ -0,0 +1,34 @@ +package com.gewuyou.forgeboot.context.impl.extension + +import com.gewuyou.forgeboot.context.api.Context + +/** + * 通过操作符重载实现 Context 的 get 方法,用于根据指定的键获取对应的值。 + * + * @param key 键,用于查找上下文中的值 + * @return 返回与键对应的值,如果不存在则返回 null + */ +operator fun Context.get(key: K): V? { + return this.retrieve(key) +} + +/** + * 通过操作符重载实现 Context 的 set 方法,用于将键值对存储到上下文中。 + * + * @param key 键,用于标识要存储的值 + * @param value 要存储的值,可以为 null + */ +operator fun Context.set(key: K, value: V?) { + this.put(key, value) +} + +/** + * 通过操作符重载实现 Context 的 get 方法,用于根据指定的键和类型获取对应的值。 + * + * @param key 键,用于查找上下文中的值 + * @param type 指定的值类型,用于类型安全地获取值 + * @return 返回与键和类型对应的值,如果不存在则返回 null + */ +operator fun Context.get(key: K, type: Class) { + this.retrieveByType(key, type) +} diff --git a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/extension/ContextHolderExtensions.kt b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/extension/ContextHolderExtensions.kt new file mode 100644 index 0000000..5e2a4c0 --- /dev/null +++ b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/extension/ContextHolderExtensions.kt @@ -0,0 +1,13 @@ +package com.gewuyou.forgeboot.context.impl.extension + +import com.gewuyou.forgeboot.context.impl.ContextHolder + +/** + * 为ContextHolder实现赋值操作符的重写方法 + * + * @param key 存储值的键,用于后续从上下文获取值时使用 + * @param value 需要存储在ContextHolder中的值,允许为null + */ +operator fun ContextHolder.set(key: String, value: Any?) { + this.put(key, value) +} \ No newline at end of file diff --git a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/filter/ContextServletFilter.kt b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/filter/ContextServletFilter.kt index da7e21a..4aaee34 100644 --- a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/filter/ContextServletFilter.kt +++ b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/filter/ContextServletFilter.kt @@ -1,7 +1,7 @@ package com.gewuyou.forgeboot.context.impl.filter import com.gewuyou.forgeboot.context.api.ContextProcessor -import com.gewuyou.forgeboot.context.impl.StringContextHolder +import com.gewuyou.forgeboot.context.impl.ContextHolder import jakarta.servlet.FilterChain import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse @@ -20,6 +20,7 @@ import org.springframework.web.filter.OncePerRequestFilter */ class ContextServletFilter( private val chain: List, + private val contextHolder: ContextHolder ) : OncePerRequestFilter() { /** @@ -38,13 +39,13 @@ class ContextServletFilter( filterChain: FilterChain, ) { // 创建当前线程上下文快照的可变副本,确保后续操作不影响原始上下文 - val ctx = StringContextHolder.snapshot().toMutableMap() + val ctx = contextHolder.snapshot().toMutableMap() // 遍历上下文处理器链,依次从请求中提取上下文信息并更新临时上下文容器 chain.forEach { it.extract(request, ctx) } // 将提取后的上下文写入当前线程的上下文持有者,供后续组件访问 - ctx.forEach(StringContextHolder::put) + ctx.forEach(contextHolder::put) // 调用下一个过滤器或最终的目标处理器 chain.forEach { it.inject(request, ctx) } @@ -56,7 +57,7 @@ class ContextServletFilter( // 向处理器链注入空上下文,触发清理操作(如有) chain.forEach { it.inject(Unit, mutableMapOf()) } // 显式清除当前线程的上下文持有者,防止上下文泄露 - StringContextHolder.clear() + contextHolder.clear() } } } \ No newline at end of file diff --git a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/filter/ContextWebFilter.kt b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/filter/ContextWebFilter.kt index 533dc29..2ae81bd 100644 --- a/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/filter/ContextWebFilter.kt +++ b/forgeboot-context/forgeboot-context-impl/src/main/kotlin/com/gewuyou/forgeboot/context/impl/filter/ContextWebFilter.kt @@ -1,7 +1,7 @@ package com.gewuyou.forgeboot.context.impl.filter import com.gewuyou.forgeboot.context.api.ContextProcessor -import com.gewuyou.forgeboot.context.impl.StringContextHolder +import com.gewuyou.forgeboot.context.impl.ContextHolder import com.gewuyou.forgeboot.context.impl.processor.ReactorProcessor import org.springframework.web.server.ServerWebExchange import org.springframework.web.server.WebFilter @@ -22,7 +22,8 @@ import reactor.core.publisher.Mono */ class ContextWebFilter( private val contextProcessors: List, - private val reactorProc: ReactorProcessor + private val reactorProc: ReactorProcessor, + private val contextHolder: ContextHolder ) : WebFilter { /** * 执行过滤逻辑,在请求链中插入上下文管理操作。 @@ -42,12 +43,12 @@ class ContextWebFilter( exchange: ServerWebExchange, chain: WebFilterChain, ): Mono { - // 从 StringContextHolder 快照获取当前上下文并转换为可变 Map - val ctx = StringContextHolder.snapshot().toMutableMap() + // 从 ContextHolder 快照获取当前上下文并转换为可变 Map + val ctx = contextHolder.snapshot().toMutableMap() // 遍历所有 ContextProcessor,从请求中提取上下文信息到 ctx contextProcessors.forEach { it.extract(exchange, ctx) } - // 将上下文写入 StringContextHolder(ThreadLocal) - ctx.forEach(StringContextHolder::put) + // 将上下文写入 ContextHolder(ThreadLocal) + ctx.forEach(contextHolder::put) // 使用 MdcProcessor 将上下文注入到 MDC 中 contextProcessors.forEach { it.inject(Unit, ctx) } // 构建新的 ServerWebExchange 实例 @@ -60,7 +61,7 @@ class ContextWebFilter( .doFinally { // 清理 ThreadLocal + MDC 上下文 contextProcessors.forEach { it.inject(Unit, mutableMapOf()) } - StringContextHolder.clear() + contextHolder.clear() } } } \ No newline at end of file