Compare commits

...

10 Commits

Author SHA1 Message Date
21b79551a5 refactor(异常处理模块): 更新依赖项范围
- 将 Webmvc.DTO 模块的依赖项范围从 implementation 改为 api- 添加 TRACE.STARTER 模块为 api 依赖项
-移除 springBoot.autoconfigure 的 implementation依赖项
2025-06-22 20:15:36 +08:00
bdba342daa feat(trace): 添加请求上下文贡献者 Bean
- 新增 requestContributor 方法,创建 ContextFieldContributor 实例
- 定义请求上下文中需要维护的字段集合- 优化 TraceRequestIdProvider 方法的注释说明
2025-06-22 20:13:26 +08:00
ed8bb7cc91 refactor(trace): 重构 TraceRequestIdProvider 并调整依赖
- 在 TraceAutoConfiguration 中添加 ContextHolder依赖
- 更新 traceRequestIdProvider 方法以使用 ContextHolder
- 修改 TraceRequestIdProvider 类以使用 ContextHolder- 调整 forgeboot-trace-impl 的构建配置,将 context-starter 依赖改为 compileOnly
- 移除 forgeboot-trace-impl 中的 spring-boot-configuration-processor 依赖
2025-06-22 20:11:29 +08:00
f6794fd14f refactor(context): 重构上下文过滤器配置
- 为 ContextWebFilter 和 ContextServletFilter 添加 ContextHolder 参数
- 优化了上下文过滤器的创建逻辑,提高灵活性和可扩展性
2025-06-22 20:02:09 +08:00
7a6c371aa3 feat(context): 重构上下文管理并添加序列化支持
- 新增 ContextHolder 类,替换原有的 StringContextHolder 对象
- 实现上下文值的序列化和反序列化
- 更新相关过滤器和装饰器以使用新的 ContextHolder- 添加获取指定类型值的方法- 实现 Context 接口的扩展方法,支持操作符重载
2025-06-22 20:01:12 +08:00
92b3eedb38 feat(core-serialization): 添加 Jackson序列化支持
- 新增 SerializerException 异常类用于处理序列化相关错误
- 实现 JacksonValueSerializer 类,提供基于 Jackson 的对象序列化和反序列化功能
- 定义 ValueSerializer 接口,规范序列化和反序列化方法
- 添加 Jackson 相关依赖到 build.gradle.kts 文件
- 创建 .gitattributes 和 .gitignore 文件,配置项目版本控制忽略项
2025-06-22 20:00:55 +08:00
82f2a50f48 build: 更新 Gradle 版本并添加新依赖
- 将 Gradle 版本从 8.12 升级到8.14
- 新增 jjwt、gradleMavenPublishPlugin、spring-security、redis等依赖
- 更新部分现有依赖的版本
2025-06-22 19:57:56 +08:00
d980363a33 build: 注释掉 Gitea 私服配置
- 注释掉了 config/repositories.gradle.kts 文件中的 Gitea 私服配置代码块
- 保留了阿里云 Maven 仓库的配置
2025-06-22 19:57:46 +08:00
05ab150b8e refactor(forgeboot-webmvc): 移除 WebMvcExceptionProperties 中未使用的 enable 属性
- 删除了 WebMvcExceptionProperties 类中未使用的 enable 属性
-简化了配置结构,提高了代码的可维护性
2025-06-09 21:14:12 +08:00
6fcf5d6d40 refactor(webmvc-dto):优化页面查询扩展和响应对象- 在 PageQueryExtensions 中添加对字段不存在的容错处理
- 在 R 类中添加 success 方法,简化成功响应对象的创建
2025-06-09 19:31:15 +08:00
29 changed files with 450 additions and 82 deletions

View File

