mirror of
https://github.moeyy.xyz/https://github.com/GeWuYou/forgeboot
synced 2025-10-27 16:36:39 +08:00
Compare commits
3 Commits
47691cd605
...
1537973803
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1537973803 | ||
|
|
6bfda9227f | ||
|
|
15709c6516 |
@ -35,8 +35,6 @@ allprojects {
|
||||
// 设置全局属性
|
||||
ext {
|
||||
set(ProjectFlags.IS_ROOT_MODULE, false)
|
||||
set(ProjectFlags.USE_SPRING_BOOT_BOM, false)
|
||||
set(ProjectFlags.USE_CONFIGURATION_PROCESSOR, false)
|
||||
}
|
||||
afterEvaluate {
|
||||
if (project.getPropertyByBoolean(ProjectFlags.IS_ROOT_MODULE)) {
|
||||
@ -53,13 +51,21 @@ allprojects {
|
||||
subprojects {
|
||||
version = rootProject.version
|
||||
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 {
|
||||
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 {
|
||||
implementation(platform(libs.springBootDependencies.bom))
|
||||
implementation(platform(libs.springCloudDependencies.bom))
|
||||
annotationProcessor(libs.springBoot.configuration.processor)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
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 {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
|
||||
|
||||
@ -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 {
|
||||
/**
|
||||
* 默认语言
|
||||
|
||||
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 {
|
||||
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 {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
implementation(libs.springBootStarter.aop)
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
extra {
|
||||
// 需要SpringBootBom
|
||||
setProperty(ProjectFlags.USE_SPRING_BOOT_BOM, true)
|
||||
}
|
||||
dependencies {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
|
||||
|
||||
@ -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" }
|
||||
kotlin-plugin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin-version" }
|
||||
|
||||
@ -43,9 +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
|
||||
//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