mirror of
https://github.moeyy.xyz/https://github.com/GeWuYou/forgeboot
synced 2025-10-27 23:22:09 +08:00
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
This commit is contained in:
parent
6bfda9227f
commit
1537973803
@ -65,6 +65,7 @@ subprojects {
|
|||||||
if (isStarterModule&&!isRootModule) {
|
if (isStarterModule&&!isRootModule) {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(platform(libs.springBootDependencies.bom))
|
implementation(platform(libs.springBootDependencies.bom))
|
||||||
|
implementation(platform(libs.springCloudDependencies.bom))
|
||||||
annotationProcessor(libs.springBoot.configuration.processor)
|
annotationProcessor(libs.springBoot.configuration.processor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||||||
* @author gewuyou
|
* @author gewuyou
|
||||||
* @since 2025-02-18 23:59:57
|
* @since 2025-02-18 23:59:57
|
||||||
*/
|
*/
|
||||||
@ConfigurationProperties(prefix = "base-forge.i18n")
|
@ConfigurationProperties(prefix = "forgeboot.i18n")
|
||||||
public class I18nProperties {
|
public class I18nProperties {
|
||||||
/**
|
/**
|
||||||
* 默认语言
|
* 默认语言
|
||||||
|
|||||||
3
forgeboot-trace/.gitattributes
vendored
Normal file
3
forgeboot-trace/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/gradlew text eol=lf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.jar binary
|
||||||
40
forgeboot-trace/.gitignore
vendored
Normal file
40
forgeboot-trace/.gitignore
vendored
Normal file
@ -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
|
||||||
12
forgeboot-trace/build.gradle.kts
Normal file
12
forgeboot-trace/build.gradle.kts
Normal file
@ -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)
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<String> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<String>): Boolean {
|
||||||
|
return isSkipRequest(this.method.name(), this.uri.path, ignorePaths)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否跳过请求
|
||||||
|
* @apiNote 这个方法是给请求id过滤器使用的
|
||||||
|
* @return true: 跳过请求; false: 不跳过请求
|
||||||
|
*/
|
||||||
|
fun HttpServletRequest.isSkipRequest(ignorePaths: Array<String>): Boolean {
|
||||||
|
return isSkipRequest(this.method, this.requestURI, ignorePaths)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否跳过请求
|
||||||
|
* @apiNote 这个方法是给请求id过滤器使用的
|
||||||
|
* @return true: 跳过请求; false: 不跳过请求
|
||||||
|
*/
|
||||||
|
fun ClientRequest.isSkipRequest(ignorePaths: Array<String>): 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<String>): 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<Void> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<ClientResponse> {
|
||||||
|
// 检查请求是否被忽略,如果被忽略,则直接执行请求
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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。")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
com.gewuyou.forgeboot.trace.config.TraceAutoConfiguration
|
||||||
@ -8,6 +8,7 @@ kotlinxDatetime-version = "0.6.1"
|
|||||||
kotlinxSerializationJSON-version = "1.7.3"
|
kotlinxSerializationJSON-version = "1.7.3"
|
||||||
#kotlinxCoroutines-version = "1.9.0"
|
#kotlinxCoroutines-version = "1.9.0"
|
||||||
axion-release-version = "1.18.7"
|
axion-release-version = "1.18.7"
|
||||||
|
spring-cloud-version = "2024.0.1"
|
||||||
spring-boot-version = "3.4.4"
|
spring-boot-version = "3.4.4"
|
||||||
|
|
||||||
slf4j-version = "2.0.17"
|
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" }
|
kotlinxCoroutines-reactor = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-reactor" }
|
||||||
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-version" }
|
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-aop = { group = "org.springframework.boot", name = "spring-boot-starter-aop" }
|
||||||
springBootStarter-web = { group = "org.springframework.boot", name = "spring-boot-starter-web" }
|
springBootStarter-web = { group = "org.springframework.boot", name = "spring-boot-starter-web" }
|
||||||
springBootStarter-webflux = { group = "org.springframework.boot", name = "spring-boot-starter-webflux" }
|
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" }
|
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
|
# Libraries can be bundled together for easier import
|
||||||
[bundles]
|
[bundles]
|
||||||
kotlinxEcosystem = ["kotlinxDatetime", "kotlinxSerialization", "kotlinxCoroutines-core"]
|
kotlinxEcosystem = ["kotlinxDatetime", "kotlinxSerialization", "kotlinxCoroutines-core"]
|
||||||
|
|||||||
@ -43,14 +43,23 @@ project(":forgeboot-core").name = "forgeboot-core"
|
|||||||
project(":forgeboot-core:forgeboot-core-extension").name = "forgeboot-core-extension"
|
project(":forgeboot-core:forgeboot-core-extension").name = "forgeboot-core-extension"
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
//region i18n
|
//region module i18n
|
||||||
include(
|
include(
|
||||||
"forgeboot-i18n"
|
"forgeboot-i18n"
|
||||||
)
|
)
|
||||||
project(":forgeboot-i18n").name = "forgeboot-i18n-spring-boot-starter"
|
project(":forgeboot-i18n").name = "forgeboot-i18n-spring-boot-starter"
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
//region module webflux
|
||||||
include(
|
include(
|
||||||
"forgeboot-webflux",
|
"forgeboot-webflux",
|
||||||
)
|
)
|
||||||
project(":forgeboot-webflux").name = "forgeboot-webflux-spring-boot-starter"
|
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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user