@ -2,12 +2,11 @@
repositories {
mavenLocal()
val host = System.getenv("GITEA_HOST")
host?.let {
maven{
url = uri("http://${host}/api/packages/gewuyou/maven")
isAllowInsecureProtocol = true
}
}
// host?.let {
// maven{
// url = uri("${host}/api/packages/gewuyou/maven")
// }
// }
maven {
url = uri("https://maven.aliyun.com/repository/public/")
}

View File

@ -26,7 +26,7 @@ abstract class AbstractContext<K, V>: Context<K, V> {
* @param key 要查找的键
* @return 对应的值如果不存在则返回 null
*/
override fun get(key: K): V? {
override fun retrieve(key: K): V? {
return local.get()[key]
}

View File

@ -21,7 +21,16 @@ interface Context<K, V> {
* @param key 要查找的键
* @return 对应的值如果不存在则返回 null
*/
fun get(key: K): V?
fun retrieve(key: K): V?
/**
* 根据指定的键和类型从上下文中获取对应的值
*
* @param key 要查找的键
* @param type 要转换的目标类型
* @return 对应类型的值如果不存在或类型不匹配则返回 null
*/
fun <T> retrieveByType(key: K, type: Class<T>): T?
/**
* 获取当前上下文的一个快照包含所有键值对

View File

@ -0,0 +1,34 @@
package com.gewuyou.forgeboot.context.api.extension
import com.gewuyou.forgeboot.context.api.Context
/**
* 通过操作符重载实现 Context get 方法用于根据指定的键获取对应的值
*
* @param key 用于查找上下文中的值
* @return 返回与键对应的值如果不存在则返回 null
*/
operator fun <K, V> Context<K, V>.get(key: K): V? {
return this.retrieve(key)
}
/**
* 通过操作符重载实现 Context set 方法用于将键值对存储到上下文中
*
* @param key 用于标识要存储的值
* @param value 要存储的值可以为 null
*/
operator fun <K, V> Context<K, V>.set(key: K, value: V?) {
this.put(key, value)
}
/**
* 通过操作符重载实现 Context get 方法用于根据指定的键和类型获取对应的值
*
* @param key 用于查找上下文中的值
* @param type 指定的值类型用于类型安全地获取值
* @return 返回与键和类型对应的值如果不存在则返回 null
*/
operator fun <K, V, T> Context<K, V>.get(key: K, type: Class<T>) {
this.retrieveByType(key, type)
}

View File

@ -10,4 +10,5 @@ dependencies {
compileOnly(libs.springBootStarter.web)
compileOnly(libs.springBootStarter.webflux)
compileOnly(libs.springCloudStarter.openfeign)
api(project(Modules.Core.SERIALIZATION))
}

View File

@ -1,17 +1,26 @@
package com.gewuyou.forgeboot.context.autoconfigure
import com.gewuyou.forgeboot.context.api.*
import com.gewuyou.forgeboot.context.impl.*
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.context.api.ContextFieldContributor
import com.gewuyou.forgeboot.context.api.ContextProcessor
import com.gewuyou.forgeboot.context.api.FieldRegistry
import com.gewuyou.forgeboot.context.impl.ContextHolder
import com.gewuyou.forgeboot.context.impl.DefaultFieldRegistry
import com.gewuyou.forgeboot.context.impl.filter.ContextServletFilter
import com.gewuyou.forgeboot.context.impl.filter.ContextWebFilter
import com.gewuyou.forgeboot.context.impl.processor.*
import com.gewuyou.forgeboot.context.impl.processor.GeneratorProcessor
import com.gewuyou.forgeboot.context.impl.processor.HeaderProcessor
import com.gewuyou.forgeboot.context.impl.processor.MdcProcessor
import com.gewuyou.forgeboot.context.impl.processor.ReactorProcessor
import com.gewuyou.forgeboot.core.serialization.serializer.ValueSerializer
import com.gewuyou.forgeboot.core.serialization.serializer.impl.serializer.JacksonValueSerializer
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.web.reactive.function.client.WebClientCustomizer
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
@ -26,6 +35,17 @@ import org.springframework.web.reactive.function.client.ClientRequest
*/
@Configuration
class ForgeContextAutoConfiguration {
@Bean
@ConditionalOnMissingBean
fun valueSerializer(objectMapper: ObjectMapper): ValueSerializer{
return JacksonValueSerializer(objectMapper)
}
@Bean
@ConditionalOnMissingBean
fun contextHolder(valueSerializer: ValueSerializer): ContextHolder {
return ContextHolder(valueSerializer)
}
/*
0 通用 Bean不依赖 Web / Feign / Reactor 等外部包
*/
@ -125,7 +145,8 @@ class ForgeContextAutoConfiguration {
fun contextWebFilter(
chain: List<ContextProcessor>,
reactorProcessor: ReactorProcessor,
) = ContextWebFilter(chain, reactorProcessor)
contextHolder: ContextHolder
) = ContextWebFilter(chain, reactorProcessor,contextHolder)
}
/*
@ -150,8 +171,8 @@ class ForgeContextAutoConfiguration {
*/
@Bean
@ConditionalOnMissingBean
fun contextServletFilter(chain: List<ContextProcessor>) =
ContextServletFilter(chain)
fun contextServletFilter(chain: List<ContextProcessor>,contextHolder: ContextHolder) =
ContextServletFilter(chain,contextHolder)
}
/*
@ -171,13 +192,14 @@ class ForgeContextAutoConfiguration {
* 拦截器会在每次 Feign 请求发起前将当前上下文字段写入 HTTP 请求头
*
* @param registry 字段注册表
* @param contextHolder 上下文持有者
* @return 构建完成的 feign.RequestInterceptor 实例
*/
@Bean
@ConditionalOnMissingBean
fun feignInterceptor(registry: FieldRegistry) =
fun feignInterceptor(registry: FieldRegistry,contextHolder: ContextHolder) =
feign.RequestInterceptor { tpl ->
val ctx = StringContextHolder.snapshot()
val ctx = contextHolder.snapshot()
registry.all().forEach { def ->
ctx[def.key]?.let { tpl.header(def.header, it) }
}
@ -200,20 +222,21 @@ class ForgeContextAutoConfiguration {
* 通过装饰线程池任务确保异步任务继承调用线程的上下文状态
*
* @param processors 所有处理器列表
* @param contextHolder 上下文持有者
* @return 构建完成的 TaskDecorator 实例
*/
@Bean
fun contextTaskDecorator(processors: List<ContextProcessor>) =
fun contextTaskDecorator(processors: List<ContextProcessor>,contextHolder: ContextHolder) =
TaskDecorator { delegate ->
val snap = StringContextHolder.snapshot()
val snap = contextHolder.snapshot()
Runnable {
try {
snap.forEach(StringContextHolder::put)
snap.forEach(contextHolder::put)
processors.forEach { it.inject(Unit, snap.toMutableMap()) }
delegate.run()
} finally {
processors.forEach { it.inject(Unit, mutableMapOf()) }
StringContextHolder.clear()
contextHolder.clear()
}
}
}
@ -234,13 +257,13 @@ class ForgeContextAutoConfiguration {
* 注册 WebClientCustomizer用于定制 WebClient 的请求行为
*
* 在每次请求发出前将当前上下文字段写入 HTTP 请求头
*
*@param contextHolder 上下文持有者
* @return 构建完成的 WebClientCustomizer 实例
*/
@Bean
fun contextWebClientCustomizer() = WebClientCustomizer { builder ->
fun contextWebClientCustomizer(contextHolder: ContextHolder) = WebClientCustomizer { builder ->
builder.filter { req, next ->
val ctx = StringContextHolder.snapshot()
val ctx = contextHolder.snapshot()
val mutated = ClientRequest.from(req).apply {
registry.all().forEach { def ->
ctx[def.key]?.let { value -> header(def.header, value) }

View File

@ -1,8 +1,9 @@
dependencies {
compileOnly(project(Modules.Context.API))
compileOnly(project(Modules.Core.SERIALIZATION))
compileOnly(libs.springBootStarter.web)
compileOnly(libs.springBootStarter.webflux)
implementation(platform(libs.springBootDependencies.bom))
implementation(libs.springBoot.autoconfigure)
implementation(libs.jackson.databind)
}

View File

@ -0,0 +1,36 @@
package com.gewuyou.forgeboot.context.impl
import com.gewuyou.forgeboot.context.api.AbstractContext
import com.gewuyou.forgeboot.core.serialization.serializer.ValueSerializer
/**
* 上下文容器用于存储和获取键值对形式的字符串数据
*
* @since 2025-06-04 15:05:43
* @author gewuyou
*/
open class ContextHolder(
private val valueSerializer: ValueSerializer
) : AbstractContext<String, String>() {
/**
* 存储键值对到上下文中如果值为 null则不会存储
*
* @param key 要存储的键
* @param value 要存储的值可以为 null
*/
open fun put(key: String, value: Any?) {
super.put(key, value?.let { valueSerializer.serialize(it) })
}
/**
* 根据指定的键和类型从上下文中获取对应的值
*
* @param key 要查找的键
* @param type 要转换的目标类型
* @return 对应类型的值如果不存在或类型不匹配则返回 null
*/
override fun <T> retrieveByType(key: String, type: Class<T>): T? {
return retrieve(key)?.let { valueSerializer.deserialize(it, type) }
}
}

View File

@ -1,11 +0,0 @@
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>()

View File

@ -0,0 +1,34 @@
package com.gewuyou.forgeboot.context.impl.extension
import com.gewuyou.forgeboot.context.api.Context
/**
* 通过操作符重载实现 Context get 方法用于根据指定的键获取对应的值
*
* @param key 用于查找上下文中的值
* @return 返回与键对应的值如果不存在则返回 null
*/
operator fun <K, V> Context<K, V>.get(key: K): V? {
return this.retrieve(key)
}
/**
* 通过操作符重载实现 Context set 方法用于将键值对存储到上下文中
*
* @param key 用于标识要存储的值
* @param value 要存储的值可以为 null
*/
operator fun <K, V> Context<K, V>.set(key: K, value: V?) {
this.put(key, value)
}
/**
* 通过操作符重载实现 Context get 方法用于根据指定的键和类型获取对应的值
*
* @param key 用于查找上下文中的值
* @param type 指定的值类型用于类型安全地获取值
* @return 返回与键和类型对应的值如果不存在则返回 null
*/
operator fun <K, V, T> Context<K, V>.get(key: K, type: Class<T>) {
this.retrieveByType(key, type)
}

View File

@ -0,0 +1,13 @@
package com.gewuyou.forgeboot.context.impl.extension
import com.gewuyou.forgeboot.context.impl.ContextHolder
/**
* 为ContextHolder实现赋值操作符的重写方法
*
* @param key 存储值的键用于后续从上下文获取值时使用
* @param value 需要存储在ContextHolder中的值允许为null
*/
operator fun ContextHolder.set(key: String, value: Any?) {
this.put(key, value)
}

View File

@ -1,7 +1,7 @@
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.ContextHolder
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
@ -20,6 +20,7 @@ import org.springframework.web.filter.OncePerRequestFilter
*/
class ContextServletFilter(
private val chain: List<ContextProcessor>,
private val contextHolder: ContextHolder
) : OncePerRequestFilter() {
/**
@ -38,13 +39,13 @@ class ContextServletFilter(
filterChain: FilterChain,
) {
// 创建当前线程上下文快照的可变副本,确保后续操作不影响原始上下文
val ctx = StringContextHolder.snapshot().toMutableMap()
val ctx = contextHolder.snapshot().toMutableMap()
// 遍历上下文处理器链,依次从请求中提取上下文信息并更新临时上下文容器
chain.forEach { it.extract(request, ctx) }
// 将提取后的上下文写入当前线程的上下文持有者,供后续组件访问
ctx.forEach(StringContextHolder::put)
ctx.forEach(contextHolder::put)
// 调用下一个过滤器或最终的目标处理器
chain.forEach { it.inject(request, ctx) }
@ -56,7 +57,7 @@ class ContextServletFilter(
// 向处理器链注入空上下文,触发清理操作(如有)
chain.forEach { it.inject(Unit, mutableMapOf()) }
// 显式清除当前线程的上下文持有者,防止上下文泄露
StringContextHolder.clear()
contextHolder.clear()
}
}
}

View File

@ -1,7 +1,7 @@
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.ContextHolder
import com.gewuyou.forgeboot.context.impl.processor.ReactorProcessor
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
@ -22,7 +22,8 @@ import reactor.core.publisher.Mono
*/
class ContextWebFilter(
private val contextProcessors: List<ContextProcessor>,
private val reactorProc: ReactorProcessor
private val reactorProc: ReactorProcessor,
private val contextHolder: ContextHolder
) : WebFilter {
/**
* 执行过滤逻辑在请求链中插入上下文管理操作
@ -42,12 +43,12 @@ class ContextWebFilter(
exchange: ServerWebExchange,
chain: WebFilterChain,
): Mono<Void> {
// 从 StringContextHolder 快照获取当前上下文并转换为可变 Map
val ctx = StringContextHolder.snapshot().toMutableMap()
// 从 ContextHolder 快照获取当前上下文并转换为可变 Map
val ctx = contextHolder.snapshot().toMutableMap()
// 遍历所有 ContextProcessor从请求中提取上下文信息到 ctx
contextProcessors.forEach { it.extract(exchange, ctx) }
// 将上下文写入 StringContextHolderThreadLocal
ctx.forEach(StringContextHolder::put)
// 将上下文写入 ContextHolderThreadLocal
ctx.forEach(contextHolder::put)
// 使用 MdcProcessor 将上下文注入到 MDC 中
contextProcessors.forEach { it.inject(Unit, ctx) }
// 构建新的 ServerWebExchange 实例
@ -60,7 +61,7 @@ class ContextWebFilter(
.doFinally {
// 清理 ThreadLocal + MDC 上下文
contextProcessors.forEach { it.inject(Unit, mutableMapOf()) }
StringContextHolder.clear()
contextHolder.clear()
}
}
}

View File

@ -0,0 +1,3 @@
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary

View 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

View File

@ -0,0 +1,4 @@
dependencies {
implementation(libs.jackson.databind)
}

View File

@ -0,0 +1,17 @@
package com.gewuyou.forgeboot.core.serialization.exception
/**
* 序列化异常类用于封装与序列化操作相关的异常信息
*
* @param message 异常的详细描述信息默认为 null
* @param cause 导致此异常的底层异常默认为 null
*
* @since 2025-06-17 21:14:40
* @author gewuyou
*/
class SerializerException(
message: String? = null,
cause: Throwable? = null,
) : RuntimeException(
message, cause
)

View File

@ -0,0 +1,26 @@
package com.gewuyou.forgeboot.core.serialization.serializer
/**
* 值序列化器接口用于定义将对象序列化为字符串以及从字符串反序列化为对象的方法
*
* @since 2025-06-16 22:16:19
* @author gewuyou
*/
interface ValueSerializer {
/**
* 将给定的对象序列化为字符串
*
* @param value 要序列化的对象
* @return 序列化后的字符串表示形式
*/
fun serialize(value: Any): String
/**
* 将给定的字符串反序列化为对象
*
* @param value 要反序列化的字符串
* @return 反序列化后的对象
*/
fun <T> deserialize(value: String, type: Class<T>): T
}

View File

@ -0,0 +1,70 @@
package com.gewuyou.forgeboot.core.serialization.serializer.impl.serializer
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.core.serialization.exception.SerializerException
import com.gewuyou.forgeboot.core.serialization.serializer.ValueSerializer
/**
* Jackson 值序列化器用于将对象序列化为 JSON 字符串或将 JSON 字符串反序列化为对象
*
* @param mapper 用于执行序列化和反序列化的 Jackson ObjectMapper 实例
* @since 2025-06-18 12:37:59
* @author gewuyou
*/
class JacksonValueSerializer(
private val mapper: ObjectMapper,
) : ValueSerializer {
/**
* 将给定的对象序列化为字符串
*
* @param value 要序列化的对象
* @return 序列化后的字符串表示形式
*/
override fun serialize(value: Any): String {
// 对于基本类型或者 String直接返回
if (value is String || value.javaClass.isPrimitive) {
return value.toString()
}
return try {
mapper.writeValueAsString(value)
} catch (e: JsonProcessingException) {
throw SerializerException("Failed to serialize value: ${e.message}", e)
}
}
/**
* 将给定的字符串反序列化为对象
*
* @param value 要反序列化的字符串
* @return 反序列化后的对象
*/
@Suppress("UNCHECKED_CAST")
override fun <T> deserialize(value: String, type: Class<T>): T {
// 对于 String 类型,直接返回
if (type == String::class.java) {
return value as T
}
// 对于基本类型,使用相应的 valueOf 方法进行转换
if (type.isPrimitive) {
return when (type) {
Boolean::class.java -> value.toBoolean() as T
Integer::class.java, Int::class.java -> value.toInt() as T
Long::class.java, java.lang.Long::class.java -> value.toLong() as T
Double::class.java, java.lang.Double::class.java -> value.toDouble() as T
Float::class.java, java.lang.Float::class.java -> value.toFloat() as T
Short::class.java, java.lang.Short::class.java -> value.toShort() as T
Byte::class.java, java.lang.Byte::class.java -> value.toByte() as T
Character::class.java -> value[0] as T // 取第一个字符作为字符
else -> throw SerializerException("Unsupported primitive type: ${type.name}")
}
}
return try {
mapper.readValue(value, type)
} catch (e: JsonProcessingException) {
throw SerializerException("Failed to deserialize value: ${e.message}", e)
}
}
}

View File

@ -4,6 +4,7 @@ plugins {
dependencies {
implementation(platform(libs.springBootDependencies.bom))
implementation(libs.springBoot.autoconfigure)
implementation(project(Modules.Context.STARTER))
compileOnly(platform(libs.springCloudDependencies.bom))
compileOnly(libs.springBootStarter.web)
compileOnly(libs.springBootStarter.webflux)

View File

@ -4,6 +4,7 @@ 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.context.impl.ContextHolder
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
@ -24,27 +25,39 @@ import java.util.UUID
@Configuration
@EnableConfigurationProperties(TraceProperties::class)
class TraceAutoConfiguration(
private val traceProperties: TraceProperties
private val traceProperties: TraceProperties,
) {
/**
* 请求ID提供者用于生成请求ID
* 创建请求ID提供者Bean
*
* 该提供者用于生成请求ID默认为TraceRequestIdProvider
* @return RequestIdProvider实例
* 用于生成分布式请求链路追踪所需的唯一请求标识
* @param contextHolder 上下文持有者用于跨组件传递请求上下文
* @return 初始化完成的TraceRequestIdProvider实例
*/
@Bean
@ConditionalOnMissingBean(RequestIdProvider::class)
fun traceRequestIdProvider(): TraceRequestIdProvider {
fun traceRequestIdProvider(contextHolder: ContextHolder): TraceRequestIdProvider {
log.info("TraceRequestIdProvider 已创建!")
return TraceRequestIdProvider(traceProperties)
return TraceRequestIdProvider(traceProperties,contextHolder)
}
/**
* 创建请求上下文贡献者Bean
*
* 定义请求上下文中需要维护的字段定义集合
* @return ContextFieldContributor实例包含完整的上下文字段定义
*/
@Bean
fun requestContributor() = ContextFieldContributor {
setOf(
FieldDef(
header = traceProperties.requestIdHeaderName, // 请求-响应头名
key = traceProperties.requestIdMdcKey, // ctx/MDC 键
generator = { UUID.randomUUID().toString() }, // 如果前端没带,用这个生成
// 请求-响应头名
header = traceProperties.requestIdHeaderName,
// ctx/MDC 键
key = traceProperties.requestIdMdcKey,
// 前端未携带时的生成策略
generator = { UUID.randomUUID().toString() },
// 作用域范围
scopes = setOf(Scope.HEADER, Scope.MDC, Scope.REACTOR)
)
)

View File

@ -1,7 +1,7 @@
dependencies {
implementation(platform(libs.springBootDependencies.bom))
api(project(Modules.Core.EXTENSION))
api(project(Modules.Context.STARTER))
compileOnly(project(Modules.Context.STARTER))
compileOnly(project(Modules.TRACE.API))
compileOnly(platform(libs.springCloudDependencies.bom))
compileOnly(libs.springBootStarter.webflux)
@ -9,5 +9,4 @@ dependencies {
// Spring Cloud OpenFeign (Compile Only)
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign
compileOnly(libs.springCloudStarter.openfeign)
kapt(libs.springBoot.configuration.processor)
}

View File

@ -1,6 +1,7 @@
package com.gewuyou.forgeboot.trace.impl.provider
import com.gewuyou.forgeboot.context.impl.StringContextHolder
import com.gewuyou.forgeboot.context.api.extension.get
import com.gewuyou.forgeboot.context.impl.ContextHolder
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
@ -12,7 +13,8 @@ import com.gewuyou.forgeboot.trace.api.config.TraceProperties
* @author gewuyou
*/
class TraceRequestIdProvider(
private val traceProperties: TraceProperties
private val traceProperties: TraceProperties,
private val contextHolder: ContextHolder
): RequestIdProvider {
/**
* 获取请求ID
@ -22,6 +24,6 @@ class TraceRequestIdProvider(
* @return 请求ID的字符串表示
*/
override fun getRequestId(): String {
return StringContextHolder.get(traceProperties.requestIdMdcKey) ?:throw RuntimeException("requestId is null")
return contextHolder[traceProperties.requestIdMdcKey] ?:throw RuntimeException("requestId is null")
}
}

View File

@ -117,6 +117,27 @@ data class R<T>(
val extra = buildExtraMap(extenders)
return R(code, true, message, data, reqId, extra)
}
/**
* 创建成功响应对象
*
* @param code 响应码
* @param message 消息
* @param data 响应数据
* @param requestIdProvider 请求ID提供者
* @param extenders 扩展信息提供者列表
* @return 成功响应对象
*/
fun <T> success(
data: T? = null,
code: Int = 200,
message: String = "success",
requestIdProvider: RequestIdProvider? = null,
extenders: List<ResultExtender> = emptyList(),
): R<T> {
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
val extra = buildExtraMap(extenders)
return R(code, true, message, data, reqId, extra)
}
/**
* 创建失败响应对象

View File

@ -99,25 +99,42 @@ fun <T, S> PageQueryReq<T>.getPredicates(
): MutableList<Predicate> {
val predicates = mutableListOf<Predicate>()
// 添加开始日期条件
// 添加开始日期条件(容错)
startDate?.let {
predicates.add(builder.greaterThanOrEqualTo(root.get(createAtName), it))
try {
predicates.add(builder.greaterThanOrEqualTo(root.get(createAtName), it))
} catch (_: IllegalArgumentException) {
// 字段不存在,忽略
}
}
// 添加结束日期条件
// 添加结束日期条件(容错)
endDate?.let {
predicates.add(builder.lessThanOrEqualTo(root.get(createAtName), it))
try {
predicates.add(builder.lessThanOrEqualTo(root.get(createAtName), it))
} catch (_: IllegalArgumentException) {
// 字段不存在,忽略
}
}
// 添加是否启用条件
// 添加是否启用条件(通常字段存在,可不做容错)
enabled?.let {
predicates.add(builder.equal(root.get<Boolean>(enabledName), it))
try {
predicates.add(builder.equal(root.get<Boolean>(enabledName), it))
} catch (_: IllegalArgumentException) {
// 可选容错
}
}
// 添加是否删除条件
// 添加是否删除条件(通常字段存在,可不做容错)
deleted.let {
predicates.add(builder.equal(root.get<Boolean>(deletedName), it))
try {
predicates.add(builder.equal(root.get<Boolean>(deletedName), it))
} catch (_: IllegalArgumentException) {
// 可选容错
}
}
return predicates
}

View File

@ -3,9 +3,9 @@ plugins{
}
dependencies {
implementation(project(Modules.Core.EXTENSION))
implementation(project(Modules.Webmvc.DTO))
implementation(platform(libs.springBootDependencies.bom))
implementation(libs.springBoot.autoconfigure)
api(project(Modules.Webmvc.DTO))
api(project(Modules.TRACE.STARTER))
compileOnly(libs.springBootStarter.validation)
compileOnly(libs.springBootStarter.web)

View File

@ -16,13 +16,6 @@ class WebMvcExceptionProperties {
}
/**
* 是否启用国际化
*
* @param enable 是否启用国际化
*/
var enable: Boolean = false
/**
* 设置其他通用外部异常的错误代码
*

View File

@ -3,6 +3,8 @@
# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format
[versions]
jjwt-version = "0.12.6"
gradleMavenPublishPlugin-version="0.32.0"
kotlin-version = "2.0.0"
kotlinxDatetime-version = "0.6.1"
kotlinxSerializationJSON-version = "1.7.3"
@ -10,8 +12,11 @@ kotlinxSerializationJSON-version = "1.7.3"
axion-release-version = "1.18.7"
spring-cloud-version = "2024.0.1"
spring-boot-version = "3.4.4"
spring-framework-version = "6.2.5"
slf4j-version = "2.0.17"
map-struct-version="1.6.3"
caffeine-version="3.2.1"
redisson-version="3.50.0"
[libraries]
kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin-version" }
kotlinxDatetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime-version" }
@ -27,12 +32,18 @@ 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-security = { group = "org.springframework.boot", name = "spring-boot-starter-security" }
springBootStarter-webflux = { group = "org.springframework.boot", name = "spring-boot-starter-webflux" }
springBootStarter-jpa = { group = "org.springframework.boot", name = "spring-boot-starter-data-jpa" }
springBootStarter-validation = { group = "org.springframework.boot", name = "spring-boot-starter-validation" }
springBootStarter-redis = { group = "org.springframework.boot", name = "spring-boot-starter-data-redis" }
redisson-springBootStarter= { group = "org.redisson", name = "redisson-spring-boot-starter", version.ref = "redisson-version" }
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" }
springExpression = { group = "org.springframework", name = "spring-expression", version.ref = "spring-framework-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" }
@ -47,12 +58,20 @@ reactor-core = { group = "io.projectreactor", name = "reactor-core" }
#org
org-mapstruct = { group = "org.mapstruct", name = "mapstruct", version.ref = "map-struct-version" }
# Libraries can be bundled together for easier import
# jwt
jjwt-api = { module = "io.jsonwebtoken:jjwt-api", version.ref = "jjwt-version" }
jjwt-impl = { module = "io.jsonwebtoken:jjwt-impl", version.ref = "jjwt-version" }
jjwt-jackson = { module = "io.jsonwebtoken:jjwt-jackson", version.ref = "jjwt-version" }
# com
com-github-benManes-caffeine = { module = "com.github.ben-manes.caffeine:caffeine",version.ref = "caffeine-version" }
[bundles]
kotlinxEcosystem = ["kotlinxDatetime", "kotlinxSerialization", "kotlinxCoroutines-core"]
jacksonAll = [
"jackson-core", "jackson-databind", "jackson-annotations", "jackson-datatype-jsr310", "jackson-module-kotlin"
]
jjwtAll = [
"jjwt-api", "jjwt-impl", "jjwt-jackson"
]
[plugins]
# 应用 Java 插件,提供基本的 Java 代码编译和构建能力
java = { id = "java" }
@ -71,3 +90,5 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" }
kotlin-plugin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin-version" }
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt" }
forgeboot-i18n-keygen = { id = "i18n-key-gen" }
gradleMavenPublishPlugin={id="com.vanniktech.maven.publish", version.ref="gradleMavenPublishPlugin-version"}

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME