mirror of
				https://github.moeyy.xyz/https://github.com/GeWuYou/forgeboot
				synced 2025-10-31 17:34:24 +08:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			47691cd605
			...
			1537973803
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 1537973803 | ||
|   | 6bfda9227f | ||
|   | 15709c6516 | 
| @ -35,8 +35,6 @@ allprojects { | |||||||
|     // 设置全局属性 |     // 设置全局属性 | ||||||
|     ext { |     ext { | ||||||
|         set(ProjectFlags.IS_ROOT_MODULE, false) |         set(ProjectFlags.IS_ROOT_MODULE, false) | ||||||
|         set(ProjectFlags.USE_SPRING_BOOT_BOM, false) |  | ||||||
|         set(ProjectFlags.USE_CONFIGURATION_PROCESSOR, false) |  | ||||||
|     } |     } | ||||||
|     afterEvaluate { |     afterEvaluate { | ||||||
|         if (project.getPropertyByBoolean(ProjectFlags.IS_ROOT_MODULE)) { |         if (project.getPropertyByBoolean(ProjectFlags.IS_ROOT_MODULE)) { | ||||||
| @ -53,13 +51,21 @@ allprojects { | |||||||
| subprojects { | subprojects { | ||||||
|     version = rootProject.version |     version = rootProject.version | ||||||
|     afterEvaluate { |     afterEvaluate { | ||||||
|         if (project.getPropertyByBoolean(ProjectFlags.USE_SPRING_BOOT_BOM)) { |         val isRootModule = project.getPropertyByBoolean(ProjectFlags.IS_ROOT_MODULE) | ||||||
|  |         val isStarterModule = project.name.contains("starter") | ||||||
|  |         if (isRootModule) { | ||||||
|             dependencies { |             dependencies { | ||||||
|                 implementation(platform(libs.springBootDependencies.bom)) |                 project.subprojects.forEach { | ||||||
|  |                     if (!it.getPropertyByBoolean(ProjectFlags.IS_ROOT_MODULE)) { | ||||||
|  |                         project.dependencies.add("api", project(it.path)) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if(project.getPropertyByBoolean(ProjectFlags.USE_CONFIGURATION_PROCESSOR)){ |         if (isStarterModule&&!isRootModule) { | ||||||
|             dependencies { |             dependencies { | ||||||
|  |                 implementation(platform(libs.springBootDependencies.bom)) | ||||||
|  |                 implementation(platform(libs.springCloudDependencies.bom)) | ||||||
|                 annotationProcessor(libs.springBoot.configuration.processor) |                 annotationProcessor(libs.springBoot.configuration.processor) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,3 +1,3 @@ | |||||||
| dependencies { | dependencies { | ||||||
|     api(project(Modules.Core.EXTENSION)) | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,8 +1,3 @@ | |||||||
| extra { |  | ||||||
|     // 需要SpringBootBom |  | ||||||
|     setProperty(ProjectFlags.USE_SPRING_BOOT_BOM, true) |  | ||||||
|     setProperty(ProjectFlags.USE_CONFIGURATION_PROCESSOR, true) |  | ||||||
| } |  | ||||||
| dependencies { | dependencies { | ||||||
|     implementation(project(Modules.Core.EXTENSION)) |     implementation(project(Modules.Core.EXTENSION)) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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 | ||||||
							
								
								
									
										3
									
								
								forgeboot-webflux/.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								forgeboot-webflux/.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | /gradlew text eol=lf | ||||||
|  | *.bat text eol=crlf | ||||||
|  | *.jar binary | ||||||
							
								
								
									
										40
									
								
								forgeboot-webflux/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								forgeboot-webflux/.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 | ||||||
							
								
								
									
										8
									
								
								forgeboot-webflux/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								forgeboot-webflux/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | extra { | ||||||
|  |     // 标记为根项目 | ||||||
|  |     setProperty(ProjectFlags.IS_ROOT_MODULE, true) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | dependencies { | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -4,6 +4,5 @@ extra { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| dependencies { | dependencies { | ||||||
|     api(project(Modules.Webmvc.VERSION_STARTER)) | 
 | ||||||
|     api(project(Modules.Webmvc.LOGGER_STARTER)) |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,7 +1,3 @@ | |||||||
| extra { |  | ||||||
|     // 需要SpringBootBom |  | ||||||
|     setProperty(ProjectFlags.USE_SPRING_BOOT_BOM, true) |  | ||||||
| } |  | ||||||
| dependencies { | dependencies { | ||||||
|     implementation(project(Modules.Core.EXTENSION)) |     implementation(project(Modules.Core.EXTENSION)) | ||||||
|     implementation(libs.springBootStarter.aop) |     implementation(libs.springBootStarter.aop) | ||||||
|  | |||||||
| @ -1,7 +1,3 @@ | |||||||
| extra { |  | ||||||
|     // 需要SpringBootBom |  | ||||||
|     setProperty(ProjectFlags.USE_SPRING_BOOT_BOM, true) |  | ||||||
| } |  | ||||||
| dependencies { | dependencies { | ||||||
|     implementation(project(Modules.Core.EXTENSION)) |     implementation(project(Modules.Core.EXTENSION)) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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,9 +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( | ||||||
|  |     "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 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user