mirror of
https://github.moeyy.xyz/https://github.com/GeWuYou/forgeboot
synced 2025-10-27 19:24:23 +08:00
Compare commits
10 Commits
8a449467ab
...
21b79551a5
| Author | SHA1 | Date | |
|---|---|---|---|
| 21b79551a5 | |||
| bdba342daa | |||
| ed8bb7cc91 | |||
| f6794fd14f | |||
| 7a6c371aa3 | |||
| 92b3eedb38 | |||
| 82f2a50f48 | |||
| d980363a33 | |||
| 05ab150b8e | |||
| 6fcf5d6d40 |
@ -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/")
|
||||
}
|
||||
|
||||
@ -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]
|
||||
}
|
||||
|
||||
|
||||
@ -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?
|
||||
|
||||
/**
|
||||
* 获取当前上下文的一个快照,包含所有键值对。
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -10,4 +10,5 @@ dependencies {
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
compileOnly(libs.springCloudStarter.openfeign)
|
||||
api(project(Modules.Core.SERIALIZATION))
|
||||
}
|
||||
|
||||
@ -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) }
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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) }
|
||||
}
|
||||
}
|
||||
@ -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>()
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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) }
|
||||
// 将上下文写入 StringContextHolder(ThreadLocal)
|
||||
ctx.forEach(StringContextHolder::put)
|
||||
// 将上下文写入 ContextHolder(ThreadLocal)
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
3
forgeboot-core/forgeboot-core-serialization/.gitattributes
vendored
Normal file
3
forgeboot-core/forgeboot-core-serialization/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/gradlew text eol=lf
|
||||
*.bat text eol=crlf
|
||||
*.jar binary
|
||||
40
forgeboot-core/forgeboot-core-serialization/.gitignore
vendored
Normal file
40
forgeboot-core/forgeboot-core-serialization/.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,4 @@
|
||||
|
||||
dependencies {
|
||||
implementation(libs.jackson.databind)
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
)
|
||||
)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败响应对象
|
||||
|
||||
@ -99,25 +99,42 @@ fun <T, S> PageQueryReq<T>.getPredicates(
|
||||
): MutableList<Predicate> {
|
||||
val predicates = mutableListOf<Predicate>()
|
||||
|
||||
// 添加开始日期条件
|
||||
// 添加开始日期条件(容错)
|
||||
startDate?.let {
|
||||
try {
|
||||
predicates.add(builder.greaterThanOrEqualTo(root.get(createAtName), it))
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// 字段不存在,忽略
|
||||
}
|
||||
}
|
||||
|
||||
// 添加结束日期条件
|
||||
// 添加结束日期条件(容错)
|
||||
endDate?.let {
|
||||
try {
|
||||
predicates.add(builder.lessThanOrEqualTo(root.get(createAtName), it))
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// 字段不存在,忽略
|
||||
}
|
||||
}
|
||||
|
||||
// 添加是否启用条件
|
||||
// 添加是否启用条件(通常字段存在,可不做容错)
|
||||
enabled?.let {
|
||||
try {
|
||||
predicates.add(builder.equal(root.get<Boolean>(enabledName), it))
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// 可选容错
|
||||
}
|
||||
}
|
||||
|
||||
// 添加是否删除条件
|
||||
// 添加是否删除条件(通常字段存在,可不做容错)
|
||||
deleted.let {
|
||||
try {
|
||||
predicates.add(builder.equal(root.get<Boolean>(deletedName), it))
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// 可选容错
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return predicates
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -16,13 +16,6 @@ class WebMvcExceptionProperties {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用国际化
|
||||
*
|
||||
* @param enable 是否启用国际化
|
||||
*/
|
||||
var enable: Boolean = false
|
||||
|
||||
/**
|
||||
* 设置其他通用外部异常的错误代码
|
||||
*
|
||||
|
||||
@ -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"}
|
||||
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user