mirror of
https://github.moeyy.xyz/https://github.com/GeWuYou/forgeboot
synced 2025-10-27 19:24:23 +08:00
Compare commits
13 Commits
446b859a8a
...
8a449467ab
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a449467ab | |||
| 96469ac1e3 | |||
| 05a8d3d409 | |||
| 2258f26544 | |||
| c3f4b6973c | |||
| 6c30120016 | |||
| 806cefd248 | |||
| 6942de1320 | |||
| 69c59aeba5 | |||
| 37309f5b15 | |||
| 0e74483168 | |||
| b90048a57d | |||
| ebe801f6bb |
@ -7,6 +7,12 @@
|
||||
*/
|
||||
object Modules {
|
||||
|
||||
object Context{
|
||||
const val STARTER = ":forgeboot-context-spring-boot-starter"
|
||||
const val API = ":forgeboot-context-spring-boot-starter:forgeboot-context-api"
|
||||
const val IMPL = ":forgeboot-context-spring-boot-starter:forgeboot-context-impl"
|
||||
const val AUTOCONFIGURE = ":forgeboot-context-spring-boot-starter:forgeboot-context-autoconfigure"
|
||||
}
|
||||
object Webmvc {
|
||||
const val STARTER = ":forgeboot-webmvc-spring-boot-starter"
|
||||
const val DTO = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-dto"
|
||||
|
||||
3
forgeboot-context/forgeboot-context-api/.gitattributes
vendored
Normal file
3
forgeboot-context/forgeboot-context-api/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/gradlew text eol=lf
|
||||
*.bat text eol=crlf
|
||||
*.jar binary
|
||||
40
forgeboot-context/forgeboot-context-api/.gitignore
vendored
Normal file
40
forgeboot-context/forgeboot-context-api/.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
|
||||
4
forgeboot-context/forgeboot-context-api/build.gradle.kts
Normal file
4
forgeboot-context/forgeboot-context-api/build.gradle.kts
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
dependencies {
|
||||
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package com.gewuyou.forgeboot.context.api
|
||||
|
||||
/**
|
||||
*抽象上下文
|
||||
*
|
||||
* @since 2025-06-04 13:36:54
|
||||
* @author gewuyou
|
||||
*/
|
||||
abstract class AbstractContext<K, V>: Context<K, V> {
|
||||
private val local= ThreadLocal.withInitial { mutableMapOf<K,V>() }
|
||||
/**
|
||||
* 将指定的键值对存入上下文中。
|
||||
*
|
||||
* @param key 要存储的键
|
||||
* @param value 要存储的值,可以为 null
|
||||
*/
|
||||
override fun put(key: K, value: V?) {
|
||||
value?.let {
|
||||
local.get()[key] = it
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定的键从上下文中获取对应的值。
|
||||
*
|
||||
* @param key 要查找的键
|
||||
* @return 对应的值,如果不存在则返回 null
|
||||
*/
|
||||
override fun get(key: K): V? {
|
||||
return local.get()[key]
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前上下文的一个快照,包含所有键值对。
|
||||
*
|
||||
* @return 一个 Map,表示当前上下文中的所有键值对
|
||||
*/
|
||||
override fun snapshot(): Map<K, V> {
|
||||
return HashMap(local.get())
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除上下文中的所有键值对。
|
||||
*/
|
||||
override fun clear() {
|
||||
local.remove()
|
||||
}
|
||||
|
||||
/**
|
||||
* 从上下文中移除指定的键值对。
|
||||
*
|
||||
* @param key 要移除的键
|
||||
*/
|
||||
override fun remove(key: K) {
|
||||
local.get().remove(key)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package com.gewuyou.forgeboot.context.api
|
||||
|
||||
/**
|
||||
* 上下文接口,用于管理键值对的存储、获取和清理操作。
|
||||
*
|
||||
* @since 2025-06-04 13:34:04
|
||||
* @author gewuyou
|
||||
*/
|
||||
interface Context<K, V> {
|
||||
/**
|
||||
* 将指定的键值对存入上下文中。
|
||||
*
|
||||
* @param key 要存储的键
|
||||
* @param value 要存储的值,可以为 null
|
||||
*/
|
||||
fun put(key: K, value: V?)
|
||||
|
||||
/**
|
||||
* 根据指定的键从上下文中获取对应的值。
|
||||
*
|
||||
* @param key 要查找的键
|
||||
* @return 对应的值,如果不存在则返回 null
|
||||
*/
|
||||
fun get(key: K): V?
|
||||
|
||||
/**
|
||||
* 获取当前上下文的一个快照,包含所有键值对。
|
||||
*
|
||||
* @return 一个 Map,表示当前上下文中的所有键值对
|
||||
*/
|
||||
fun snapshot(): Map<K, V?>
|
||||
|
||||
/**
|
||||
* 清除上下文中的所有键值对。
|
||||
*/
|
||||
fun clear()
|
||||
|
||||
/**
|
||||
* 从上下文中移除指定的键值对。
|
||||
*
|
||||
* @param key 要移除的键
|
||||
*/
|
||||
fun remove(key: K)
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.gewuyou.forgeboot.context.api
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.entities.FieldDef
|
||||
|
||||
/**
|
||||
* 上下文字段贡献者接口,用于定义上下文所需字段的契约。
|
||||
*
|
||||
* 实现此接口的类应提供一组上下文字段定义([FieldDef]),用于描述当前上下文的数据结构。
|
||||
*
|
||||
* @since 2025-06-04 13:32:39
|
||||
* @author gewuyou
|
||||
*/
|
||||
fun interface ContextFieldContributor {
|
||||
/**
|
||||
* 提供上下文字段定义集合。
|
||||
*
|
||||
* @return 返回一个不可变的[Set]集合,包含当前上下文所需的所有字段定义对象[FieldDef]。
|
||||
*/
|
||||
fun fields(): Set<FieldDef>
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package com.gewuyou.forgeboot.context.api
|
||||
|
||||
/**
|
||||
* 上下文处理器
|
||||
*
|
||||
* 用于定义上下文的处理逻辑,包括顺序控制、上下文提取与注入。
|
||||
*
|
||||
* @since 2025-06-04 15:07:13
|
||||
* @author gewuyou
|
||||
*/
|
||||
interface ContextProcessor : Comparable<ContextProcessor> {
|
||||
|
||||
/**
|
||||
* 获取当前处理器的执行顺序优先级。
|
||||
*
|
||||
* 默认实现返回0,数值越小优先级越高。
|
||||
*
|
||||
* @return Int 表示当前处理器的顺序值
|
||||
*/
|
||||
fun order(): Int = 0
|
||||
|
||||
/**
|
||||
* 从给定的载体中提取上下文信息,并填充到上下文对象中。
|
||||
*
|
||||
* 默认实现为空方法,子类可根据需要重写此方法。
|
||||
*
|
||||
* @param carrier 载体对象,通常包含上下文数据
|
||||
* @param ctx 可变映射,用于存储提取出的上下文键值对
|
||||
*/
|
||||
fun extract(carrier: Any, ctx: MutableMap<String, String>) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* 将上下文信息注入到给定的载体中。
|
||||
*
|
||||
* 默认实现为空方法,子类可根据需要重写此方法。
|
||||
*
|
||||
* @param carrier 载体对象,将上下文数据注入其中
|
||||
* @param ctx 包含上下文键值对的映射
|
||||
*/
|
||||
fun inject(carrier: Any, ctx: MutableMap<String, String>) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据处理器的执行顺序进行比较,实现Comparable接口的方法。
|
||||
*
|
||||
* @param other 待比较的另一个上下文处理器
|
||||
* @return Int 当前对象与other对象的顺序差值,用于排序
|
||||
*/
|
||||
override fun compareTo(other: ContextProcessor) = order() - other.order()
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.gewuyou.forgeboot.context.api
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.entities.FieldDef
|
||||
|
||||
/**
|
||||
* 字段注册表接口
|
||||
*
|
||||
* 该接口定义了字段注册表的基本操作,包括获取所有字段定义和通过字段头查询字段定义。
|
||||
*
|
||||
* @since 2025-06-04 14:44:40
|
||||
* @author gewuyou
|
||||
*/
|
||||
interface FieldRegistry {
|
||||
|
||||
/**
|
||||
* 获取所有字段定义的集合
|
||||
*
|
||||
* @return 返回包含所有字段定义的集合
|
||||
*/
|
||||
fun all(): Collection<FieldDef>
|
||||
|
||||
/**
|
||||
* 根据字段头查找对应的字段定义
|
||||
*
|
||||
* @param header 字段头信息
|
||||
* @return 如果找到匹配的字段定义则返回,否则返回 null
|
||||
*/
|
||||
fun findByHeader(header: String): FieldDef?
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.gewuyou.forgeboot.context.api.entities
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.enums.Scope
|
||||
|
||||
/**
|
||||
* 字段定义,用于描述数据结构中的字段属性。
|
||||
*
|
||||
* @property header 字段的显示名称,通常用于界面展示。
|
||||
* @property key 字段的唯一标识符,用于数据映射和识别。
|
||||
* @property generator 生成字段值的函数,默认为 null,表示不使用动态生成。
|
||||
* @property scopes 定义该字段适用的上下文范围,默认包括 HEADER 和 MDC。
|
||||
*
|
||||
* @since 2025-06-04 13:31:32
|
||||
* @author gewuyou
|
||||
*/
|
||||
data class FieldDef(
|
||||
val header: String,
|
||||
val key: String,
|
||||
val generator: (() -> String)? = null,
|
||||
val scopes: Set<Scope> = setOf(Scope.HEADER, Scope.MDC)
|
||||
)
|
||||
@ -0,0 +1,29 @@
|
||||
package com.gewuyou.forgeboot.context.api.enums
|
||||
|
||||
/**
|
||||
* 范围枚举类,定义了不同上下文信息的存储位置
|
||||
*
|
||||
* @since 2025-06-04 13:29:57
|
||||
* @author gewuyou
|
||||
*/
|
||||
enum class Scope {
|
||||
/**
|
||||
* 存储在请求头(Header)中
|
||||
*/
|
||||
HEADER,
|
||||
|
||||
/**
|
||||
* 存储在 MDC(Mapped Diagnostic Context)中,用于日志跟踪
|
||||
*/
|
||||
MDC,
|
||||
|
||||
/**
|
||||
* 存储在 Reactor 上下文中
|
||||
*/
|
||||
REACTOR,
|
||||
|
||||
/**
|
||||
* 存储在 Baggage 中,用于分布式追踪
|
||||
*/
|
||||
BAGGAGE
|
||||
}
|
||||
3
forgeboot-context/forgeboot-context-autoconfigure/.gitattributes
vendored
Normal file
3
forgeboot-context/forgeboot-context-autoconfigure/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/gradlew text eol=lf
|
||||
*.bat text eol=crlf
|
||||
*.jar binary
|
||||
40
forgeboot-context/forgeboot-context-autoconfigure/.gitignore
vendored
Normal file
40
forgeboot-context/forgeboot-context-autoconfigure/.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
|
||||
@ -0,0 +1,13 @@
|
||||
plugins{
|
||||
alias(libs.plugins.kotlin.plugin.spring)
|
||||
}
|
||||
dependencies {
|
||||
implementation(platform(libs.springBootDependencies.bom))
|
||||
implementation(libs.springBoot.autoconfigure)
|
||||
compileOnly(project(Modules.Context.API))
|
||||
compileOnly(project(Modules.Context.IMPL))
|
||||
compileOnly(libs.springCloudDependencies.bom)
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
compileOnly(libs.springCloudStarter.openfeign)
|
||||
}
|
||||
@ -0,0 +1,253 @@
|
||||
package com.gewuyou.forgeboot.context.autoconfigure
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.*
|
||||
import com.gewuyou.forgeboot.context.impl.*
|
||||
import com.gewuyou.forgeboot.context.impl.filter.ContextServletFilter
|
||||
import com.gewuyou.forgeboot.context.impl.filter.ContextWebFilter
|
||||
import com.gewuyou.forgeboot.context.impl.processor.*
|
||||
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.boot.autoconfigure.condition.*
|
||||
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer
|
||||
import org.springframework.core.task.TaskDecorator
|
||||
import org.springframework.web.reactive.function.client.ClientRequest
|
||||
|
||||
/**
|
||||
* 配置类,用于自动配置上下文相关的 Bean。
|
||||
*
|
||||
* 该配置类根据不同的运行时依赖和配置条件,定义了一系列的 Bean,
|
||||
* 实现了上下文字段在不同场景下的传播与管理机制。
|
||||
*
|
||||
* @since 2025-06-04 11:48:01
|
||||
* @author gewuyou
|
||||
*/
|
||||
@Configuration
|
||||
class ForgeContextAutoConfiguration {
|
||||
/* ───────────────────────────────────────────────────────────────
|
||||
0️⃣ 通用 Bean:不依赖 Web / Feign / Reactor 等外部包
|
||||
─────────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* 创建 FieldRegistry Bean,用于注册上下文中所有字段定义。
|
||||
*
|
||||
* FieldRegistry 是上下文字段的核心注册中心,聚合所有 ContextFieldContributor 提供的字段定义。
|
||||
*
|
||||
* @param contributors 提供字段定义的贡献者列表
|
||||
* @return 构建完成的 FieldRegistry 实例
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
fun fieldRegistry(contributors: List<ContextFieldContributor>): FieldRegistry =
|
||||
DefaultFieldRegistry(contributors.flatMap { it.fields() }.toSet())
|
||||
|
||||
/**
|
||||
* 创建 HeaderProcessor Bean,用于处理请求头中的上下文字段。
|
||||
*
|
||||
* HeaderProcessor 负责从请求头中提取上下文字段并注入到当前线程上下文中。
|
||||
*
|
||||
* @param reg 字段注册表
|
||||
* @return 构建完成的 HeaderProcessor 实例
|
||||
*/
|
||||
@Bean("headerProcessor")
|
||||
fun headerProcessor(reg: FieldRegistry) = HeaderProcessor(reg)
|
||||
|
||||
/**
|
||||
* 创建 GeneratorProcessor Bean,用于生成上下文字段值。
|
||||
*
|
||||
* GeneratorProcessor 根据字段定义生成默认值(如 traceId、spanId 等),适用于首次进入系统的情况。
|
||||
*
|
||||
* @param reg 字段注册表
|
||||
* @return 构建完成的 GeneratorProcessor 实例
|
||||
*/
|
||||
@Bean("generatorProcessor")
|
||||
fun generatorProcessor(reg: FieldRegistry) = GeneratorProcessor(reg)
|
||||
|
||||
/**
|
||||
* 创建 MdcProcessor Bean,用于将上下文字段写入 MDC(Mapped Diagnostic Context)。
|
||||
*
|
||||
* MdcProcessor 使得日志框架(如 Logback)可以访问当前上下文字段,便于日志追踪。
|
||||
*
|
||||
* @param reg 字段注册表
|
||||
* @return 构建完成的 MdcProcessor 实例
|
||||
*/
|
||||
@Bean("mdcProcessor")
|
||||
fun mdcProcessor(reg: FieldRegistry) = MdcProcessor(reg)
|
||||
|
||||
/* ───────────────────────────────────────────────────────────────
|
||||
1️⃣ Reactor 支持(只有 classpath 有 Reactor 时才激活)
|
||||
─────────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* 配置类,提供对 Reactor 上下文传播的支持。
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(name = ["reactor.util.context.Context"])
|
||||
class ReactorSupport {
|
||||
|
||||
/**
|
||||
* 创建 ReactorProcessor Bean,用于在 Reactor 上下文中传播上下文字段。
|
||||
*
|
||||
* ReactorProcessor 适配了 Reactor 的 Context 接口,确保上下文字段在响应式流中正确传递。
|
||||
*
|
||||
* @param reg 字段注册表
|
||||
* @return 构建完成的 ReactorProcessor 实例
|
||||
*/
|
||||
@Bean("reactorProcessor")
|
||||
fun reactorProcessor(reg: FieldRegistry) = ReactorProcessor(reg)
|
||||
}
|
||||
|
||||
/* ───────────────────────────────────────────────────────────────
|
||||
2️⃣ WebFlux 过滤器(依赖 WebFlux + Reactor)
|
||||
─────────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* 配置类,注册 WebFlux 环境下的上下文传播过滤器。
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnProperty(name = ["spring.main.web-application-type"], havingValue = "reactive")
|
||||
@ConditionalOnClass(name = ["org.springframework.web.server.WebFilter"])
|
||||
class WebFluxPart {
|
||||
|
||||
/**
|
||||
* 注册 ContextWebFilter Bean,用于在 WebFlux 请求链中传播上下文字段。
|
||||
*
|
||||
* 该过滤器利用 ReactorProcessor 在 WebFlux 的过滤器链中维护上下文一致性。
|
||||
*
|
||||
* @param chain 处理器链
|
||||
* @param reactorProcessor ReactorProcessor 实例
|
||||
* @return 构建完成的 ContextWebFilter 实例
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
fun contextWebFilter(
|
||||
chain: List<ContextProcessor>,
|
||||
reactorProcessor: ReactorProcessor,
|
||||
) = ContextWebFilter(chain, reactorProcessor)
|
||||
}
|
||||
|
||||
/* ───────────────────────────────────────────────────────────────
|
||||
3️⃣ Servlet 过滤器(依赖 Servlet API)
|
||||
─────────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* 配置类,注册 Servlet 环境下的上下文传播过滤器。
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnProperty(name = ["spring.main.web-application-type"], havingValue = "servlet", matchIfMissing = true)
|
||||
@ConditionalOnClass(name = ["jakarta.servlet.Filter"])
|
||||
class ServletPart {
|
||||
|
||||
/**
|
||||
* 注册 ContextServletFilter Bean,用于在 Servlet 请求链中传播上下文字段。
|
||||
*
|
||||
* 该过滤器负责在同步阻塞的 Servlet 请求链中维护上下文一致性。
|
||||
*
|
||||
* @param chain 处理器链
|
||||
* @return 构建完成的 ContextServletFilter 实例
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
fun contextServletFilter(chain: List<ContextProcessor>) =
|
||||
ContextServletFilter(chain)
|
||||
}
|
||||
|
||||
/* ───────────────────────────────────────────────────────────────
|
||||
4️⃣ Feign 请求拦截器(依赖 OpenFeign)
|
||||
─────────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* 配置类,注册 Feign 客户端的请求拦截器。
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(name = ["feign.RequestInterceptor"])
|
||||
class FeignPart {
|
||||
|
||||
/**
|
||||
* 注册 Feign 请求拦截器,用于在 Feign 调用中传播上下文字段。
|
||||
*
|
||||
* 拦截器会在每次 Feign 请求发起前,将当前上下文字段写入 HTTP 请求头。
|
||||
*
|
||||
* @param registry 字段注册表
|
||||
* @return 构建完成的 feign.RequestInterceptor 实例
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
fun feignInterceptor(registry: FieldRegistry) =
|
||||
feign.RequestInterceptor { tpl ->
|
||||
val ctx = StringContextHolder.snapshot()
|
||||
registry.all().forEach { def ->
|
||||
ctx[def.key]?.let { tpl.header(def.header, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ───────────────────────────────────────────────────────────────
|
||||
5️⃣ 线程池 TaskDecorator(纯 Spring,安全通用)
|
||||
─────────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* 配置类,注册异步执行上下文保持支持。
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class TaskDecoratorPart {
|
||||
|
||||
/**
|
||||
* 创建 TaskDecorator Bean,用于在异步执行中保持上下文一致性。
|
||||
*
|
||||
* 通过装饰线程池任务,确保异步任务继承调用线程的上下文状态。
|
||||
*
|
||||
* @param processors 所有处理器列表
|
||||
* @return 构建完成的 TaskDecorator 实例
|
||||
*/
|
||||
@Bean
|
||||
fun contextTaskDecorator(processors: List<ContextProcessor>) =
|
||||
TaskDecorator { delegate ->
|
||||
val snap = StringContextHolder.snapshot()
|
||||
Runnable {
|
||||
try {
|
||||
snap.forEach(StringContextHolder::put)
|
||||
processors.forEach { it.inject(Unit, snap.toMutableMap()) }
|
||||
delegate.run()
|
||||
} finally {
|
||||
processors.forEach { it.inject(Unit, mutableMapOf()) }
|
||||
StringContextHolder.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ───────────────────────────────────────────────────────────────
|
||||
6️⃣ WebClient 过滤器
|
||||
─────────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* 配置类,注册 WebClient 自定义器以支持上下文传播。
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(name = ["org.springframework.web.reactive.function.client.WebClient"])
|
||||
class WebClientPart(private val registry: FieldRegistry) {
|
||||
|
||||
/**
|
||||
* 注册 WebClientCustomizer,用于定制 WebClient 的请求行为。
|
||||
*
|
||||
* 在每次请求发出前,将当前上下文字段写入 HTTP 请求头。
|
||||
*
|
||||
* @return 构建完成的 WebClientCustomizer 实例
|
||||
*/
|
||||
@Bean
|
||||
fun contextWebClientCustomizer() = WebClientCustomizer { builder ->
|
||||
builder.filter { req, next ->
|
||||
val ctx = StringContextHolder.snapshot()
|
||||
val mutated = ClientRequest.from(req).apply {
|
||||
registry.all().forEach { def ->
|
||||
ctx[def.key]?.let { value -> header(def.header, value) }
|
||||
}
|
||||
}.build()
|
||||
next.exchange(mutated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
com.gewuyou.forgeboot.context.autoconfigure.ForgeContextAutoConfiguration
|
||||
3
forgeboot-context/forgeboot-context-impl/.gitattributes
vendored
Normal file
3
forgeboot-context/forgeboot-context-impl/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/gradlew text eol=lf
|
||||
*.bat text eol=crlf
|
||||
*.jar binary
|
||||
40
forgeboot-context/forgeboot-context-impl/.gitignore
vendored
Normal file
40
forgeboot-context/forgeboot-context-impl/.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
|
||||
@ -0,0 +1,8 @@
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(Modules.Context.API))
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
implementation(platform(libs.springBootDependencies.bom))
|
||||
implementation(libs.springBoot.autoconfigure)
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package com.gewuyou.forgeboot.context.impl
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.FieldRegistry
|
||||
import com.gewuyou.forgeboot.context.api.entities.FieldDef
|
||||
|
||||
/**
|
||||
* 默认字段注册表
|
||||
*
|
||||
* 该类实现了字段的注册和查找功能,通过字段的 key 和 header 建立索引,
|
||||
* 提供了基于 header 的快速查找能力。
|
||||
*
|
||||
* @param defs 初始化字段定义集合,用于构建注册表
|
||||
* @since 2025-06-04 14:51:39
|
||||
* @author gewuyou
|
||||
*/
|
||||
class DefaultFieldRegistry(defs: Set<FieldDef>) : FieldRegistry {
|
||||
/**
|
||||
* 按字段 key 构建的有序映射表,用于通过 key 快速访问字段定义。
|
||||
* 保持字段注册顺序的一致性,适用于需要按注册顺序遍历的场景。
|
||||
*/
|
||||
private val byKey: Map<String, FieldDef> =
|
||||
LinkedHashMap<String, FieldDef>().apply {
|
||||
defs.forEach { put(it.key, it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 按字段 header(小写形式)构建的映射表,用于通过 header 快速查找字段定义。
|
||||
*/
|
||||
private val byHeader = defs.associateBy { it.header.lowercase() }
|
||||
|
||||
/**
|
||||
* 获取注册表中所有的字段定义。
|
||||
*
|
||||
* @return 字段定义的集合
|
||||
*/
|
||||
override fun all() = byKey.values
|
||||
|
||||
/**
|
||||
* 根据字段 header 查找对应的字段定义。
|
||||
*
|
||||
* @param header 要查找的字段 header(不区分大小写)
|
||||
* @return 匹配到的字段定义,若未找到则返回 null
|
||||
*/
|
||||
override fun findByHeader(header: String) = byHeader[header.lowercase()]
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package com.gewuyou.forgeboot.context.impl
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.AbstractContext
|
||||
|
||||
/**
|
||||
*字符串上下文容器
|
||||
*
|
||||
* @since 2025-06-04 15:05:43
|
||||
* @author gewuyou
|
||||
*/
|
||||
object StringContextHolder: AbstractContext<String, String>()
|
||||
@ -0,0 +1,62 @@
|
||||
package com.gewuyou.forgeboot.context.impl.filter
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.ContextProcessor
|
||||
import com.gewuyou.forgeboot.context.impl.StringContextHolder
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.web.filter.OncePerRequestFilter
|
||||
|
||||
/**
|
||||
* 上下文 Servlet 过滤器
|
||||
*
|
||||
* 该过滤器用于在 HTTP 请求处理过程中提取和注入上下文信息,
|
||||
* 确保请求链中各组件可以共享上下文数据。
|
||||
*
|
||||
* @property chain 处理上下文的处理器链表,按顺序依次执行
|
||||
*
|
||||
* @since 2025-06-04 16:08:33
|
||||
* @author gewuyou
|
||||
*/
|
||||
class ContextServletFilter(
|
||||
private val chain: List<ContextProcessor>,
|
||||
) : OncePerRequestFilter() {
|
||||
|
||||
/**
|
||||
* 执行内部过滤逻辑
|
||||
*
|
||||
* 在请求进入业务逻辑前,从请求中提取上下文信息并存储到上下文持有者中;
|
||||
* 在请求完成后,清理上下文以避免内存泄漏或上下文污染。
|
||||
*
|
||||
* @param request 当前 HTTP 请求对象
|
||||
* @param response 当前 HTTP 响应对象
|
||||
* @param filterChain 过滤器链,用于继续执行后续过滤器或目标处理逻辑
|
||||
*/
|
||||
override fun doFilterInternal(
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
filterChain: FilterChain,
|
||||
) {
|
||||
// 创建当前线程上下文快照的可变副本,确保后续操作不影响原始上下文
|
||||
val ctx = StringContextHolder.snapshot().toMutableMap()
|
||||
|
||||
// 遍历上下文处理器链,依次从请求中提取上下文信息并更新临时上下文容器
|
||||
chain.forEach { it.extract(request, ctx) }
|
||||
|
||||
// 将提取后的上下文写入当前线程的上下文持有者,供后续组件访问
|
||||
ctx.forEach(StringContextHolder::put)
|
||||
|
||||
// 调用下一个过滤器或最终的目标处理器
|
||||
chain.forEach { it.inject(request, ctx) }
|
||||
|
||||
try {
|
||||
filterChain.doFilter(request, response)
|
||||
} finally {
|
||||
// 确保在请求结束时清理所有上下文资源
|
||||
// 向处理器链注入空上下文,触发清理操作(如有)
|
||||
chain.forEach { it.inject(Unit, mutableMapOf()) }
|
||||
// 显式清除当前线程的上下文持有者,防止上下文泄露
|
||||
StringContextHolder.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
package com.gewuyou.forgeboot.context.impl.filter
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.ContextProcessor
|
||||
import com.gewuyou.forgeboot.context.impl.StringContextHolder
|
||||
import com.gewuyou.forgeboot.context.impl.processor.ReactorProcessor
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import org.springframework.web.server.WebFilter
|
||||
import org.springframework.web.server.WebFilterChain
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
/**
|
||||
* 上下文 Web 过滤器
|
||||
*
|
||||
* 用于在 Web 请求处理过程中维护和传递上下文信息,包括 ThreadLocal、MDC 和 Reactor Context。
|
||||
* 通过一系列的 [ContextProcessor] 实现对上下文的提取、注入和清理操作。
|
||||
*
|
||||
* @property contextProcessors 上下文处理器列表,用于处理上下文的提取与注入逻辑。
|
||||
* @property reactorProc 反应式上下文处理器,用于将上下文写入 Reactor 的 Context 中。
|
||||
*
|
||||
* @since 2025-06-04 15:54:43
|
||||
* @author gewuyou
|
||||
*/
|
||||
class ContextWebFilter(
|
||||
private val contextProcessors: List<ContextProcessor>,
|
||||
private val reactorProc: ReactorProcessor
|
||||
) : WebFilter {
|
||||
/**
|
||||
* 执行过滤逻辑,在请求链中插入上下文管理操作。
|
||||
*
|
||||
* 此方法依次完成以下步骤:
|
||||
* 1. 提取当前上下文并构建新的上下文数据;
|
||||
* 2. 将上下文填充到 ThreadLocal 和 MDC;
|
||||
* 3. 将上下文注入到请求头中以构建新地请求;
|
||||
* 4. 继续执行请求链,并将上下文写入 Reactor Context;
|
||||
* 5. 最终清理 ThreadLocal、MDC 中的上下文。
|
||||
*
|
||||
* @param exchange 当前的 ServerWebExchange 对象,代表 HTTP 请求交换。
|
||||
* @param chain Web 过滤器链,用于继续执行后续的过滤器或目标处理逻辑。
|
||||
* @return 返回一个 Mono<Void> 表示异步完成的操作。
|
||||
*/
|
||||
override fun filter(
|
||||
exchange: ServerWebExchange,
|
||||
chain: WebFilterChain,
|
||||
): Mono<Void> {
|
||||
// 从 StringContextHolder 快照获取当前上下文并转换为可变 Map
|
||||
val ctx = StringContextHolder.snapshot().toMutableMap()
|
||||
// 遍历所有 ContextProcessor,从请求中提取上下文信息到 ctx
|
||||
contextProcessors.forEach { it.extract(exchange, ctx) }
|
||||
// 将上下文写入 StringContextHolder(ThreadLocal)
|
||||
ctx.forEach(StringContextHolder::put)
|
||||
// 使用 MdcProcessor 将上下文注入到 MDC 中
|
||||
contextProcessors.forEach { it.inject(Unit, ctx) }
|
||||
// 构建新的 ServerWebExchange 实例
|
||||
val mutated = exchange.mutate()
|
||||
// 注入上下文到请求头中
|
||||
contextProcessors.forEach { it.inject(mutated, ctx) }
|
||||
// 继续执行过滤器链,同时将上下文写入 Reactor Context
|
||||
return chain.filter(mutated.build())
|
||||
.contextWrite(reactorProc.injectToReactor(ctx))
|
||||
.doFinally {
|
||||
// 清理 ThreadLocal + MDC 上下文
|
||||
contextProcessors.forEach { it.inject(Unit, mutableMapOf()) }
|
||||
StringContextHolder.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package com.gewuyou.forgeboot.context.impl.processor
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.ContextProcessor
|
||||
import com.gewuyou.forgeboot.context.api.FieldRegistry
|
||||
|
||||
/**
|
||||
* 生成器处理器
|
||||
*
|
||||
* 用于通过字段注册表(FieldRegistry)动态填充上下文中的空白字段。
|
||||
* 当前类实现了 ContextProcessor 接口,提供了一种自动化的上下文字段填充机制。
|
||||
*
|
||||
* @since 2025-06-04 15:35:11
|
||||
* @author gewuyou
|
||||
*/
|
||||
class GeneratorProcessor(
|
||||
private val reg: FieldRegistry
|
||||
) : ContextProcessor {
|
||||
/**
|
||||
* 获取当前处理器的执行顺序优先级。
|
||||
*
|
||||
* 在多个 ContextProcessor 实现中,该方法决定本处理器的执行顺序。
|
||||
* 数值越小,优先级越高,在上下文处理流程中就越早被调用。
|
||||
*
|
||||
* @return Int 表示当前处理器的顺序值,默认为20
|
||||
*/
|
||||
override fun order(): Int {
|
||||
return 20
|
||||
}
|
||||
|
||||
/**
|
||||
* 从给定的载体中提取上下文信息,并填充到上下文对象中。
|
||||
*
|
||||
* 遍历 FieldRegistry 中注册的所有字段定义:
|
||||
* - 如果当前字段在上下文中不存在或为空白,则尝试使用其关联的生成器函数进行填充。
|
||||
* - 生成器函数非空时会被调用,并将结果存入上下文映射中。
|
||||
*
|
||||
* @param carrier 载体对象,通常包含上下文数据(未使用于当前实现)
|
||||
* @param ctx 可变映射,用于存储提取出的上下文键值对
|
||||
*/
|
||||
override fun extract(carrier: Any, ctx: MutableMap<String, String>) {
|
||||
reg.all().forEach { def ->
|
||||
if (ctx[def.key].isNullOrBlank()) {
|
||||
def.generator?.invoke()?.let { ctx[def.key] = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
package com.gewuyou.forgeboot.context.impl.processor
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.ContextProcessor
|
||||
import com.gewuyou.forgeboot.context.api.FieldRegistry
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
|
||||
/**
|
||||
* 请求头处理器
|
||||
*
|
||||
* @since 2025-06-04 15:14:57
|
||||
* @author gewuyou
|
||||
*/
|
||||
class HeaderProcessor(private val reg: FieldRegistry) : ContextProcessor {
|
||||
/**
|
||||
* 获取当前处理器的执行顺序优先级。
|
||||
*
|
||||
* 默认实现返回0,数值越小优先级越高。
|
||||
*
|
||||
* @return Int 表示当前处理器的顺序值
|
||||
*/
|
||||
override fun order(): Int {
|
||||
return 10
|
||||
}
|
||||
|
||||
/**
|
||||
* 从给定的载体中提取上下文信息,并填充到上下文对象中。
|
||||
*
|
||||
* 默认实现为空方法,子类可根据需要重写此方法。
|
||||
*
|
||||
* @param carrier 载体对象,通常包含上下文数据
|
||||
* @param ctx 可变映射,用于存储提取出的上下文键值对
|
||||
*/
|
||||
override fun extract(carrier: Any, ctx: MutableMap<String, String>) {
|
||||
when (carrier) {
|
||||
is ServerWebExchange -> reg.all().forEach { def ->
|
||||
// 从ServerWebExchange请求头中提取指定字段并存入上下文
|
||||
carrier.request.headers[def.header]?.firstOrNull()?.let { ctx[def.key] = it }
|
||||
}
|
||||
is HttpServletRequest -> reg.all().forEach { def ->
|
||||
// 从HttpServletRequest请求头中提取指定字段并存入上下文
|
||||
carrier.getHeader(def.header)?.let { ctx[def.key] = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将上下文信息注入到给定的载体中。
|
||||
*
|
||||
* 默认实现为空方法,子类可根据需要重写此方法。
|
||||
*
|
||||
* @param carrier 载体对象,将上下文数据注入其中
|
||||
* @param ctx 包含上下文键值对的映射
|
||||
*/
|
||||
override fun inject(carrier: Any, ctx: MutableMap<String, String>) {
|
||||
when (carrier) {
|
||||
is ServerWebExchange.Builder -> reg.all().forEach { def ->
|
||||
// 向ServerWebExchange构建器中注入请求头字段
|
||||
ctx[def.key]?.let { value ->
|
||||
carrier.request { reqBuilder ->
|
||||
reqBuilder.header(def.header, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
is HttpServletResponse -> reg.all().forEach { def ->
|
||||
// 向HttpServletResponse中设置对应的响应头字段
|
||||
ctx[def.key]?.let { carrier.setHeader(def.header, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package com.gewuyou.forgeboot.context.impl.processor
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.ContextProcessor
|
||||
import com.gewuyou.forgeboot.context.api.FieldRegistry
|
||||
import org.slf4j.MDC
|
||||
|
||||
/**
|
||||
* MDC 处理器
|
||||
* 把 ctx 写入 SLF4J MDC,方便日志打印;请求结束或线程任务结束时清理。
|
||||
*
|
||||
*
|
||||
* @since 2025-06-04 15:39:35
|
||||
* @author gewuyou
|
||||
*/
|
||||
class MdcProcessor(
|
||||
private val reg: FieldRegistry,
|
||||
) : ContextProcessor {
|
||||
/**
|
||||
* 获取当前处理器的执行顺序优先级。
|
||||
*
|
||||
* 默认实现返回0,数值越小优先级越高。
|
||||
*
|
||||
* @return Int 表示当前处理器的顺序值
|
||||
*/
|
||||
override fun order(): Int {
|
||||
return 30
|
||||
}
|
||||
|
||||
/**
|
||||
* 将上下文信息注入到给定的载体中。
|
||||
*
|
||||
* 默认实现为空方法,子类可根据需要重写此方法。
|
||||
*
|
||||
* @param carrier 载体对象,将上下文数据注入其中
|
||||
* @param ctx 包含上下文键值对的映射
|
||||
*/
|
||||
override fun inject(carrier: Any, ctx: MutableMap<String, String>) {
|
||||
if (ctx.isEmpty()) {
|
||||
// 视为空 ctx → 清理
|
||||
reg.all().forEach { def -> MDC.remove(def.key) }
|
||||
} else {
|
||||
// 正常写入
|
||||
reg.all().forEach { def ->
|
||||
ctx[def.key]?.let { MDC.put(def.key, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package com.gewuyou.forgeboot.context.impl.processor
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.ContextProcessor
|
||||
import com.gewuyou.forgeboot.context.api.FieldRegistry
|
||||
import reactor.util.context.Context
|
||||
import java.util.function.Function
|
||||
/**
|
||||
* 反应器处理器
|
||||
*
|
||||
* 用于在 WebFlux/Reactor 流中透传字段。当进入异步链时,将字段写入 reactor.util.context.Context。
|
||||
* Context 在链尾无需手动清理,会自动复制和管理。
|
||||
*
|
||||
* @property reg 字段注册表,用于获取所有需要处理的字段定义
|
||||
* @since 2025-06-04 15:43:20
|
||||
* @author gewuyou
|
||||
*/
|
||||
class ReactorProcessor(
|
||||
private val reg: FieldRegistry
|
||||
) : ContextProcessor {
|
||||
/**
|
||||
* 获取当前处理器的执行顺序优先级。
|
||||
*
|
||||
* 默认实现返回0,数值越小优先级越高。
|
||||
*
|
||||
* @return Int 表示当前处理器的顺序值
|
||||
*/
|
||||
override fun order(): Int {
|
||||
return 40
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个函数,用于将给定的上下文数据注入到 Reactor 的 Context 中。
|
||||
*
|
||||
* 遍历字段注册表中的所有字段定义,并将对应的值从输入上下文中取出,
|
||||
* 然后放入 Reactor 的 Context 中。
|
||||
*
|
||||
* @param ctx 包含要注入字段的映射表(key-value)
|
||||
* @return Function<Context, Context> 返回一个函数,该函数接受原始的 Reactor Context 并返回更新后的 Context
|
||||
*/
|
||||
fun injectToReactor(ctx: Map<String, String>): Function<Context, Context> =
|
||||
Function { rCtx ->
|
||||
var updated = rCtx
|
||||
// 遍历所有字段定义,并将对应的值注入到 Reactor Context 中
|
||||
reg.all().forEach { def ->
|
||||
ctx[def.key]?.let { value -> updated = updated.put(def.key, value) }
|
||||
}
|
||||
updated
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,11 @@
|
||||
|
||||
dependencies {
|
||||
compileOnly(platform(libs.springBootDependencies.bom))
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
implementation(platform(libs.springBootDependencies.bom))
|
||||
implementation(libs.springBoot.autoconfigure)
|
||||
compileOnly(platform(libs.springCloudDependencies.bom))
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
compileOnly(project(Modules.I18n.API))
|
||||
compileOnly(project(Modules.I18n.IMPL))
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
}
|
||||
|
||||
@ -21,10 +21,4 @@ class TraceProperties {
|
||||
* MDC(Mapped Diagnostic Context)中用于存储请求ID的键名,默认为"requestId"。
|
||||
*/
|
||||
var requestIdMdcKey: String = "requestId"
|
||||
|
||||
/**
|
||||
* 配置忽略日志记录的路径模式,通常用于静态资源文件,
|
||||
* 默认忽略以.css、.js、.png等结尾的静态资源请求。
|
||||
*/
|
||||
var ignorePatten = arrayOf(".*\\.(css|js|png|jpg|jpeg|gif|svg)")
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.plugin.spring)
|
||||
}
|
||||
dependencies {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
compileOnly(platform(libs.springBootDependencies.bom))
|
||||
implementation(platform(libs.springBootDependencies.bom))
|
||||
implementation(libs.springBoot.autoconfigure)
|
||||
compileOnly(platform(libs.springCloudDependencies.bom))
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
package com.gewuyou.forgeboot.trace.autoconfig
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
/**
|
||||
*Feign跟踪自动配置
|
||||
*
|
||||
* @since 2025-05-31 22:02:56
|
||||
* @author gewuyou
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(name = ["feign.RequestInterceptor"])
|
||||
open class FeignTraceAutoConfiguration {
|
||||
/**
|
||||
* Feign 拦截器(仅当 Feign 存在时生效)
|
||||
*
|
||||
* 该拦截器用于在Feign客户端中传递请求ID
|
||||
* @param traceProperties 跟踪配置属性
|
||||
* @return FeignRequestIdInterceptor实例
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = ["feignRequestIdInterceptor"])
|
||||
open fun feignRequestIdInterceptor(traceProperties: TraceProperties): Any {
|
||||
val clazz = Class.forName("com.gewuyou.forgeboot.trace.impl.interceptor.FeignRequestIdInterceptor")
|
||||
val constructor = clazz.getConstructor(TraceProperties::class.java)
|
||||
log.info( "创建FeignRequestIdInterceptor实例")
|
||||
return constructor.newInstance(traceProperties)
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,18 @@
|
||||
package com.gewuyou.forgeboot.trace.autoconfig
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.ContextFieldContributor
|
||||
import com.gewuyou.forgeboot.context.api.entities.FieldDef
|
||||
import com.gewuyou.forgeboot.context.api.enums.Scope
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
|
||||
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.impl.decorator.RequestIdTaskDecorator
|
||||
import com.gewuyou.forgeboot.trace.impl.filter.ReactiveRequestIdFilter
|
||||
import com.gewuyou.forgeboot.trace.impl.filter.RequestIdFilter
|
||||
import com.gewuyou.forgeboot.trace.impl.provider.TraceRequestIdProvider
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* 跟踪自动配置
|
||||
@ -23,37 +23,9 @@ import org.springframework.context.annotation.Configuration
|
||||
*/
|
||||
@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 {
|
||||
log.info("RequestIdFilter 已创建!")
|
||||
return 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 {
|
||||
log.info("ReactiveRequestIdFilter 已创建!")
|
||||
return ReactiveRequestIdFilter(traceProperties)
|
||||
}
|
||||
|
||||
class TraceAutoConfiguration(
|
||||
private val traceProperties: TraceProperties
|
||||
) {
|
||||
/**
|
||||
* 请求ID提供者(用于生成请求ID)
|
||||
*
|
||||
@ -62,23 +34,19 @@ open class TraceAutoConfiguration {
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(RequestIdProvider::class)
|
||||
open fun traceRequestIdProvider(): TraceRequestIdProvider {
|
||||
fun traceRequestIdProvider(): TraceRequestIdProvider {
|
||||
log.info("TraceRequestIdProvider 已创建!")
|
||||
return TraceRequestIdProvider()
|
||||
return TraceRequestIdProvider(traceProperties)
|
||||
}
|
||||
|
||||
/**
|
||||
* 线程池装饰器(用于 @Async)
|
||||
*
|
||||
* 该装饰器用于在异步线程池中传递请求ID,确保异步执行的任务能够携带正确地请求信息
|
||||
* @param traceProperties 跟踪配置属性
|
||||
* @return RequestIdTaskDecorator实例
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
open fun requestIdTaskDecorator(traceProperties: TraceProperties): RequestIdTaskDecorator {
|
||||
log.info("RequestIdTaskDecorator 已创建!")
|
||||
return RequestIdTaskDecorator(traceProperties)
|
||||
fun requestContributor() = ContextFieldContributor {
|
||||
setOf(
|
||||
FieldDef(
|
||||
header = traceProperties.requestIdHeaderName, // 请求-响应头名
|
||||
key = traceProperties.requestIdMdcKey, // ctx/MDC 键
|
||||
generator = { UUID.randomUUID().toString() }, // 如果前端没带,用这个生成
|
||||
scopes = setOf(Scope.HEADER, Scope.MDC, Scope.REACTOR)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
package com.gewuyou.forgeboot.trace.autoconfig
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.impl.filter.WebClientRequestIdFilter
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
/**
|
||||
* Web客户端跟踪自动配置
|
||||
*
|
||||
* 该配置类用于自动配置Web客户端的请求ID过滤器,以实现请求跟踪功能
|
||||
* 它依赖于特定条件,如类的存在和Bean的定义,以确保在适当的时候进行配置
|
||||
*
|
||||
* @since 2025-05-31 21:59:02
|
||||
* @author gewuyou
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(name = ["org.springframework.web.reactive.function.client.WebClient\$Builder"])
|
||||
open class WebClientTraceAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 配置Web客户端的请求ID过滤器
|
||||
*
|
||||
* 该方法在满足条件时被调用,它获取WebClient.Builder实例并应用请求ID过滤器,
|
||||
* 以便在发出请求时添加请求ID信息这对于跟踪请求跨多个服务非常有用
|
||||
*
|
||||
* @param webClientBuilder Web客户端构建器,用于配置过滤器
|
||||
* @param traceProperties 跟踪属性配置,用于定制跟踪行为
|
||||
* @return 配置后的WebClient.Builder实例
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnBean(name = ["webClientBuilder"])
|
||||
open fun webClientRequestIdFilter(
|
||||
webClientBuilder: org.springframework.web.reactive.function.client.WebClient.Builder,
|
||||
traceProperties: TraceProperties
|
||||
): org.springframework.web.reactive.function.client.WebClient.Builder {
|
||||
log .info("配置Web客户端的请求ID过滤器")
|
||||
return webClientBuilder.filter(WebClientRequestIdFilter(traceProperties))
|
||||
}
|
||||
}
|
||||
@ -1,3 +1 @@
|
||||
com.gewuyou.forgeboot.trace.autoconfig.TraceAutoConfiguration
|
||||
com.gewuyou.forgeboot.trace.autoconfig.WebClientTraceAutoConfiguration
|
||||
com.gewuyou.forgeboot.trace.autoconfig.FeignTraceAutoConfiguration
|
||||
@ -1,7 +1,8 @@
|
||||
dependencies {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
implementation(platform(libs.springBootDependencies.bom))
|
||||
api(project(Modules.Core.EXTENSION))
|
||||
api(project(Modules.Context.STARTER))
|
||||
compileOnly(project(Modules.TRACE.API))
|
||||
compileOnly(platform(libs.springBootDependencies.bom))
|
||||
compileOnly(platform(libs.springCloudDependencies.bom))
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.decorator
|
||||
|
||||
import com.gewuyou.forgeboot.trace.api.config.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.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
|
||||
}
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.filter
|
||||
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.impl.extension.isSkipRequest
|
||||
import com.gewuyou.forgeboot.trace.impl.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::requestId::set) ?: RequestIdUtil.generateRequestId()
|
||||
} ?: RequestIdUtil.generateRequestId()
|
||||
// 获取当前的 requestId
|
||||
val currentRequestId = RequestIdUtil.requestId
|
||||
// 将 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.filter
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.impl.extension.isSkipRequest
|
||||
import com.gewuyou.forgeboot.trace.impl.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::requestId::set
|
||||
) ?: run {
|
||||
// 如果没有,则生成新的 requestId
|
||||
RequestIdUtil.generateRequestId()
|
||||
}
|
||||
// 获取 requestId
|
||||
val requestId = RequestIdUtil.requestId
|
||||
// 将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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.filter
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.impl.extension.isSkipRequest
|
||||
import com.gewuyou.forgeboot.trace.impl.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::requestId::set) ?: RequestIdUtil.generateRequestId()
|
||||
} ?: RequestIdUtil.generateRequestId()
|
||||
// 获取当前的 requestId
|
||||
val currentRequestId = RequestIdUtil.requestId
|
||||
// 将 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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.interceptor
|
||||
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.impl.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.requestId
|
||||
requestId?.let {
|
||||
// 如果请求id存在,则添加到请求头中
|
||||
template.header(traceProperties.requestIdHeaderName, requestId)
|
||||
} ?: run {
|
||||
log.warn("请求ID为null,请检查您是否已在过滤链中添加了请求filter。")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.provider
|
||||
|
||||
import com.gewuyou.forgeboot.context.impl.StringContextHolder
|
||||
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
|
||||
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
|
||||
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
|
||||
|
||||
|
||||
/**
|
||||
@ -10,7 +11,9 @@ import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
|
||||
* @since 2025-05-03 17:26:46
|
||||
* @author gewuyou
|
||||
*/
|
||||
class TraceRequestIdProvider: RequestIdProvider {
|
||||
class TraceRequestIdProvider(
|
||||
private val traceProperties: TraceProperties
|
||||
): RequestIdProvider {
|
||||
/**
|
||||
* 获取请求ID
|
||||
*
|
||||
@ -19,6 +22,6 @@ class TraceRequestIdProvider: RequestIdProvider {
|
||||
* @return 请求ID的字符串表示
|
||||
*/
|
||||
override fun getRequestId(): String {
|
||||
return RequestIdUtil.requestId?:throw RuntimeException("requestId is null")
|
||||
return StringContextHolder.get(traceProperties.requestIdMdcKey) ?:throw RuntimeException("requestId is null")
|
||||
}
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.util
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 请求 ID Util
|
||||
* 这个类需配合 RequestIdFilter 使用,用于生成请求 ID,并将其绑定到线程变量中,供后续可能需要的地方使用。
|
||||
* @author gewuyou
|
||||
* @since 2025-01-02 14:27:45
|
||||
*/
|
||||
object RequestIdUtil {
|
||||
private val REQUEST_ID_HOLDER = ThreadLocal<String>()
|
||||
fun generateRequestId() {
|
||||
REQUEST_ID_HOLDER.set(UUID.randomUUID().toString())
|
||||
}
|
||||
|
||||
var requestId: String?
|
||||
get() = REQUEST_ID_HOLDER.get()
|
||||
set(uuid) {
|
||||
REQUEST_ID_HOLDER.set(uuid)
|
||||
}
|
||||
|
||||
fun removeRequestId() {
|
||||
REQUEST_ID_HOLDER.remove()
|
||||
}
|
||||
}
|
||||
@ -32,4 +32,8 @@ open class BaseResult<T>(
|
||||
}
|
||||
|
||||
fun toFlatMap(): Map<String, Any?> = toMutableFlatMap().toMap()
|
||||
|
||||
override fun toString(): String {
|
||||
return "BaseResult(code=$code, success=$success, message='$message', data=$data, requestId=$requestId, extra=$extra)"
|
||||
}
|
||||
}
|
||||
@ -109,4 +109,7 @@ open class PageResult<T> {
|
||||
*/
|
||||
fun <T> empty(): PageResult<T> = PageResult()
|
||||
}
|
||||
override fun toString(): String {
|
||||
return "PageResult(records=$records, totalRecords=$totalRecords, totalPages=$totalPages, currentPage=$currentPage, pageSize=$pageSize, hasPrevious=$hasPrevious, hasNext=$hasNext)"
|
||||
}
|
||||
}
|
||||
@ -85,4 +85,9 @@ open class PageQueryReq<T> {
|
||||
fun isDateRangeValid(): Boolean {
|
||||
return startDate == null || endDate == null || !startDate!!.isAfter(endDate)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "PageQueryReq(currentPage=$currentPage, pageSize=$pageSize, sortBy='$sortBy', sortDirection=$sortDirection, sortConditions=$sortConditions, keyword=$keyword, filter=$filter, startDate=$startDate, endDate=$endDate, enabled=$enabled, deleted=$deleted)"
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,6 +4,8 @@ plugins{
|
||||
}
|
||||
dependencies {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
implementation(platform(libs.springBootDependencies.bom))
|
||||
implementation(libs.springBoot.autoconfigure)
|
||||
api(project(Modules.I18n.STARTER))
|
||||
api(project(Modules.TRACE.STARTER))
|
||||
implementation(project(Modules.Webmvc.DTO))
|
||||
|
||||
@ -3,8 +3,10 @@ plugins{
|
||||
}
|
||||
dependencies {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
api(project(Modules.TRACE.STARTER))
|
||||
implementation(project(Modules.Webmvc.DTO))
|
||||
implementation(platform(libs.springBootDependencies.bom))
|
||||
implementation(libs.springBoot.autoconfigure)
|
||||
api(project(Modules.TRACE.STARTER))
|
||||
compileOnly(libs.springBootStarter.validation)
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
kapt(libs.springBoot.configuration.processor)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
dependencies {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
implementation(libs.springBootStarter.aop)
|
||||
|
||||
implementation(libs.kotlinxCoroutines.reactor)
|
||||
|
||||
implementation(platform(libs.springBootDependencies.bom))
|
||||
implementation(libs.springBoot.autoconfigure)
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.gewuyou.forgeboot.webmvc.spec.service.impl
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.webmvc.dto.PageResult
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.map
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.toPageResult
|
||||
@ -227,6 +226,6 @@ abstract class CrudServiceImplSpec<Entity : Any, Id : Any, Filter : Any>(
|
||||
exist?.let {
|
||||
setDeleted(it)
|
||||
update(it)
|
||||
} ?: log.error("删除失败,找不到该租户")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
dependencies {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
implementation(platform(libs.springBootDependencies.bom))
|
||||
implementation(libs.springBoot.autoconfigure)
|
||||
kapt(libs.springBoot.configuration.processor)
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@ springBootStarter-webflux = { group = "org.springframework.boot", name = "spring
|
||||
springBootStarter-jpa = { group = "org.springframework.boot", name = "spring-boot-starter-data-jpa" }
|
||||
springBootStarter-validation = { group = "org.springframework.boot", name = "spring-boot-starter-validation" }
|
||||
springBoot-configuration-processor = { group = "org.springframework.boot", name = "spring-boot-configuration-processor", version.ref = "spring-boot-version" }
|
||||
springBoot-autoconfigure = { group = "org.springframework.boot", name = "spring-boot-autoconfigure" }
|
||||
|
||||
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" }
|
||||
|
||||
@ -1,28 +1,55 @@
|
||||
// The settings file is the entry point of every Gradle build.
|
||||
// Its primary purpose is to define the subprojects.
|
||||
// It is also used for some aspects of project-wide configuration, like managing plugins, dependencies, etc.
|
||||
// https://docs.gradle.org/current/userguide/settings_file_basics.html
|
||||
|
||||
/**
|
||||
* This settings.gradle.kts file configures the Gradle build for the forgeboot project.
|
||||
* It sets up dependency resolution, plugins, and includes all relevant subprojects.
|
||||
*/
|
||||
|
||||
// Configures the dependency resolution management across all subprojects
|
||||
dependencyResolutionManagement {
|
||||
// Use Maven Central as the default repository (where Gradle will download dependencies) in all subprojects.
|
||||
/**
|
||||
* Use Maven Central as the default repository (where Gradle will download dependencies)
|
||||
* The @Suppress annotation is used to bypass warnings about unstable API usage.
|
||||
*/
|
||||
@Suppress("UnstableApiUsage")
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
// Applies necessary plugins for the build process
|
||||
plugins {
|
||||
// Use the Foojay Toolchains plugin to automatically download JDKs required by subprojects.
|
||||
/**
|
||||
* Use the Foojay Toolchains plugin to automatically download JDKs required by subprojects.
|
||||
* This ensures consistent Java versions across different environments.
|
||||
*/
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
|
||||
}
|
||||
|
||||
// Include the `app` and `utils` subprojects in the build.
|
||||
// If there are changes in only one of the projects, Gradle will rebuild only the one that has changed.
|
||||
// Learn more about structuring projects with Gradle - https://docs.gradle.org/8.7/userguide/multi_project_builds.html
|
||||
|
||||
|
||||
// Sets the root project name
|
||||
rootProject.name = "forgeboot"
|
||||
|
||||
//region module context
|
||||
/**
|
||||
* Includes and configures projects related to 'forgeboot-context'
|
||||
* This module appears to be focused on contextual functionality within the application.
|
||||
*/
|
||||
include(
|
||||
"forgeboot-context",
|
||||
":forgeboot-context:forgeboot-context-api",
|
||||
":forgeboot-context:forgeboot-context-impl",
|
||||
":forgeboot-context:forgeboot-context-autoconfigure",
|
||||
)
|
||||
project(":forgeboot-context").name = "forgeboot-context-spring-boot-starter"
|
||||
project(":forgeboot-context:forgeboot-context-api").name = "forgeboot-context-api"
|
||||
project(":forgeboot-context:forgeboot-context-impl").name = "forgeboot-context-impl"
|
||||
project(":forgeboot-context:forgeboot-context-autoconfigure").name = "forgeboot-context-autoconfigure"
|
||||
//endregion
|
||||
|
||||
//region module banner
|
||||
/**
|
||||
* Includes and configures projects related to 'forgeboot-banner'
|
||||
* This module likely deals with banners or startup messages in the application.
|
||||
*/
|
||||
include(
|
||||
"forgeboot-banner",
|
||||
":forgeboot-banner:forgeboot-banner-api",
|
||||
@ -36,6 +63,11 @@ include(
|
||||
//endregion
|
||||
|
||||
//region module webmvc
|
||||
/**
|
||||
* Includes and configures projects related to 'forgeboot-webmvc'
|
||||
* This module seems to handle Spring WebMVC-related functionalities like logging,
|
||||
* exceptions, DTO handling, validation, etc.
|
||||
*/
|
||||
include(
|
||||
"forgeboot-webmvc",
|
||||
":forgeboot-webmvc:version",
|
||||
@ -57,6 +89,10 @@ project(":forgeboot-webmvc:spec").name = "forgeboot-webmvc-spec"
|
||||
//endregion
|
||||
|
||||
//region module core
|
||||
/**
|
||||
* Includes and configures projects related to 'forgeboot-core'
|
||||
* This module represents foundational components of the application.
|
||||
*/
|
||||
include(
|
||||
"forgeboot-core",
|
||||
":forgeboot-core:forgeboot-core-extension"
|
||||
@ -66,6 +102,10 @@ project(":forgeboot-core:forgeboot-core-extension").name = "forgeboot-core-exten
|
||||
//endregion
|
||||
|
||||
//region module i18n
|
||||
/**
|
||||
* Includes and configures projects related to 'forgeboot-i18n'
|
||||
* This module handles internationalization (i18n) support.
|
||||
*/
|
||||
include(
|
||||
"forgeboot-i18n",
|
||||
":forgeboot-i18n:forgeboot-i18n-api",
|
||||
@ -78,14 +118,12 @@ project(":forgeboot-i18n:forgeboot-i18n-impl").name = "forgeboot-i18n-impl"
|
||||
project(":forgeboot-i18n:forgeboot-i18n-autoconfigure").name = "forgeboot-i18n-autoconfigure"
|
||||
//endregion
|
||||
|
||||
//region module webflux
|
||||
//include(
|
||||
// "forgeboot-webflux",
|
||||
//)
|
||||
//project(":forgeboot-webflux").name = "forgeboot-webflux-spring-boot-starter"
|
||||
//endregion
|
||||
|
||||
//region module trace
|
||||
/**
|
||||
* Includes and configures projects related to 'forgeboot-trace'
|
||||
* This module handles distributed tracing functionality.
|
||||
*/
|
||||
include(
|
||||
"forgeboot-trace",
|
||||
":forgeboot-trace:forgeboot-trace-api",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user