From 1537973803b8e5fa56be87fcb51668a556ad9373 Mon Sep 17 00:00:00 2001 From: gewuyou <1063891901@qq.com> Date: Fri, 2 May 2025 22:35:42 +0800 Subject: [PATCH] feat(trace): Add request tracking function - Added TraceProperties class to configure tracking properties - Implement RequestIdUtil tool class generation and storage request ID - Add TraceAutoConfiguration Automatic Configuration Class - Implementing RequestIdFilter and ReactiveRequestIdFilterRequestIdInterceptor interceptor support Feign clients -Filter - Add Feign to implement WebClientRequestIdFilter to support WebClient - Add RequestIdTaskDecorator Decorator to support asynchronous thread pooling - Added Request extension function to determine whether requests are skipped --- build.gradle.kts | 1 + .../i18n/config/entity/I18nProperties.java | 2 +- forgeboot-trace/.gitattributes | 3 + forgeboot-trace/.gitignore | 40 ++++++++ forgeboot-trace/build.gradle.kts | 12 +++ .../config/entities/TraceProperties.java | 54 +++++++++++ .../forgeboot/trace/util/RequestIdUtil.java | 30 ++++++ .../trace/config/TraceAutoConfiguration.kt | 91 +++++++++++++++++++ .../trace/decorator/RequestIdTaskDecorator.kt | 29 ++++++ .../forgeboot/trace/extension/Request.kt | 60 ++++++++++++ .../trace/filter/ReactiveRequestIdFilter.kt | 64 +++++++++++++ .../forgeboot/trace/filter/RequestIdFilter.kt | 63 +++++++++++++ .../trace/filter/WebClientRequestIdFilter.kt | 57 ++++++++++++ .../interceptor/FeignRequestIdInterceptor.kt | 30 ++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + gradle/libs.versions.toml | 10 +- settings.gradle.kts | 11 ++- 17 files changed, 554 insertions(+), 4 deletions(-) create mode 100644 forgeboot-trace/.gitattributes create mode 100644 forgeboot-trace/.gitignore create mode 100644 forgeboot-trace/build.gradle.kts create mode 100644 forgeboot-trace/src/main/java/com/gewuyou/forgeboot/trace/config/entities/TraceProperties.java create mode 100644 forgeboot-trace/src/main/java/com/gewuyou/forgeboot/trace/util/RequestIdUtil.java create mode 100644 forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/config/TraceAutoConfiguration.kt create mode 100644 forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/decorator/RequestIdTaskDecorator.kt create mode 100644 forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/extension/Request.kt create mode 100644 forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/filter/ReactiveRequestIdFilter.kt create mode 100644 forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/filter/RequestIdFilter.kt create mode 100644 forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/filter/WebClientRequestIdFilter.kt create mode 100644 forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/interceptor/FeignRequestIdInterceptor.kt create mode 100644 forgeboot-trace/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/build.gradle.kts b/build.gradle.kts index 2070e3c..7c177ce 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -65,6 +65,7 @@ subprojects { if (isStarterModule&&!isRootModule) { dependencies { implementation(platform(libs.springBootDependencies.bom)) + implementation(platform(libs.springCloudDependencies.bom)) annotationProcessor(libs.springBoot.configuration.processor) } } diff --git a/forgeboot-i18n/src/main/java/com/gewuyou/forgeboot/i18n/config/entity/I18nProperties.java b/forgeboot-i18n/src/main/java/com/gewuyou/forgeboot/i18n/config/entity/I18nProperties.java index 4665384..25392ed 100644 --- a/forgeboot-i18n/src/main/java/com/gewuyou/forgeboot/i18n/config/entity/I18nProperties.java +++ b/forgeboot-i18n/src/main/java/com/gewuyou/forgeboot/i18n/config/entity/I18nProperties.java @@ -8,7 +8,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * @author gewuyou * @since 2025-02-18 23:59:57 */ -@ConfigurationProperties(prefix = "base-forge.i18n") +@ConfigurationProperties(prefix = "forgeboot.i18n") public class I18nProperties { /** * 默认语言 diff --git a/forgeboot-trace/.gitattributes b/forgeboot-trace/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/forgeboot-trace/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/forgeboot-trace/.gitignore b/forgeboot-trace/.gitignore new file mode 100644 index 0000000..5a979af --- /dev/null +++ b/forgeboot-trace/.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-trace/build.gradle.kts b/forgeboot-trace/build.gradle.kts new file mode 100644 index 0000000..8706700 --- /dev/null +++ b/forgeboot-trace/build.gradle.kts @@ -0,0 +1,12 @@ +dependencies { + implementation(project(Modules.Core.EXTENSION)) + // Spring Cloud OpenFeign (Compile Only) + // https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign + compileOnly(libs.springCloudStarter.openfeign) + // Reactor Core (Compile Only) + // https://mvnrepository.com/artifact/io.projectreactor/reactor-core + compileOnly(libs.reactor.core) + + compileOnly(libs.springBootStarter.web) + compileOnly(libs.springBootStarter.webflux) +} diff --git a/forgeboot-trace/src/main/java/com/gewuyou/forgeboot/trace/config/entities/TraceProperties.java b/forgeboot-trace/src/main/java/com/gewuyou/forgeboot/trace/config/entities/TraceProperties.java new file mode 100644 index 0000000..fa2520d --- /dev/null +++ b/forgeboot-trace/src/main/java/com/gewuyou/forgeboot/trace/config/entities/TraceProperties.java @@ -0,0 +1,54 @@ +package com.gewuyou.forgeboot.trace.config.entities; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 跟踪属性 + * 该类用于配置和管理请求跟踪相关的属性,通过这些属性可以对请求进行唯一的标识和跟踪 + * 主要功能包括定义请求ID的HTTP头名称、请求ID在MDC中的键名称,以及忽略跟踪的URL模式 + * + * @author gewuyou + * @since 2025-05-02 20:58:45 + */ +@ConfigurationProperties(prefix = "forgeboot.trace") +public class TraceProperties { + /** + * HTTP请求头中用于传递请求ID的字段名称,默认为"X-Request-Id"。 + */ + private String requestIdHeaderName = "X-Request-Id"; + + /** + * MDC(Mapped Diagnostic Context)中用于存储请求ID的键名,默认为"requestId"。 + */ + private String requestIdMdcKey = "requestId"; + + /** + * 配置忽略日志记录的路径模式,通常用于静态资源文件, + * 默认忽略以.css、.js、.png等结尾的静态资源请求。 + */ + private String[] ignorePatten = new String[]{".*\\.(css|js|png|jpg|jpeg|gif|svg)"}; + + public String getRequestIdHeaderName() { + return requestIdHeaderName; + } + + public String[] getIgnorePatten() { + return ignorePatten; + } + + public void setIgnorePatten(String[] ignorePatten) { + this.ignorePatten = ignorePatten; + } + + public void setRequestIdHeaderName(String requestIdHeaderName) { + this.requestIdHeaderName = requestIdHeaderName; + } + + public String getRequestIdMdcKey() { + return requestIdMdcKey; + } + + public void setRequestIdMdcKey(String requestIdMdcKey) { + this.requestIdMdcKey = requestIdMdcKey; + } +} diff --git a/forgeboot-trace/src/main/java/com/gewuyou/forgeboot/trace/util/RequestIdUtil.java b/forgeboot-trace/src/main/java/com/gewuyou/forgeboot/trace/util/RequestIdUtil.java new file mode 100644 index 0000000..30fb171 --- /dev/null +++ b/forgeboot-trace/src/main/java/com/gewuyou/forgeboot/trace/util/RequestIdUtil.java @@ -0,0 +1,30 @@ +package com.gewuyou.forgeboot.trace.util; + +import java.util.UUID; + +/** + * 请求 ID Util + * 这个类需配合 RequestIdFilter 使用,用于生成请求 ID,并将其绑定到线程变量中,供后续可能需要的地方使用。 + * @author gewuyou + * @since 2025-01-02 14:27:45 + */ +public class RequestIdUtil { + private static final ThreadLocal REQUEST_ID_HOLDER = new ThreadLocal<>(); + private RequestIdUtil() { + } + public static void generateRequestId() { + REQUEST_ID_HOLDER.set(UUID.randomUUID().toString()); + } + + public static String getRequestId() { + return REQUEST_ID_HOLDER.get(); + } + + public static void setRequestId(String uuid) { + REQUEST_ID_HOLDER.set(uuid); + } + + public static void removeRequestId() { + REQUEST_ID_HOLDER.remove(); + } +} diff --git a/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/config/TraceAutoConfiguration.kt b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/config/TraceAutoConfiguration.kt new file mode 100644 index 0000000..4166e8a --- /dev/null +++ b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/config/TraceAutoConfiguration.kt @@ -0,0 +1,91 @@ +package com.gewuyou.forgeboot.trace.config + +import com.gewuyou.forgeboot.trace.config.entities.TraceProperties +import com.gewuyou.forgeboot.trace.decorator.RequestIdTaskDecorator +import com.gewuyou.forgeboot.trace.filter.ReactiveRequestIdFilter +import com.gewuyou.forgeboot.trace.filter.RequestIdFilter +import com.gewuyou.forgeboot.trace.filter.WebClientRequestIdFilter +import com.gewuyou.forgeboot.trace.interceptor.FeignRequestIdInterceptor +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean +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.context.properties.EnableConfigurationProperties +import org.springframework.cloud.openfeign.FeignAutoConfiguration +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.web.reactive.function.client.WebClient + +/** + * 跟踪自动配置 + * + * 该配置类用于自动配置请求跟踪功能,包括Spring MVC、Spring WebFlux、Feign和异步线程池的请求ID生成和传递 + * @since 2025-03-17 16:33:40 + * @author gewuyou + */ +@Configuration +@EnableConfigurationProperties(TraceProperties::class) +open class TraceAutoConfiguration { + /** + * Spring MVC 过滤器(仅当 Spring MVC 存在时生效) + * + * 该过滤器用于在Spring MVC应用中生成和传递请求ID + * @param traceProperties 跟踪配置属性 + * @return RequestIdFilter实例 + */ + @Bean + @ConditionalOnProperty(name = ["spring.main.web-application-type"], havingValue = "servlet", matchIfMissing = true) + @ConditionalOnMissingBean + open fun requestIdFilter(traceProperties: TraceProperties): RequestIdFilter = RequestIdFilter(traceProperties) + + /** + * Spring WebFlux 过滤器(仅当 Spring WebFlux 存在时生效) + * + * 该过滤器用于在Spring WebFlux应用中生成和传递请求ID + * @param traceProperties 跟踪配置属性 + * @return ReactiveRequestIdFilter实例 + */ + @Bean + @ConditionalOnProperty(name = ["spring.main.web-application-type"], havingValue = "reactive") + @ConditionalOnMissingBean + open fun reactiveRequestIdFilter(traceProperties: TraceProperties): ReactiveRequestIdFilter = + ReactiveRequestIdFilter(traceProperties) + + /** + * Feign 拦截器(仅当 Feign 存在时生效) + * + * 该拦截器用于在Feign客户端中传递请求ID + * @param traceProperties 跟踪配置属性 + * @return FeignRequestIdInterceptor实例 + */ + @Bean + @ConditionalOnClass(FeignAutoConfiguration::class) + @ConditionalOnMissingBean + open fun feignRequestIdInterceptor(traceProperties: TraceProperties): FeignRequestIdInterceptor = + FeignRequestIdInterceptor(traceProperties) + + /** + * 线程池装饰器(用于 @Async) + * + * 该装饰器用于在异步线程池中传递请求ID,确保异步执行的任务能够携带正确地请求信息 + * @param traceProperties 跟踪配置属性 + * @return RequestIdTaskDecorator实例 + */ + @Bean + @ConditionalOnMissingBean + open fun requestIdTaskDecorator(traceProperties: TraceProperties): RequestIdTaskDecorator = + RequestIdTaskDecorator(traceProperties) + + /** + * 配置 WebClient 并自动添加请求 ID 过滤器(仅在 WebClient 已存在时引入) + */ + @Bean + @ConditionalOnBean(WebClient.Builder::class) // 如果 WebClient.Builder 已存在,则添加过滤器 + open fun webClientRequestIdFilter( + webClientBuilder: WebClient.Builder, + traceProperties: TraceProperties + ): WebClient.Builder { + // 在现有 WebClient 配置中添加请求 ID 过滤器 + return webClientBuilder.filter(WebClientRequestIdFilter(traceProperties)) + } +} diff --git a/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/decorator/RequestIdTaskDecorator.kt b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/decorator/RequestIdTaskDecorator.kt new file mode 100644 index 0000000..c058999 --- /dev/null +++ b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/decorator/RequestIdTaskDecorator.kt @@ -0,0 +1,29 @@ +package com.gewuyou.forgeboot.trace.decorator + +import com.gewuyou.forgeboot.trace.config.entities.TraceProperties +import org.slf4j.MDC +import org.springframework.core.task.TaskDecorator + +/** + *请求ID任务装饰器 + * + * @since 2025-03-17 16:57:54 + * @author gewuyou + */ +class RequestIdTaskDecorator( + private val traceProperties: TraceProperties +) : TaskDecorator { + override fun decorate(task: Runnable): Runnable { + val requestIdMdcKey = traceProperties.requestIdMdcKey + // 获取主线程 requestId + val requestId = MDC.get(requestIdMdcKey) + return Runnable { + try { + MDC.put(requestIdMdcKey, requestId) + task.run() + } finally { + MDC.remove(requestIdMdcKey) + } + } + } +} \ No newline at end of file diff --git a/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/extension/Request.kt b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/extension/Request.kt new file mode 100644 index 0000000..25484a9 --- /dev/null +++ b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/extension/Request.kt @@ -0,0 +1,60 @@ +package com.gewuyou.forgeboot.trace.extension + +import jakarta.servlet.http.HttpServletRequest +import org.springframework.http.HttpMethod +import org.springframework.http.server.reactive.ServerHttpRequest +import org.springframework.web.reactive.function.client.ClientRequest + +/** + * 请求扩展 + * + * @since 2025-05-02 21:59:26 + * @author gewuyou + */ + +/** + * 判断是否跳过请求 + * @apiNote 这个方法是给反应式请求id过滤器使用的 + * @return true: 跳过请求; false: 不跳过请求 + */ +fun ServerHttpRequest.isSkipRequest(ignorePaths: Array): Boolean { + return isSkipRequest(this.method.name(), this.uri.path, ignorePaths) +} + +/** + * 判断是否跳过请求 + * @apiNote 这个方法是给请求id过滤器使用的 + * @return true: 跳过请求; false: 不跳过请求 + */ +fun HttpServletRequest.isSkipRequest(ignorePaths: Array): Boolean { + return isSkipRequest(this.method, this.requestURI, ignorePaths) +} + +/** + * 判断是否跳过请求 + * @apiNote 这个方法是给请求id过滤器使用的 + * @return true: 跳过请求; false: 不跳过请求 + */ +fun ClientRequest.isSkipRequest(ignorePaths: Array): Boolean { + return isSkipRequest(this.method().name(), this.url().path, ignorePaths) +} + +/** + * 判断是否跳过请求 + * @param method 请求方法 + * @param uri 请求路径 + * @return true: 跳过请求; false: 不跳过请求 + */ +fun isSkipRequest(method: String, uri: String, ignorePaths: Array): Boolean { + return when { + // 跳过 OPTIONS 请求 + HttpMethod.OPTIONS.name() == method -> true + // 跳过 HEAD 请求 + HttpMethod.HEAD.name() == method -> true + // 跳过 TRACE 请求 + HttpMethod.TRACE.name() == method -> true + // 跳过模式匹配 + ignorePaths.any { uri.matches(Regex(it)) } -> true + else -> false + } +} diff --git a/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/filter/ReactiveRequestIdFilter.kt b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/filter/ReactiveRequestIdFilter.kt new file mode 100644 index 0000000..67a38e4 --- /dev/null +++ b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/filter/ReactiveRequestIdFilter.kt @@ -0,0 +1,64 @@ +package com.gewuyou.forgeboot.trace.filter + + + +import com.gewuyou.forgeboot.core.extension.log +import com.gewuyou.forgeboot.trace.config.entities.TraceProperties +import com.gewuyou.forgeboot.trace.extension.isSkipRequest +import com.gewuyou.forgeboot.trace.util.RequestIdUtil +import org.slf4j.MDC +import org.springframework.web.server.ServerWebExchange +import org.springframework.web.server.WebFilter +import org.springframework.web.server.WebFilterChain +import reactor.core.publisher.Mono + +/** + * 反应式请求 ID 过滤器 + * + * 该类的主要作用是在每个请求开始时生成或获取一个唯一的请求 ID,并将其设置到日志上下文中, + * 以便在后续的日志记录中能够追踪到该请求。它还支持基于特定模式跳过某些请求的处理。 + * + * @param traceProperties 配置属性,包含请求 ID 的头名称和 MDK 关键等信息 + * @since 2025-02-09 02:14:49 + * @author gewuyou + */ +class ReactiveRequestIdFilter( + private val traceProperties: TraceProperties +) : WebFilter { + override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono { + val request = exchange.request + // 检测请求是否需要跳过 + if (request.isSkipRequest(traceProperties.ignorePatten)) { + return chain.filter(exchange) + } + // 获取请求头中请求 ID 的名称和 MDK 中的键 + val requestIdHeader = traceProperties.requestIdHeaderName + val requestIdMdcKey = traceProperties.requestIdMdcKey + // 尝试从请求头中获取 requestId,如果存在则设置到 RequestIdUtil 中,否则生成一个新的 requestId + request.headers[requestIdHeader]?.let { + it.firstOrNull()?.let(RequestIdUtil::setRequestId) ?: RequestIdUtil.generateRequestId() + } ?: RequestIdUtil.generateRequestId() + // 获取当前的 requestId + val currentRequestId = RequestIdUtil.getRequestId() + // 将 requestId 设置到日志中 + MDC.put(requestIdMdcKey, currentRequestId) + log.info("设置 Request id: $currentRequestId") + // ✅ **创建新的 request 并更新 exchange** + // 更新请求头,确保后续的请求处理中包含 requestId + val mutatedRequest = request.mutate() + .header(requestIdHeader, currentRequestId) + .build() + val mutatedExchange = exchange.mutate().request(mutatedRequest).build() + // 放行请求 + return chain.filter(mutatedExchange) + // ✅ 让 Reactor 线程也能获取 requestId + // 将 requestId 写入 Reactor 的上下文中,以便在异步处理中也能访问 + .contextWrite { ctx -> ctx.put(requestIdMdcKey, currentRequestId) } + .doFinally { + // 清理 MDC 中的 requestId,避免内存泄漏 + MDC.remove(requestIdMdcKey) + // 将 requestId 清除 + RequestIdUtil.removeRequestId() + } + } +} diff --git a/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/filter/RequestIdFilter.kt b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/filter/RequestIdFilter.kt new file mode 100644 index 0000000..4447e29 --- /dev/null +++ b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/filter/RequestIdFilter.kt @@ -0,0 +1,63 @@ +package com.gewuyou.forgeboot.trace.filter + + +import com.gewuyou.forgeboot.core.extension.log +import com.gewuyou.forgeboot.trace.config.entities.TraceProperties +import com.gewuyou.forgeboot.trace.extension.isSkipRequest +import com.gewuyou.forgeboot.trace.util.RequestIdUtil +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.slf4j.MDC +import org.springframework.web.filter.OncePerRequestFilter + +/** + * 请求过滤器 + * + * 该过滤器用于在请求处理过程中添加和管理请求ID(RequestId),以便于日志追踪和调试 + * 它基于Spring的OncePerRequestFilter,确保每个请求只被过滤一次 + * + * @param traceProperties Trace属性配置,包含请求ID的相关配置信息 + * @since 2025-01-02 14:31:07 + * @author gewuyou + */ +class RequestIdFilter( + private val traceProperties: TraceProperties +) : OncePerRequestFilter() { + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + chain: FilterChain + ) { + // 检查请求是否需要跳过 + if (request.isSkipRequest(traceProperties.ignorePatten)) { + return chain.doFilter(request, response) + } + // 获取请求头中 requestId 的名称和 MDC 中的键 + val requestIdHeader = traceProperties.requestIdHeaderName + val requestIdMdcKey = traceProperties.requestIdMdcKey + try { + // 尝试从请求头中获取 requestId + request.getHeader(requestIdHeader)?.also( + RequestIdUtil::setRequestId + ) ?: run { + // 如果没有,则生成新的 requestId + RequestIdUtil.generateRequestId() + } + // 获取 requestId + val requestId = RequestIdUtil.getRequestId() + // 将requestId 设置到日志中 + MDC.put(requestIdMdcKey, requestId) + log.info("设置 Request id: $requestId") + // 将 requestId 设置到响应头中 + response.setHeader(requestIdHeader, requestId) + // 继续处理请求 + chain.doFilter(request, response) + } finally { + // 移除 MDC 中的 requestId + MDC.remove(requestIdMdcKey) + // 清理当前线程的 RequestId,防止内存泄漏 + RequestIdUtil.removeRequestId() + } + } +} \ No newline at end of file diff --git a/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/filter/WebClientRequestIdFilter.kt b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/filter/WebClientRequestIdFilter.kt new file mode 100644 index 0000000..76656c7 --- /dev/null +++ b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/filter/WebClientRequestIdFilter.kt @@ -0,0 +1,57 @@ +package com.gewuyou.forgeboot.trace.filter + +import com.gewuyou.forgeboot.core.extension.log +import com.gewuyou.forgeboot.trace.config.entities.TraceProperties +import com.gewuyou.forgeboot.trace.extension.isSkipRequest +import com.gewuyou.forgeboot.trace.util.RequestIdUtil +import org.slf4j.MDC +import org.springframework.web.reactive.function.client.ClientRequest +import org.springframework.web.reactive.function.client.ClientResponse +import org.springframework.web.reactive.function.client.ExchangeFilterFunction +import org.springframework.web.reactive.function.client.ExchangeFunction +import reactor.core.publisher.Mono + +/** + * Web客户端请求ID过滤器 + * + * 该类的主要作用是在发出HTTP请求时,确保请求ID的正确传递和记录 + * 如果请求被忽略,则直接传递不做处理;否则,会尝试从请求头中获取请求ID, + * 如果获取不到则生成新的请求ID,并将其设置到请求头中以及日志中,以便于追踪请求 + * + * @param traceProperties 追踪属性配置,包括忽略模式、请求ID头名称和MDK中的键 + * @since 2025-05-02 22:18:06 + * @author gewuyou + */ +class WebClientRequestIdFilter( + private val traceProperties: TraceProperties +) : ExchangeFilterFunction { + override fun filter(request: ClientRequest, next: ExchangeFunction): Mono { + // 检查请求是否被忽略,如果被忽略,则直接执行请求 + if (request.isSkipRequest(traceProperties.ignorePatten)) { + return next.exchange(request) + } + // 获取请求头中请求 ID 的名称和 MDK 中的键 + val requestIdHeader = traceProperties.requestIdHeaderName + val requestIdMdcKey = traceProperties.requestIdMdcKey + // 尝试从请求头中获取 requestId,如果存在则设置到 RequestIdUtil 中,否则生成一个新的 requestId + request.headers()[requestIdHeader]?.let { + it.firstOrNull()?.let(RequestIdUtil::setRequestId) ?: RequestIdUtil.generateRequestId() + } ?: RequestIdUtil.generateRequestId() + // 获取当前的 requestId + val currentRequestId = RequestIdUtil.getRequestId() + // 将 requestId 设置到日志中 + MDC.put(requestIdMdcKey, currentRequestId) + log.info("设置 Request id: $currentRequestId") + // 创建一个新的请求,包含 requestId 头 + val mutatedRequest = ClientRequest.from(request) + .header(requestIdHeader, currentRequestId) + .build() + // 执行请求,并在请求完成后清除 MDC 和 RequestIdUtil 中的 requestId + return next.exchange(mutatedRequest) + .doFinally { + MDC.remove(requestIdMdcKey) + RequestIdUtil.removeRequestId() + } + } + +} diff --git a/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/interceptor/FeignRequestIdInterceptor.kt b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/interceptor/FeignRequestIdInterceptor.kt new file mode 100644 index 0000000..20a62f9 --- /dev/null +++ b/forgeboot-trace/src/main/kotlin/com/gewuyou/forgeboot/trace/interceptor/FeignRequestIdInterceptor.kt @@ -0,0 +1,30 @@ +package com.gewuyou.forgeboot.trace.interceptor + + + +import com.gewuyou.forgeboot.core.extension.log +import com.gewuyou.forgeboot.trace.config.entities.TraceProperties +import com.gewuyou.forgeboot.trace.util.RequestIdUtil +import feign.RequestInterceptor +import feign.RequestTemplate + +/** + * Feign请求ID 拦截器 + * + * @since 2025-03-17 16:42:50 + * @author gewuyou + */ +class FeignRequestIdInterceptor( + private val traceProperties: TraceProperties +) : RequestInterceptor { + override fun apply(template: RequestTemplate) { + // 尝试获取当前请求的请求id + val requestId = RequestIdUtil.getRequestId() + requestId?.let { + // 如果请求id存在,则添加到请求头中 + template.header(traceProperties.requestIdHeaderName, requestId) + } ?: run { + log.warn("请求ID为null,请检查您是否已在过滤链中添加了请求filter。") + } + } +} \ No newline at end of file diff --git a/forgeboot-trace/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/forgeboot-trace/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..84e7809 --- /dev/null +++ b/forgeboot-trace/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.gewuyou.forgeboot.trace.config.TraceAutoConfiguration \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1a1ea46..98c6dab 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ kotlinxDatetime-version = "0.6.1" kotlinxSerializationJSON-version = "1.7.3" #kotlinxCoroutines-version = "1.9.0" axion-release-version = "1.18.7" +spring-cloud-version = "2024.0.1" spring-boot-version = "3.4.4" slf4j-version = "2.0.17" @@ -22,11 +23,16 @@ kotlinxCoroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-co kotlinxCoroutines-reactor = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-reactor" } slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-version" } +springBootDependencies-bom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "spring-boot-version" } springBootStarter-aop = { group = "org.springframework.boot", name = "spring-boot-starter-aop" } springBootStarter-web = { group = "org.springframework.boot", name = "spring-boot-starter-web" } springBootStarter-webflux = { group = "org.springframework.boot", name = "spring-boot-starter-webflux" } -springBootDependencies-bom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "spring-boot-version" } springBoot-configuration-processor = { group = "org.springframework.boot", name = "spring-boot-configuration-processor", version.ref = "spring-boot-version" } + +springCloudDependencies-bom = { module = "org.springframework.cloud:spring-cloud-dependencies", version.ref = "spring-cloud-version" } +springCloudStarter-openfeign = { group = "org.springframework.cloud", name = "spring-cloud-starter-openfeign" } + +reactor-core={group="io.projectreactor", name="reactor-core"} # Libraries can be bundled together for easier import [bundles] kotlinxEcosystem = ["kotlinxDatetime", "kotlinxSerialization", "kotlinxCoroutines-core"] @@ -43,4 +49,4 @@ maven-publish = { id = "maven-publish" } # 引入 Kotlin 支持 kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } # 支持 Spring 的 Kotlin 插件 -kotlin-plugin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin-version" } \ No newline at end of file +kotlin-plugin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin-version" } diff --git a/settings.gradle.kts b/settings.gradle.kts index aebe6f9..241816a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -43,14 +43,23 @@ project(":forgeboot-core").name = "forgeboot-core" project(":forgeboot-core:forgeboot-core-extension").name = "forgeboot-core-extension" //endregion -//region i18n +//region module i18n include( "forgeboot-i18n" ) project(":forgeboot-i18n").name = "forgeboot-i18n-spring-boot-starter" //endregion +//region module webflux include( "forgeboot-webflux", ) project(":forgeboot-webflux").name = "forgeboot-webflux-spring-boot-starter" +//endregion + +//region module trace +include( + "forgeboot-trace" +) +project(":forgeboot-trace").name = "forgeboot-trace-spring-boot-starter" +//endregion