mirror of
https://github.moeyy.xyz/https://github.com/GeWuYou/forgeboot
synced 2025-10-27 19:24:23 +08:00
Compare commits
No commits in common. "58593f7b218de8bc3f1c49a5e017f59f300f5325" and "88f016dad2b80c97f308b7e096143b651b0f074d" have entirely different histories.
58593f7b21
...
88f016dad2
@ -8,13 +8,6 @@ plugins {
|
||||
alias(libs.plugins.axionRelease)
|
||||
// Kotlin Spring 支持
|
||||
alias(libs.plugins.kotlin.plugin.spring)
|
||||
// Kotlin kapt 支持
|
||||
alias(libs.plugins.kotlin.kapt)
|
||||
id(libs.plugins.kotlin.jvm.get().pluginId)
|
||||
}
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
// 配置 SCM 版本插件
|
||||
@ -22,7 +15,7 @@ scmVersion {
|
||||
tag {
|
||||
prefix.set("") // 不加 v,生成 1.0.1 而不是 v1.0.1
|
||||
}
|
||||
versionIncrementer("incrementPatch")
|
||||
versionIncrementer("incrementMinorIfNotOnRelease")
|
||||
hooks {
|
||||
pre(
|
||||
"fileUpdate", mapOf(
|
||||
@ -64,12 +57,14 @@ allprojects {
|
||||
|
||||
// 子项目配置
|
||||
subprojects {
|
||||
version = rootProject.version
|
||||
afterEvaluate {
|
||||
val isRootModule = project.getPropertyByBoolean(ProjectFlags.IS_ROOT_MODULE)
|
||||
val isStarterModule = project.name.contains("starter")
|
||||
val parentProject = project.parent
|
||||
// 让父项目引入子项目
|
||||
parentProject?.dependencies?.add("api", project(project.path))
|
||||
if (!isRootModule) {
|
||||
if (isStarterModule && !isRootModule) {
|
||||
// Starter 模块依赖配置
|
||||
dependencies {
|
||||
implementation(platform(libs.springBootDependencies.bom))
|
||||
@ -80,41 +75,25 @@ subprojects {
|
||||
}
|
||||
// 应用插件和配置
|
||||
val libs = rootProject.libs
|
||||
plugins.withId(libs.plugins.java.get().pluginId) {
|
||||
extensions.configure<JavaPluginExtension> {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
}
|
||||
plugins.withId(libs.plugins.forgeboot.i18n.keygen.get().pluginId) {
|
||||
tasks.named("kotlinSourcesJar") {
|
||||
dependsOn("generateI18nKeys")
|
||||
}
|
||||
tasks.named<Jar>("sourcesJar") {
|
||||
dependsOn("generateI18nKeys")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
version = rootProject.version
|
||||
|
||||
apply {
|
||||
plugin(libs.plugins.java.get().pluginId)
|
||||
plugin(libs.plugins.javaLibrary.get().pluginId)
|
||||
plugin(libs.plugins.maven.publish.get().pluginId)
|
||||
plugin(libs.plugins.kotlin.jvm.get().pluginId)
|
||||
plugin(libs.plugins.axionRelease.get().pluginId)
|
||||
plugin(libs.plugins.kotlin.kapt.get().pluginId)
|
||||
// 导入仓库配置
|
||||
from(file("$configDir/repositories.gradle.kts"))
|
||||
// 导入源代码任务
|
||||
from(file("$tasksDir/sourceTask.gradle.kts"))
|
||||
}
|
||||
|
||||
// 发布配置
|
||||
publishing {
|
||||
repositories {
|
||||
// 本地仓库
|
||||
maven {
|
||||
name = "localRepo"
|
||||
url = uri("file://D:/Config/Jrebel/.jrebel/.m2/repository")
|
||||
url = uri("file://D:/Maven/mvn_repository")
|
||||
}
|
||||
// GitHub Packages 仓库
|
||||
maven {
|
||||
|
||||
@ -13,13 +13,3 @@ dependencies {
|
||||
// Add a dependency on the Kotlin Gradle plugin, so that convention plugins can apply it.
|
||||
implementation(libs.kotlinGradlePlugin)
|
||||
}
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
register("forgeboot-i18n-key-gen") {
|
||||
id = "i18n-key-gen"
|
||||
implementationClass = "I18nKeyGenPlugin"
|
||||
description =
|
||||
"提供一个用于生成 i18n key文件 的插件"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,184 +0,0 @@
|
||||
/**
|
||||
* 本地化键生成插件
|
||||
*
|
||||
* 该插件用于自动生成本地化键的代码,以简化国际化(i18n)过程
|
||||
* 它通过读取属性文件来生成一个包含所有本地化键的Kotlin文件
|
||||
* 此方法便于集中管理和自动生成代码,避免手动编写易出错的字符串字面量
|
||||
*
|
||||
* @since 2025-05-28 17:11:40
|
||||
* @author gewuyou
|
||||
*/
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.*
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 可缓存的任务类,用于生成本地化键
|
||||
*
|
||||
* 该任务读取输入文件中的属性,并根据这些属性生成一个Kotlin文件
|
||||
* 文件中包含根据属性键生成的常量,这些键按指定的分组深度和级别进行组织
|
||||
*/
|
||||
@CacheableTask
|
||||
abstract class GenerateI18nKeysTask : DefaultTask() {
|
||||
|
||||
/**
|
||||
* 输入文件属性,包含本地化属性
|
||||
* 被注解为@InputFile和@PathSensitive,以确保Gradle可以正确地检测文件路径的变化
|
||||
*/
|
||||
@get:InputFile
|
||||
@get:PathSensitive(PathSensitivity.RELATIVE)
|
||||
abstract val inputFile: RegularFileProperty
|
||||
|
||||
/**
|
||||
* 输出文件属性,将生成的代码写入此文件
|
||||
* 被注解为@OutputFile,以告知Gradle该文件是任务的输出
|
||||
*/
|
||||
@get:OutputFile
|
||||
abstract val outputFile: RegularFileProperty
|
||||
|
||||
/**
|
||||
* 根包名属性,用于指定生成代码的包路径
|
||||
* 被注解为@Input,因为包路径的变化应导致任务重新执行
|
||||
*/
|
||||
@get:Input
|
||||
abstract val rootPackage: Property<String>
|
||||
|
||||
/**
|
||||
* 层级属性,决定键的分组深度
|
||||
* 注解为@Input,因为层级变化会影响生成的代码结构
|
||||
*/
|
||||
@get:Input
|
||||
abstract val level: Property<Int>
|
||||
|
||||
/**
|
||||
* 任务的主要执行方法
|
||||
*
|
||||
* 该方法读取输入文件中的属性,然后根据这些属性生成一个Kotlin文件
|
||||
* 它首先加载属性,然后根据指定的包路径、层级和分组深度生成代码
|
||||
*/
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
// 加载属性文件
|
||||
val props = Properties().apply {
|
||||
inputFile.get().asFile.inputStream().use { load(it) }
|
||||
}
|
||||
|
||||
// 准备输出文件
|
||||
val output = outputFile.get().asFile
|
||||
output.parentFile.mkdirs()
|
||||
|
||||
// 处理属性键,按分组深度分组
|
||||
val keys = props.stringPropertyNames().sorted()
|
||||
val levelDepth = level.get()
|
||||
val indentUnit = " "
|
||||
|
||||
fun Appendable.generateObjects(keys: List<String>, depth: Int) {
|
||||
val groups = keys.groupBy { it.split(".").take(depth).joinToString(".") }
|
||||
|
||||
groups.forEach { (groupKey, groupKeys) ->
|
||||
val segments = groupKey.split(".")
|
||||
val indent = indentUnit.repeat(segments.size)
|
||||
val objectName = segments.last().replaceFirstChar(Char::uppercase)
|
||||
|
||||
appendLine("$indent object $objectName {")
|
||||
|
||||
val deeperKeys = groupKeys.filter { it.split(".").size > depth }
|
||||
|
||||
if (depth < levelDepth && deeperKeys.isNotEmpty()) {
|
||||
generateObjects(deeperKeys, depth + 1)
|
||||
} else {
|
||||
groupKeys.forEach { fullKey ->
|
||||
val constName = fullKey
|
||||
.split(".")
|
||||
.drop(levelDepth)
|
||||
.joinToString("_") { it.uppercase() }
|
||||
.ifBlank { fullKey.replace(".", "_").uppercase() }
|
||||
|
||||
appendLine("${indent}$indentUnit const val $constName = \"$fullKey\"")
|
||||
}
|
||||
}
|
||||
|
||||
appendLine("$indent }")
|
||||
}
|
||||
}
|
||||
// 生成代码并写入输出文件
|
||||
output.writeText(buildString {
|
||||
appendLine("package ${rootPackage.get()}")
|
||||
appendLine("// ⚠️ Auto-generated. Do not edit manually.")
|
||||
appendLine("object I18nKeys {")
|
||||
generateObjects(keys, 1)
|
||||
appendLine("}")
|
||||
})
|
||||
|
||||
println("✅ Generated I18nKeys.kt at ${output.absolutePath}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展类,用于配置插件
|
||||
*
|
||||
* 该扩展定义了插件的可配置属性,包括层级、分组深度和根包名
|
||||
*/
|
||||
interface I18nKeyGenExtension {
|
||||
val level: Property<Int>
|
||||
val rootPackage: Property<String>
|
||||
val inputPath: Property<String>
|
||||
val fileName: Property<String>
|
||||
val readPath: Property<String>
|
||||
val readFileName: Property<String>
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地化键生成插件的实现
|
||||
*
|
||||
* 该插件在项目中添加扩展和任务,以生成本地化键的代码
|
||||
*/
|
||||
// ---- 插件主体 ----
|
||||
class I18nKeyGenPlugin : Plugin<Project> {
|
||||
override fun apply(project: Project) {
|
||||
val ext = project.extensions.create("i18nKeyGen", I18nKeyGenExtension::class.java).apply {
|
||||
level.convention(2)
|
||||
rootPackage.convention("com.gewuyou.i18n")
|
||||
inputPath.convention("generated/i18n") // 子路径
|
||||
fileName.convention("I18nKeys.kt")
|
||||
readPath.convention("src/main/resources/i18n")
|
||||
readFileName.convention("messages.properties")
|
||||
}
|
||||
|
||||
project.afterEvaluate {
|
||||
val propsFile = project.file("${ext.readPath.get()}/${ext.readFileName.get()}")
|
||||
if (!propsFile.exists()) return@afterEvaluate
|
||||
|
||||
// 输出文件路径
|
||||
val outputFile = project.layout.buildDirectory.file("${ext.inputPath.get()}/${ext.fileName.get()}")
|
||||
val outputDir = project.layout.buildDirectory.dir(ext.inputPath)
|
||||
|
||||
// 注册生成任务
|
||||
val generateTask = project.tasks.register("generateI18nKeys", GenerateI18nKeysTask::class.java) {
|
||||
setProperty("inputFile", propsFile)
|
||||
setProperty("outputFile", outputFile)
|
||||
setProperty("level", ext.level)
|
||||
setProperty("rootPackage", ext.rootPackage)
|
||||
}
|
||||
|
||||
// 👇 确保编译前依赖该任务
|
||||
listOf("compileKotlin", "kaptGenerateStubsKotlin").forEach { name ->
|
||||
project.tasks.matching { it.name == name }.configureEach {
|
||||
dependsOn(generateTask)
|
||||
}
|
||||
}
|
||||
|
||||
// 👇 延迟注册源码目录:使用 map 避免提前访问目录
|
||||
project.plugins.withId("org.jetbrains.kotlin.jvm") {
|
||||
val kotlinExt = project.extensions.getByType(KotlinProjectExtension::class.java)
|
||||
kotlinExt.sourceSets.getByName("main").kotlin.srcDir(outputDir.map { it.asFile })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,25 +9,17 @@ object Modules {
|
||||
|
||||
object Webmvc {
|
||||
const val STARTER = ":forgeboot-webmvc-spring-boot-starter"
|
||||
const val DTO = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-dto"
|
||||
const val VALIDATION = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-validation"
|
||||
const val VERSION = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-version-spring-boot-starter"
|
||||
const val LOGGER = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-logger-spring-boot-starter"
|
||||
const val VERSION_STARTER = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-version-spring-boot-starter"
|
||||
const val LOGGER_STARTER = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-logger-spring-boot-starter"
|
||||
}
|
||||
object Core{
|
||||
const val ROOT = ":forgeboot-core"
|
||||
const val EXTENSION = ":forgeboot-core:forgeboot-core-extension"
|
||||
}
|
||||
object I18n {
|
||||
const val STARTER = ":forgeboot-i18n-spring-boot-starter"
|
||||
const val API = ":forgeboot-i18n-spring-boot-starter:forgeboot-i18n-api"
|
||||
const val IMPL = ":forgeboot-i18n-spring-boot-starter:forgeboot-i18n-impl"
|
||||
const val AUTOCONFIGURE = ":forgeboot-i18n-spring-boot-starter:forgeboot-i18n-autoconfigure"
|
||||
}
|
||||
object TRACE{
|
||||
const val STARTER = ":forgeboot-trace-spring-boot-starter"
|
||||
const val API = ":forgeboot-trace-spring-boot-starter:forgeboot-trace-api"
|
||||
const val IMPL = ":forgeboot-trace-spring-boot-starter:forgeboot-trace-impl"
|
||||
const val AUTOCONFIGURE = ":forgeboot-trace-spring-boot-starter:forgeboot-trace-autoconfigure"
|
||||
object Common {
|
||||
const val ROOT = ":forgeboot-common"
|
||||
const val RESULT = ":forgeboot-common:forgeboot-common-result"
|
||||
const val RESULT_IMPL = ":forgeboot-common:forgeboot-common-result:forgeboot-common-result-impl"
|
||||
const val RESULT_API = ":forgeboot-common:forgeboot-common-result:forgeboot-common-result-api"
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,3 @@
|
||||
object ProjectFlags {
|
||||
const val IS_ROOT_MODULE = "isRootModule"
|
||||
const val IS_PUBLISH_MODULE = "isPublishModule"
|
||||
const val ARTIFACT_ID = "artifactId"
|
||||
}
|
||||
@ -8,6 +8,12 @@ repositories {
|
||||
isAllowInsecureProtocol = true
|
||||
}
|
||||
}
|
||||
maven {
|
||||
url = uri("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
|
||||
}
|
||||
maven {
|
||||
url = uri("https://raw.githubusercontent.com/eurotech/kura_addons/mvn-repo/")
|
||||
}
|
||||
maven {
|
||||
url = uri("https://maven.aliyun.com/repository/public/")
|
||||
}
|
||||
|
||||
19
config/tasks/sourceTask.gradle.kts
Normal file
19
config/tasks/sourceTask.gradle.kts
Normal file
@ -0,0 +1,19 @@
|
||||
// This task creates a jar file with the source code of the project
|
||||
|
||||
tasks.register<Jar>("sourceTask") {
|
||||
logger.info("正在配置${project.name}源代码 jar 文件...")
|
||||
|
||||
// 收集所有源代码(包括 Kotlin 和 Java)
|
||||
val sourceSet = project.extensions.getByType<SourceSetContainer>()["main"]
|
||||
from(sourceSet.allSource) {
|
||||
into("sources") // 将所有源代码放入子目录 sources
|
||||
}
|
||||
|
||||
archiveClassifier.set("sources") // 设置生成文件的分类标识
|
||||
|
||||
logger.info("正在创建${project.name}源代码 jar 文件...")
|
||||
logger.info("创建${project.name}源代码 jar 文件完成!")
|
||||
}.configure {
|
||||
group = "source"
|
||||
description = "使用项目的源代码创建源代码 jar 文件"
|
||||
}
|
||||
7
forgeboot-common/build.gradle.kts
Normal file
7
forgeboot-common/build.gradle.kts
Normal file
@ -0,0 +1,7 @@
|
||||
extra {
|
||||
// 标记为根项目
|
||||
setProperty(ProjectFlags.IS_ROOT_MODULE, true)
|
||||
}
|
||||
dependencies {
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
extra {
|
||||
// 标记为根项目
|
||||
setProperty(ProjectFlags.IS_ROOT_MODULE, true)
|
||||
}
|
||||
dependencies {
|
||||
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
|
||||
dependencies {
|
||||
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.gewuyou.forgeboot.i18n.api
|
||||
package com.gewuyou.forgeboot.common.result.api
|
||||
/**
|
||||
* 消息解析器接口
|
||||
*
|
||||
@ -1,4 +1,4 @@
|
||||
package com.gewuyou.forgeboot.trace.api
|
||||
package com.gewuyou.forgeboot.common.result.api
|
||||
/**
|
||||
* 请求ID提供商接口
|
||||
*
|
||||
@ -1,4 +1,4 @@
|
||||
package com.gewuyou.forgeboot.i18n.api
|
||||
package com.gewuyou.forgeboot.common.result.api
|
||||
|
||||
/**
|
||||
* 响应信息
|
||||
@ -6,7 +6,7 @@ package com.gewuyou.forgeboot.i18n.api
|
||||
* @author gewuyou
|
||||
* @since 2025-05-03 16:31:59
|
||||
*/
|
||||
interface I18nResponseInformation {
|
||||
interface ResponseInformation {
|
||||
/**
|
||||
* 获取响应码
|
||||
* @return 响应码
|
||||
@ -18,10 +18,4 @@ interface I18nResponseInformation {
|
||||
* @return 响应信息 code
|
||||
*/
|
||||
val responseI8nMessageCode: String
|
||||
|
||||
/**
|
||||
* 获取i18n响应信息参数
|
||||
* @return 响应信息 参数数组
|
||||
*/
|
||||
val responseI8nMessageArgs: Array<Any>?
|
||||
}
|
||||
@ -1,6 +1,4 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto
|
||||
|
||||
|
||||
package com.gewuyou.forgeboot.common.result.api
|
||||
/**
|
||||
* 结果扩展器
|
||||
*
|
||||
@ -20,4 +18,4 @@ fun interface ResultExtender {
|
||||
* @param resultMap 一个包含结果数据的可变映射,可以在此方法中对其进行修改
|
||||
*/
|
||||
fun extend(resultMap: MutableMap<String, Any?>)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
dependencies {
|
||||
implementation(project(Modules.Common.RESULT_API))
|
||||
}
|
||||
|
||||
@ -1,107 +1,47 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto
|
||||
package com.gewuyou.forgeboot.common.result
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.i18n.api.MessageResolver
|
||||
import com.gewuyou.forgeboot.i18n.api.I18nResponseInformation
|
||||
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
|
||||
import com.gewuyou.forgeboot.webmvc.dto.i18n.I18nKeys
|
||||
|
||||
|
||||
/**
|
||||
*默认请求ID提供商
|
||||
*
|
||||
* @since 2025-05-03 16:22:18
|
||||
* @author gewuyou
|
||||
*/
|
||||
val DefaultRequestIdProvider : RequestIdProvider = RequestIdProvider{""}
|
||||
|
||||
/**
|
||||
*默认消息解析器
|
||||
*
|
||||
* @since 2025-05-03 16:21:43
|
||||
* @author gewuyou
|
||||
*/
|
||||
val DefaultMessageResolver : MessageResolver = MessageResolver { code, _ -> code }
|
||||
|
||||
/**
|
||||
* 默认成功i18n响应信息对象
|
||||
*
|
||||
* 提供标准的成功响应定义,包含国际化支持
|
||||
* 响应码为200,表示操作成功
|
||||
* i18n消息码用于查找本地化文本
|
||||
*
|
||||
* @since 2025-05-03 16:23:00
|
||||
* @author gewuyou
|
||||
*/
|
||||
val defaultOkI18nResponseInformation: I18nResponseInformation = object : I18nResponseInformation {
|
||||
/**
|
||||
* 获取响应码
|
||||
* @return 响应码
|
||||
*/
|
||||
override val responseCode: Int
|
||||
get() = 200
|
||||
|
||||
/**
|
||||
* 获取i18n响应信息code
|
||||
* @return 响应信息 code
|
||||
*/
|
||||
override val responseI8nMessageCode: String
|
||||
get() = I18nKeys.Forgeboot.Webmvc.Dto.RESULT_RESPONSEINFO_OK
|
||||
|
||||
/**
|
||||
* 获取i18n响应信息参数
|
||||
* @return 响应信息 参数数组
|
||||
*/
|
||||
override val responseI8nMessageArgs: Array<Any>?
|
||||
get() = arrayOf()
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认失败i18n响应信息对象
|
||||
*
|
||||
* 提供标准的失败响应定义,包含国际化支持
|
||||
* 响应码为400,表示请求错误
|
||||
* i18n消息码用于查找本地化文本
|
||||
*
|
||||
* @since 2025-05-03 16:23:15
|
||||
* @author gewuyou
|
||||
*/
|
||||
val defaultFailureI18nResponseInformation: I18nResponseInformation = object : I18nResponseInformation {
|
||||
/**
|
||||
* 获取响应码
|
||||
* @return 响应码
|
||||
*/
|
||||
override val responseCode: Int
|
||||
get() = 400
|
||||
|
||||
/**
|
||||
* 获取i18n响应信息code
|
||||
* @return 响应信息 code
|
||||
*/
|
||||
override val responseI8nMessageCode: String
|
||||
get() = I18nKeys.Forgeboot.Webmvc.Dto.RESULT_RESPONSEINFO_FAIL
|
||||
|
||||
/**
|
||||
* 获取i18n响应信息参数
|
||||
* @return 响应信息 参数数组
|
||||
*/
|
||||
override val responseI8nMessageArgs: Array<Any>?
|
||||
get() = arrayOf()
|
||||
}
|
||||
import com.gewuyou.forgeboot.common.result.api.MessageResolver
|
||||
import com.gewuyou.forgeboot.common.result.api.RequestIdProvider
|
||||
import com.gewuyou.forgeboot.common.result.api.ResponseInformation
|
||||
import com.gewuyou.forgeboot.common.result.api.ResultExtender
|
||||
import com.gewuyou.forgeboot.common.result.impl.DefaultMessageResolver
|
||||
import com.gewuyou.forgeboot.common.result.impl.DefaultRequestIdProvider
|
||||
|
||||
/**
|
||||
* 统一响应封装类
|
||||
*
|
||||
* @since 2025-05-03 16:04:42
|
||||
*/
|
||||
data class I18nResult<T>(
|
||||
override val code: Int,
|
||||
override val success: Boolean,
|
||||
override val message: String,
|
||||
override val data: T? = null,
|
||||
override val requestId: String? = null,
|
||||
override val extra: Map<String, Any?> = emptyMap(),
|
||||
) : BaseResult<T>(code, success, message, data, requestId, extra) {
|
||||
data class R<T>(
|
||||
val code: Int,
|
||||
val success: Boolean,
|
||||
val message: String,
|
||||
val data: T? = null,
|
||||
val requestId: String? = null,
|
||||
val extra: Map<String, Any?> = emptyMap()
|
||||
) {
|
||||
/**
|
||||
* 转换为可变 Map,包含 extra 中的字段
|
||||
*/
|
||||
fun toMutableFlatMap(): MutableMap<String, Any?> {
|
||||
val map = mutableMapOf(
|
||||
"code" to code,
|
||||
"success" to success,
|
||||
"message" to message,
|
||||
"data" to data
|
||||
)
|
||||
if (!requestId.isNullOrBlank()) {
|
||||
map["requestId"] = requestId
|
||||
}
|
||||
map.putAll(extra)
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为不可变 Map,包含 extra 中的字段
|
||||
*/
|
||||
fun toFlatMap(): Map<String, Any?> = toMutableFlatMap().toMap()
|
||||
|
||||
companion object {
|
||||
private fun buildExtraMap(extenders: List<ResultExtender>): Map<String, Any?> {
|
||||
return mutableMapOf<String, Any?>().apply {
|
||||
@ -115,21 +55,23 @@ data class I18nResult<T>(
|
||||
* @param info 响应信息对象
|
||||
* @param data 响应数据
|
||||
* @param messageResolver 消息解析器
|
||||
* @param i18bArgs 国际化参数
|
||||
* @param requestIdProvider 请求ID提供者
|
||||
* @param extenders 扩展信息提供者列表
|
||||
* @return 成功响应对象
|
||||
*/
|
||||
fun <T> success(
|
||||
info: I18nResponseInformation = defaultOkI18nResponseInformation,
|
||||
info: ResponseInformation,
|
||||
data: T? = null,
|
||||
messageResolver: MessageResolver? = null,
|
||||
i18bArgs: Array<Any>? = null,
|
||||
requestIdProvider: RequestIdProvider? = null,
|
||||
extenders: List<ResultExtender> = emptyList(),
|
||||
): I18nResult<T> {
|
||||
val msg = (messageResolver ?: DefaultMessageResolver).resolve(info.responseI8nMessageCode, info.responseI8nMessageArgs)
|
||||
extenders: List<ResultExtender> = emptyList()
|
||||
): R<T> {
|
||||
val msg = (messageResolver ?: DefaultMessageResolver).resolve(info.responseI8nMessageCode, i18bArgs)
|
||||
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
||||
val extra = buildExtraMap(extenders)
|
||||
return I18nResult(info.responseCode, true, msg, data, reqId, extra)
|
||||
return R(info.responseCode, true, msg, data, reqId, extra)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,21 +80,23 @@ data class I18nResult<T>(
|
||||
* @param info 响应信息对象
|
||||
* @param data 响应数据
|
||||
* @param messageResolver 消息解析器
|
||||
* @param i18bArgs 国际化参数
|
||||
* @param requestIdProvider 请求ID提供者
|
||||
* @param extenders 扩展信息提供者列表
|
||||
* @return 失败响应对象
|
||||
*/
|
||||
fun <T> failure(
|
||||
info: I18nResponseInformation = defaultFailureI18nResponseInformation,
|
||||
info: ResponseInformation,
|
||||
data: T? = null,
|
||||
messageResolver: MessageResolver? = null,
|
||||
i18bArgs: Array<Any>? = null,
|
||||
requestIdProvider: RequestIdProvider? = null,
|
||||
extenders: List<ResultExtender> = emptyList(),
|
||||
): I18nResult<T> {
|
||||
val msg = (messageResolver ?: DefaultMessageResolver).resolve(info.responseI8nMessageCode, info.responseI8nMessageArgs)
|
||||
extenders: List<ResultExtender> = emptyList()
|
||||
): R<T> {
|
||||
val msg = (messageResolver ?: DefaultMessageResolver).resolve(info.responseI8nMessageCode, i18bArgs)
|
||||
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
||||
val extra = buildExtraMap(extenders)
|
||||
return I18nResult(info.responseCode, false, msg, data, reqId, extra)
|
||||
return R(info.responseCode, false, msg, data, reqId, extra)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -174,12 +118,12 @@ data class I18nResult<T>(
|
||||
i18nArgs: Array<Any>? = null,
|
||||
messageResolver: MessageResolver? = null,
|
||||
requestIdProvider: RequestIdProvider? = null,
|
||||
extenders: List<ResultExtender> = emptyList(),
|
||||
): I18nResult<T> {
|
||||
extenders: List<ResultExtender> = emptyList()
|
||||
): R<T> {
|
||||
val msg = (messageResolver ?: DefaultMessageResolver).resolve(messageCode, i18nArgs)
|
||||
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
||||
val extra = buildExtraMap(extenders)
|
||||
return I18nResult(code, true, msg, data, reqId, extra)
|
||||
return R(code, true, msg, data, reqId, extra)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -201,12 +145,12 @@ data class I18nResult<T>(
|
||||
i18nArgs: Array<Any>? = null,
|
||||
messageResolver: MessageResolver? = null,
|
||||
requestIdProvider: RequestIdProvider? = null,
|
||||
extenders: List<ResultExtender> = emptyList(),
|
||||
): I18nResult<T> {
|
||||
extenders: List<ResultExtender> = emptyList()
|
||||
): R<T> {
|
||||
val msg = (messageResolver ?: DefaultMessageResolver).resolve(messageCode, i18nArgs)
|
||||
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
||||
val extra = buildExtraMap(extenders)
|
||||
return I18nResult(code, false, msg, data, reqId, extra)
|
||||
return R(code, false, msg, data, reqId, extra)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.gewuyou.forgeboot.common.result.impl
|
||||
|
||||
import com.gewuyou.forgeboot.common.result.api.MessageResolver
|
||||
|
||||
/**
|
||||
*默认消息解析器
|
||||
*
|
||||
* @since 2025-05-03 16:21:43
|
||||
* @author gewuyou
|
||||
*/
|
||||
object DefaultMessageResolver : MessageResolver {
|
||||
override fun resolve(code: String, args: Array<Any>? ): String = code
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.gewuyou.forgeboot.common.result.impl
|
||||
|
||||
import com.gewuyou.forgeboot.common.result.api.RequestIdProvider
|
||||
|
||||
/**
|
||||
*默认请求ID提供商
|
||||
*
|
||||
* @since 2025-05-03 16:22:18
|
||||
* @author gewuyou
|
||||
*/
|
||||
object DefaultRequestIdProvider : RequestIdProvider {
|
||||
override fun getRequestId(): String = ""
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
dependencies {
|
||||
val libs = rootProject.libs
|
||||
compileOnly(libs.slf4j.api)
|
||||
implementation(libs.slf4j.api)
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
package com.gewuyou.forgeboot.core.extension
|
||||
|
||||
/**
|
||||
* 尝试将给定的字符串转换为指定枚举类型的枚举值
|
||||
* 如果转换失败或输入字符串为空,则返回null
|
||||
*
|
||||
* @param name 要转换为枚举值的字符串名称,可以为空
|
||||
* @return 成功转换的枚举值,如果失败则返回null
|
||||
*/
|
||||
inline fun <reified T : Enum<T>> enumValueOfOrNull(name: String?): T? =
|
||||
try { enumValueOf<T>(name ?: "") } catch (_: Exception) { null }
|
||||
@ -1,22 +0,0 @@
|
||||
package com.gewuyou.forgeboot.core.extension
|
||||
|
||||
/**
|
||||
* 尝试执行一段代码块,如果执行过程中抛出异常,则返回null。
|
||||
* 使用inline避免额外的函数调用开销,对于性能敏感的场景特别有用。
|
||||
*
|
||||
* @param block 一个lambda表达式,代表尝试执行的代码块。
|
||||
* @return 成功执行的结果,或者如果执行过程中抛出异常,则返回null。
|
||||
*/
|
||||
inline fun <T> tryOrNull(block: () -> T): T? =
|
||||
try { block() } catch (_: Exception) { null }
|
||||
|
||||
/**
|
||||
* 尝试执行一段代码块,如果执行过程中抛出异常,则返回一个预设的备选值。
|
||||
* 同样使用inline关键字,以减少性能开销。
|
||||
*
|
||||
* @param fallBack 备选值,如果代码块执行过程中抛出异常,则返回该值。
|
||||
* @param block 一个lambda表达式,代表尝试执行的代码块。
|
||||
* @return 成功执行的结果,或者如果执行过程中抛出异常,则返回备选值。
|
||||
*/
|
||||
inline fun <T> tryOrFallBack(fallBack: T, block: () -> T): T =
|
||||
try { block() } catch (_: Exception) { fallBack }
|
||||
@ -2,6 +2,12 @@ package com.gewuyou.forgeboot.core.extension
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
/**
|
||||
*日志扩展类
|
||||
*
|
||||
* @since 2025-01-02 12:49:13
|
||||
* @author gewuyou
|
||||
*/
|
||||
|
||||
/**
|
||||
* 日志扩展
|
||||
@ -1,6 +1,8 @@
|
||||
extra{
|
||||
setProperty(ProjectFlags.IS_ROOT_MODULE,true)
|
||||
}
|
||||
dependencies {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
implementation(project(Modules.Common.RESULT_API))
|
||||
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
// Spring Boot WebFlux
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
}
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
|
||||
dependencies {
|
||||
compileOnly(platform(libs.springBootDependencies.bom))
|
||||
// compileOnly(platform(libs.springCloudDependencies.bom))
|
||||
// compileOnly(libs.springBootStarter.web)
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
}
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
package com.gewuyou.forgeboot.i18n.api
|
||||
|
||||
import org.springframework.web.server.WebFilter
|
||||
|
||||
interface WebFluxLocaleResolver: WebFilter
|
||||
@ -1 +0,0 @@
|
||||
spring.application.name=forgeboot-i18n-api
|
||||
@ -1,10 +0,0 @@
|
||||
|
||||
dependencies {
|
||||
compileOnly(platform(libs.springBootDependencies.bom))
|
||||
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))
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
com.gewuyou.forgeboot.i18n.autoconfigure.I18nAutoConfiguration
|
||||
com.gewuyou.forgeboot.i18n.autoconfigure.I18nWebConfiguration
|
||||
@ -1,7 +0,0 @@
|
||||
dependencies {
|
||||
compileOnly(platform(libs.springBootDependencies.bom))
|
||||
compileOnly(platform(libs.springCloudDependencies.bom))
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
compileOnly(project(Modules.I18n.API))
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package com.gewuyou.forgeboot.i18n.impl.config
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
|
||||
/**
|
||||
* i18n属性
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-02-18 23:59:57
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "forgeboot.i18n")
|
||||
class I18nProperties {
|
||||
/**
|
||||
* 默认语言
|
||||
*/
|
||||
var defaultLocale: String = "zh_CN"
|
||||
|
||||
/**
|
||||
* 语言请求参数名
|
||||
*/
|
||||
var langRequestParameter: String = "lang"
|
||||
|
||||
/**
|
||||
* 语言文件路径
|
||||
*/
|
||||
var wildPathForLanguageFiles: String = "classpath*:i18n/**/messages"
|
||||
|
||||
/**
|
||||
* 位置模式后缀
|
||||
*/
|
||||
var locationPatternSuffix: String = ".properties"
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
package com.gewuyou.forgeboot.i18n.impl.exception
|
||||
|
||||
import com.gewuyou.forgeboot.i18n.api.I18nResponseInformation
|
||||
|
||||
|
||||
/**
|
||||
* i18n异常
|
||||
*
|
||||
* 该异常类用于处理国际化相关的异常情况,提供了错误代码和国际化消息代码的封装,
|
||||
* 以便于在异常处理时能够方便地获取这些信息
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2024-11-12 00:11:32
|
||||
*/
|
||||
open class I18nBaseException : RuntimeException {
|
||||
/**
|
||||
* 响应信息对象,用于存储错误代码和国际化消息代码
|
||||
*/
|
||||
@Transient
|
||||
private val i18nResponseInformation: I18nResponseInformation
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* 初始化异常对象,用于当只有响应信息可用时
|
||||
*
|
||||
* @param i18nResponseInformation 响应信息对象,包含错误代码和国际化消息代码
|
||||
*/
|
||||
constructor(i18nResponseInformation: I18nResponseInformation)
|
||||
: super(i18nResponseInformation.responseI8nMessageCode) {
|
||||
this.i18nResponseInformation = i18nResponseInformation
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* 初始化异常对象,用于当有响应信息和异常原因时
|
||||
*
|
||||
* @param i18nResponseInformation 响应信息对象,包含错误代码和国际化消息代码
|
||||
* @param cause 异常原因
|
||||
*/
|
||||
constructor(i18nResponseInformation: I18nResponseInformation, cause: Throwable)
|
||||
: super(i18nResponseInformation.responseI8nMessageCode, cause) {
|
||||
this.i18nResponseInformation = i18nResponseInformation
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误代码
|
||||
*
|
||||
* @return 错误代码
|
||||
*/
|
||||
val errorCode: Int
|
||||
get() = i18nResponseInformation.responseCode
|
||||
|
||||
/**
|
||||
* 获取国际化消息代码
|
||||
*
|
||||
* @return 国际化消息代码
|
||||
*/
|
||||
val errorI18nMessageCode: String
|
||||
get() = i18nResponseInformation.responseI8nMessageCode
|
||||
|
||||
/**
|
||||
* 获取国际化消息参数
|
||||
*
|
||||
* @return 国际化消息参数数组
|
||||
*/
|
||||
val errorI18nMessageArgs: Array<Any>?
|
||||
get() = i18nResponseInformation.responseI8nMessageArgs
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
spring.application.name=forgeboot-i18n-impl-starter
|
||||
@ -0,0 +1,63 @@
|
||||
package com.gewuyou.forgeboot.i18n.config.entity;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* i18n属性
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-02-18 23:59:57
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "forgeboot.i18n")
|
||||
public class I18nProperties {
|
||||
/**
|
||||
* 默认语言
|
||||
*/
|
||||
private String defaultLocale = "zh_CN";
|
||||
|
||||
/**
|
||||
* 语言请求参数名
|
||||
*/
|
||||
private String langRequestParameter = "lang";
|
||||
/**
|
||||
* 语言文件路径
|
||||
*/
|
||||
private String wildPathForLanguageFiles = "classpath*:i18n/**/messages";
|
||||
|
||||
/**
|
||||
* 位置模式后缀
|
||||
*/
|
||||
private String locationPatternSuffix = ".properties";
|
||||
|
||||
public String getLocationPatternSuffix() {
|
||||
return locationPatternSuffix;
|
||||
}
|
||||
|
||||
public void setLocationPatternSuffix(String locationPatternSuffix) {
|
||||
this.locationPatternSuffix = locationPatternSuffix;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return defaultLocale;
|
||||
}
|
||||
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
public String getLangRequestParameter() {
|
||||
return langRequestParameter;
|
||||
}
|
||||
|
||||
public void setLangRequestParameter(String langRequestParameter) {
|
||||
this.langRequestParameter = langRequestParameter;
|
||||
}
|
||||
|
||||
public String getWildPathForLanguageFiles() {
|
||||
return wildPathForLanguageFiles;
|
||||
}
|
||||
|
||||
public void setWildPathForLanguageFiles(String wildPathForLanguageFiles) {
|
||||
this.wildPathForLanguageFiles = wildPathForLanguageFiles;
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
package com.gewuyou.forgeboot.i18n.autoconfigure
|
||||
|
||||
package com.gewuyou.forgeboot.i18n.config
|
||||
|
||||
import com.gewuyou.forgeboot.common.result.api.MessageResolver
|
||||
import com.gewuyou.forgeboot.i18n.filter.ReactiveLocaleResolver
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.i18n.api.MessageResolver
|
||||
import com.gewuyou.forgeboot.i18n.impl.config.I18nProperties
|
||||
import com.gewuyou.forgeboot.i18n.impl.filter.ReactiveLocaleResolver
|
||||
import com.gewuyou.forgeboot.i18n.impl.resolver.I18nMessageResolver
|
||||
import com.gewuyou.forgeboot.i18n.config.entity.I18nProperties
|
||||
import com.gewuyou.forgeboot.i18n.resolver.I18nMessageResolver
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
@ -38,11 +38,12 @@ open class I18nAutoConfiguration(
|
||||
* 配置并创建一个国际化的消息源
|
||||
*
|
||||
* 此方法首先会扫描指定路径下所有的国际化属性文件,然后将这些文件路径设置到消息源中
|
||||
* 如果项目中还没有名为 [MESSAGE_SOURCE_BEAN_NAME] 的消息源 bean,则会创建一个
|
||||
*
|
||||
* @return MessageSource 国际化消息源
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Bean(name = [MESSAGE_SOURCE_BEAN_NAME])
|
||||
@ConditionalOnMissingBean(name = [MESSAGE_SOURCE_BEAN_NAME])
|
||||
open fun messageSource(): MessageSource {
|
||||
log.info("开始加载 I18n 配置...")
|
||||
val messageSource = ReloadableResourceBundleMessageSource()
|
||||
@ -61,13 +62,13 @@ open class I18nAutoConfiguration(
|
||||
* 该方法通过Spring的条件注解有选择性地创建一个MessageResolver实例
|
||||
* 主要用于解决国际化消息的解析问题
|
||||
*
|
||||
* @param i18nMessageSource 一个MessageSource实例,用于解析国际化消息
|
||||
* @param forgebootI18nMessageSource 一个MessageSource实例,用于解析国际化消息
|
||||
* @return 返回一个MessageResolver实例,用于在国际化的环境中解析消息
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(MessageResolver::class)
|
||||
open fun i18nMessageResolver(i18nMessageSource: MessageSource): MessageResolver {
|
||||
return I18nMessageResolver(i18nMessageSource)
|
||||
open fun i18nMessageResolver(@Qualifier(MESSAGE_SOURCE_BEAN_NAME) forgebootI18nMessageSource: MessageSource): MessageResolver {
|
||||
return I18nMessageResolver(forgebootI18nMessageSource)
|
||||
}
|
||||
|
||||
|
||||
@ -163,4 +164,11 @@ open class I18nAutoConfiguration(
|
||||
log.info("创建 WebFlux 区域设置解析器...")
|
||||
return ReactiveLocaleResolver(i18nProperties)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* 消息源 bean 的名称
|
||||
*/
|
||||
const val MESSAGE_SOURCE_BEAN_NAME: String = "forgebootI18nMessageSource"
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.gewuyou.forgeboot.i18n.autoconfigure
|
||||
package com.gewuyou.forgeboot.i18n.config
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
@ -1,4 +1,4 @@
|
||||
package com.gewuyou.forgeboot.i18n.api
|
||||
package com.gewuyou.forgeboot.i18n.entity
|
||||
|
||||
/**
|
||||
* 内部信息(用于扩展项目内的i18n信息)
|
||||
@ -6,16 +6,10 @@ package com.gewuyou.forgeboot.i18n.api
|
||||
* @author gewuyou
|
||||
* @since 2024-11-26 17:14:15
|
||||
*/
|
||||
interface I18nInternalInformation {
|
||||
interface InternalInformation {
|
||||
/**
|
||||
* 获取i18n响应信息code
|
||||
* @return 响应信息 code
|
||||
*/
|
||||
val responseI8nMessageCode: String?
|
||||
|
||||
/**
|
||||
* 获取i18n响应信息参数
|
||||
* @return 响应信息 参数数组
|
||||
*/
|
||||
val responseI8nMessageArgs: Array<Any>?
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.gewuyou.forgeboot.i18n.entity
|
||||
|
||||
/**
|
||||
* 响应信息(用于对外提供i18n响应信息)
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2024-11-26 15:43:06
|
||||
*/
|
||||
interface ResponseInformation {
|
||||
/**
|
||||
* 获取响应码
|
||||
* @return 响应码
|
||||
*/
|
||||
val responseCode: Int
|
||||
|
||||
/**
|
||||
* 获取i18n响应信息code
|
||||
* @return 响应信息 code
|
||||
*/
|
||||
val responseI8nMessageCode: String
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.gewuyou.forgeboot.i18n.exception
|
||||
|
||||
import com.gewuyou.forgeboot.i18n.entity.ResponseInformation
|
||||
|
||||
/**
|
||||
* i18n异常
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2024-11-12 00:11:32
|
||||
*/
|
||||
class I18nBaseException : RuntimeException {
|
||||
/**
|
||||
* 响应信息对象,用于存储错误代码和国际化消息代码
|
||||
*/
|
||||
@Transient
|
||||
private val responseInformation: ResponseInformation
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param responseInformation 响应信息对象,包含错误代码和国际化消息代码
|
||||
*/
|
||||
constructor(responseInformation: ResponseInformation) : super() {
|
||||
this.responseInformation = responseInformation
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param responseInformation 响应信息对象,包含错误代码和国际化消息代码
|
||||
* @param cause 异常原因
|
||||
*/
|
||||
constructor(responseInformation: ResponseInformation, cause: Throwable?) : super(cause) {
|
||||
this.responseInformation = responseInformation
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误代码
|
||||
*
|
||||
* @return 错误代码
|
||||
*/
|
||||
val errorCode: Int
|
||||
get() = responseInformation.responseCode
|
||||
|
||||
/**
|
||||
* 获取国际化消息代码
|
||||
*
|
||||
* @return 国际化消息代码
|
||||
*/
|
||||
val errorI18nMessageCode: String
|
||||
get() = responseInformation.responseI8nMessageCode
|
||||
}
|
||||
@ -1,9 +1,7 @@
|
||||
package com.gewuyou.forgeboot.i18n.impl.filter
|
||||
package com.gewuyou.forgeboot.i18n.filter
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.i18n.api.WebFluxLocaleResolver
|
||||
import com.gewuyou.forgeboot.i18n.impl.config.I18nProperties
|
||||
|
||||
import com.gewuyou.forgeboot.i18n.config.entity.I18nProperties
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.i18n.LocaleContextHolder
|
||||
import org.springframework.util.StringUtils
|
||||
@ -0,0 +1,5 @@
|
||||
package com.gewuyou.forgeboot.i18n.filter
|
||||
|
||||
import org.springframework.web.server.WebFilter
|
||||
|
||||
interface WebFluxLocaleResolver:WebFilter
|
||||
@ -1,7 +1,6 @@
|
||||
package com.gewuyou.forgeboot.i18n.impl.resolver
|
||||
package com.gewuyou.forgeboot.i18n.resolver
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.i18n.api.MessageResolver
|
||||
import com.gewuyou.forgeboot.common.result.api.MessageResolver
|
||||
import org.springframework.context.MessageSource
|
||||
import org.springframework.context.i18n.LocaleContextHolder
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
com.gewuyou.forgeboot.i18n.config.I18nAutoConfiguration
|
||||
com.gewuyou.forgeboot.i18n.config.I18nWebConfiguration
|
||||
@ -1,6 +1,14 @@
|
||||
extra{
|
||||
setProperty(ProjectFlags.IS_ROOT_MODULE,true)
|
||||
}
|
||||
dependencies {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
implementation(project(Modules.Common.RESULT_API))
|
||||
|
||||
// Spring Cloud OpenFeign (Compile Only)
|
||||
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign
|
||||
compileOnly(libs.springCloudStarter.openfeign)
|
||||
// Reactor Core (Compile Only)
|
||||
// https://mvnrepository.com/artifact/io.projectreactor/reactor-core
|
||||
compileOnly(libs.reactor.core)
|
||||
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
}
|
||||
|
||||
@ -1 +0,0 @@
|
||||
spring.application.name=api
|
||||
@ -1,16 +0,0 @@
|
||||
|
||||
dependencies {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
compileOnly(platform(libs.springBootDependencies.bom))
|
||||
compileOnly(platform(libs.springCloudDependencies.bom))
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
compileOnly(project(Modules.TRACE.API))
|
||||
compileOnly(project(Modules.TRACE.IMPL))
|
||||
// Spring Cloud OpenFeign (Compile Only)
|
||||
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign
|
||||
compileOnly(libs.springCloudStarter.openfeign)
|
||||
// Reactor Core (Compile Only)
|
||||
// https://mvnrepository.com/artifact/io.projectreactor/reactor-core
|
||||
compileOnly(libs.reactor.core)
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
package com.gewuyou.forgeboot.trace.autoconfig
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.impl.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,43 +0,0 @@
|
||||
package com.gewuyou.forgeboot.trace.autoconfig
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.impl.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 +0,0 @@
|
||||
com.gewuyou.forgeboot.trace.autoconfig.TraceAutoConfiguration
|
||||
com.gewuyou.forgeboot.trace.autoconfig.WebClientTraceAutoConfiguration
|
||||
com.gewuyou.forgeboot.trace.autoconfig.FeignTraceAutoConfiguration
|
||||
@ -1,12 +0,0 @@
|
||||
dependencies {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
compileOnly(project(Modules.TRACE.API))
|
||||
compileOnly(platform(libs.springBootDependencies.bom))
|
||||
compileOnly(platform(libs.springCloudDependencies.bom))
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
// 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,30 +0,0 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.config
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
|
||||
/**
|
||||
* 跟踪属性
|
||||
* 该类用于配置和管理请求跟踪相关的属性,通过这些属性可以对请求进行唯一的标识和跟踪
|
||||
* 主要功能包括定义请求ID的HTTP头名称、请求ID在MDC中的键名称,以及忽略跟踪的URL模式
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-05-02 20:58:45
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "forgeboot.trace")
|
||||
class TraceProperties {
|
||||
/**
|
||||
* HTTP请求头中用于传递请求ID的字段名称,默认为"X-Request-Id"。
|
||||
*/
|
||||
var requestIdHeaderName: String = "X-Request-Id"
|
||||
|
||||
/**
|
||||
* MDC(Mapped Diagnostic Context)中用于存储请求ID的键名,默认为"requestId"。
|
||||
*/
|
||||
var requestIdMdcKey: String = "requestId"
|
||||
|
||||
/**
|
||||
* 配置忽略日志记录的路径模式,通常用于静态资源文件,
|
||||
* 默认忽略以.css、.js、.png等结尾的静态资源请求。
|
||||
*/
|
||||
var ignorePatten = arrayOf(".*\\.(css|js|png|jpg|jpeg|gif|svg)")
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
spring.application.name=impl
|
||||
@ -0,0 +1,54 @@
|
||||
package com.gewuyou.forgeboot.trace.config.entities;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* 跟踪属性
|
||||
* 该类用于配置和管理请求跟踪相关的属性,通过这些属性可以对请求进行唯一的标识和跟踪
|
||||
* 主要功能包括定义请求ID的HTTP头名称、请求ID在MDC中的键名称,以及忽略跟踪的URL模式
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-05-02 20:58:45
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "forgeboot.trace")
|
||||
public class TraceProperties {
|
||||
/**
|
||||
* HTTP请求头中用于传递请求ID的字段名称,默认为"X-Request-Id"。
|
||||
*/
|
||||
private String requestIdHeaderName = "X-Request-Id";
|
||||
|
||||
/**
|
||||
* MDC(Mapped Diagnostic Context)中用于存储请求ID的键名,默认为"requestId"。
|
||||
*/
|
||||
private String requestIdMdcKey = "requestId";
|
||||
|
||||
/**
|
||||
* 配置忽略日志记录的路径模式,通常用于静态资源文件,
|
||||
* 默认忽略以.css、.js、.png等结尾的静态资源请求。
|
||||
*/
|
||||
private String[] ignorePatten = new String[]{".*\\.(css|js|png|jpg|jpeg|gif|svg)"};
|
||||
|
||||
public String getRequestIdHeaderName() {
|
||||
return requestIdHeaderName;
|
||||
}
|
||||
|
||||
public String[] getIgnorePatten() {
|
||||
return ignorePatten;
|
||||
}
|
||||
|
||||
public void setIgnorePatten(String[] ignorePatten) {
|
||||
this.ignorePatten = ignorePatten;
|
||||
}
|
||||
|
||||
public void setRequestIdHeaderName(String requestIdHeaderName) {
|
||||
this.requestIdHeaderName = requestIdHeaderName;
|
||||
}
|
||||
|
||||
public String getRequestIdMdcKey() {
|
||||
return requestIdMdcKey;
|
||||
}
|
||||
|
||||
public void setRequestIdMdcKey(String requestIdMdcKey) {
|
||||
this.requestIdMdcKey = requestIdMdcKey;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.gewuyou.forgeboot.trace.util;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 请求 ID Util
|
||||
* 这个类需配合 RequestIdFilter 使用,用于生成请求 ID,并将其绑定到线程变量中,供后续可能需要的地方使用。
|
||||
* @author gewuyou
|
||||
* @since 2025-01-02 14:27:45
|
||||
*/
|
||||
public class RequestIdUtil {
|
||||
private static final ThreadLocal<String> REQUEST_ID_HOLDER = new ThreadLocal<>();
|
||||
private RequestIdUtil() {
|
||||
}
|
||||
public static void generateRequestId() {
|
||||
REQUEST_ID_HOLDER.set(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
public static String getRequestId() {
|
||||
return REQUEST_ID_HOLDER.get();
|
||||
}
|
||||
|
||||
public static void setRequestId(String uuid) {
|
||||
REQUEST_ID_HOLDER.set(uuid);
|
||||
}
|
||||
|
||||
public static void removeRequestId() {
|
||||
REQUEST_ID_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,22 @@
|
||||
package com.gewuyou.forgeboot.trace.autoconfig
|
||||
package com.gewuyou.forgeboot.trace.config
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
|
||||
import com.gewuyou.forgeboot.trace.impl.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 com.gewuyou.forgeboot.common.result.api.RequestIdProvider
|
||||
import com.gewuyou.forgeboot.trace.config.entities.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.decorator.RequestIdTaskDecorator
|
||||
import com.gewuyou.forgeboot.trace.filter.ReactiveRequestIdFilter
|
||||
import com.gewuyou.forgeboot.trace.filter.RequestIdFilter
|
||||
import com.gewuyou.forgeboot.trace.filter.WebClientRequestIdFilter
|
||||
import com.gewuyou.forgeboot.trace.interceptor.FeignRequestIdInterceptor
|
||||
import com.gewuyou.forgeboot.trace.provider.TraceRequestIdProvider
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.cloud.openfeign.FeignAutoConfiguration
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.web.reactive.function.client.WebClient
|
||||
|
||||
/**
|
||||
* 跟踪自动配置
|
||||
@ -34,10 +38,7 @@ open class TraceAutoConfiguration {
|
||||
@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)
|
||||
}
|
||||
open fun requestIdFilter(traceProperties: TraceProperties): RequestIdFilter = RequestIdFilter(traceProperties)
|
||||
|
||||
/**
|
||||
* Spring WebFlux 过滤器(仅当 Spring WebFlux 存在时生效)
|
||||
@ -49,10 +50,8 @@ open class TraceAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = ["spring.main.web-application-type"], havingValue = "reactive")
|
||||
@ConditionalOnMissingBean
|
||||
open fun reactiveRequestIdFilter(traceProperties: TraceProperties): ReactiveRequestIdFilter {
|
||||
log.info("ReactiveRequestIdFilter 已创建!")
|
||||
return ReactiveRequestIdFilter(traceProperties)
|
||||
}
|
||||
open fun reactiveRequestIdFilter(traceProperties: TraceProperties): ReactiveRequestIdFilter =
|
||||
ReactiveRequestIdFilter(traceProperties)
|
||||
|
||||
/**
|
||||
* 请求ID提供者(用于生成请求ID)
|
||||
@ -62,10 +61,20 @@ open class TraceAutoConfiguration {
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(RequestIdProvider::class)
|
||||
open fun traceRequestIdProvider(): TraceRequestIdProvider {
|
||||
log.info("TraceRequestIdProvider 已创建!")
|
||||
return TraceRequestIdProvider()
|
||||
}
|
||||
open fun traceRequestIdProvider(): TraceRequestIdProvider = TraceRequestIdProvider()
|
||||
|
||||
/**
|
||||
* Feign 拦截器(仅当 Feign 存在时生效)
|
||||
*
|
||||
* 该拦截器用于在Feign客户端中传递请求ID
|
||||
* @param traceProperties 跟踪配置属性
|
||||
* @return FeignRequestIdInterceptor实例
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnClass(FeignAutoConfiguration::class)
|
||||
@ConditionalOnMissingBean
|
||||
open fun feignRequestIdInterceptor(traceProperties: TraceProperties): FeignRequestIdInterceptor =
|
||||
FeignRequestIdInterceptor(traceProperties)
|
||||
|
||||
/**
|
||||
* 线程池装饰器(用于 @Async)
|
||||
@ -76,9 +85,19 @@ open class TraceAutoConfiguration {
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
open fun requestIdTaskDecorator(traceProperties: TraceProperties): RequestIdTaskDecorator {
|
||||
log.info("RequestIdTaskDecorator 已创建!")
|
||||
return RequestIdTaskDecorator(traceProperties)
|
||||
}
|
||||
open fun requestIdTaskDecorator(traceProperties: TraceProperties): RequestIdTaskDecorator =
|
||||
RequestIdTaskDecorator(traceProperties)
|
||||
|
||||
/**
|
||||
* 配置 WebClient 并自动添加请求 ID 过滤器(仅在 WebClient 已存在时引入)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnBean(WebClient.Builder::class) // 如果 WebClient.Builder 已存在,则添加过滤器
|
||||
open fun webClientRequestIdFilter(
|
||||
webClientBuilder: WebClient.Builder,
|
||||
traceProperties: TraceProperties
|
||||
): WebClient.Builder {
|
||||
// 在现有 WebClient 配置中添加请求 ID 过滤器
|
||||
return webClientBuilder.filter(WebClientRequestIdFilter(traceProperties))
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.decorator
|
||||
package com.gewuyou.forgeboot.trace.decorator
|
||||
|
||||
import com.gewuyou.forgeboot.trace.impl.config.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.config.entities.TraceProperties
|
||||
import org.slf4j.MDC
|
||||
import org.springframework.core.task.TaskDecorator
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.extension
|
||||
package com.gewuyou.forgeboot.trace.extension
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.http.HttpMethod
|
||||
@ -1,12 +1,11 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.filter
|
||||
package com.gewuyou.forgeboot.trace.filter
|
||||
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.impl.config.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.impl.extension.isSkipRequest
|
||||
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
|
||||
|
||||
import com.gewuyou.forgeboot.trace.config.entities.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.extension.isSkipRequest
|
||||
import com.gewuyou.forgeboot.trace.util.RequestIdUtil
|
||||
import org.slf4j.MDC
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import org.springframework.web.server.WebFilter
|
||||
@ -37,10 +36,10 @@ class ReactiveRequestIdFilter(
|
||||
val requestIdMdcKey = traceProperties.requestIdMdcKey
|
||||
// 尝试从请求头中获取 requestId,如果存在则设置到 RequestIdUtil 中,否则生成一个新的 requestId
|
||||
request.headers[requestIdHeader]?.let {
|
||||
it.firstOrNull()?.let(RequestIdUtil::requestId::set) ?: RequestIdUtil.generateRequestId()
|
||||
it.firstOrNull()?.let(RequestIdUtil::setRequestId) ?: RequestIdUtil.generateRequestId()
|
||||
} ?: RequestIdUtil.generateRequestId()
|
||||
// 获取当前的 requestId
|
||||
val currentRequestId = RequestIdUtil.requestId
|
||||
val currentRequestId = RequestIdUtil.getRequestId()
|
||||
// 将 requestId 设置到日志中
|
||||
MDC.put(requestIdMdcKey, currentRequestId)
|
||||
log.info("设置 Request id: $currentRequestId")
|
||||
@ -54,7 +53,7 @@ class ReactiveRequestIdFilter(
|
||||
return chain.filter(mutatedExchange)
|
||||
// ✅ 让 Reactor 线程也能获取 requestId
|
||||
// 将 requestId 写入 Reactor 的上下文中,以便在异步处理中也能访问
|
||||
.contextWrite { ctx -> ctx.put(requestIdMdcKey, currentRequestId!!) }
|
||||
.contextWrite { ctx -> ctx.put(requestIdMdcKey, currentRequestId) }
|
||||
.doFinally {
|
||||
// 清理 MDC 中的 requestId,避免内存泄漏
|
||||
MDC.remove(requestIdMdcKey)
|
||||
@ -1,11 +1,10 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.filter
|
||||
package com.gewuyou.forgeboot.trace.filter
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.impl.config.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.impl.extension.isSkipRequest
|
||||
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
|
||||
|
||||
import com.gewuyou.forgeboot.trace.config.entities.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.extension.isSkipRequest
|
||||
import com.gewuyou.forgeboot.trace.util.RequestIdUtil
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
@ -40,13 +39,13 @@ class RequestIdFilter(
|
||||
try {
|
||||
// 尝试从请求头中获取 requestId
|
||||
request.getHeader(requestIdHeader)?.also(
|
||||
RequestIdUtil::requestId::set
|
||||
RequestIdUtil::setRequestId
|
||||
) ?: run {
|
||||
// 如果没有,则生成新的 requestId
|
||||
RequestIdUtil.generateRequestId()
|
||||
}
|
||||
// 获取 requestId
|
||||
val requestId = RequestIdUtil.requestId
|
||||
val requestId = RequestIdUtil.getRequestId()
|
||||
// 将requestId 设置到日志中
|
||||
MDC.put(requestIdMdcKey, requestId)
|
||||
log.info("设置 Request id: $requestId")
|
||||
@ -1,10 +1,9 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.filter
|
||||
package com.gewuyou.forgeboot.trace.filter
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.impl.config.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.impl.extension.isSkipRequest
|
||||
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
|
||||
|
||||
import com.gewuyou.forgeboot.trace.config.entities.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.extension.isSkipRequest
|
||||
import com.gewuyou.forgeboot.trace.util.RequestIdUtil
|
||||
import org.slf4j.MDC
|
||||
import org.springframework.web.reactive.function.client.ClientRequest
|
||||
import org.springframework.web.reactive.function.client.ClientResponse
|
||||
@ -36,10 +35,10 @@ class WebClientRequestIdFilter(
|
||||
val requestIdMdcKey = traceProperties.requestIdMdcKey
|
||||
// 尝试从请求头中获取 requestId,如果存在则设置到 RequestIdUtil 中,否则生成一个新的 requestId
|
||||
request.headers()[requestIdHeader]?.let {
|
||||
it.firstOrNull()?.let(RequestIdUtil::requestId::set) ?: RequestIdUtil.generateRequestId()
|
||||
it.firstOrNull()?.let(RequestIdUtil::setRequestId) ?: RequestIdUtil.generateRequestId()
|
||||
} ?: RequestIdUtil.generateRequestId()
|
||||
// 获取当前的 requestId
|
||||
val currentRequestId = RequestIdUtil.requestId
|
||||
val currentRequestId = RequestIdUtil.getRequestId()
|
||||
// 将 requestId 设置到日志中
|
||||
MDC.put(requestIdMdcKey, currentRequestId)
|
||||
log.info("设置 Request id: $currentRequestId")
|
||||
@ -1,10 +1,10 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.interceptor
|
||||
package com.gewuyou.forgeboot.trace.interceptor
|
||||
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.trace.impl.config.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
|
||||
import com.gewuyou.forgeboot.trace.config.entities.TraceProperties
|
||||
import com.gewuyou.forgeboot.trace.util.RequestIdUtil
|
||||
import feign.RequestInterceptor
|
||||
import feign.RequestTemplate
|
||||
|
||||
@ -19,7 +19,7 @@ class FeignRequestIdInterceptor(
|
||||
) : RequestInterceptor {
|
||||
override fun apply(template: RequestTemplate) {
|
||||
// 尝试获取当前请求的请求id
|
||||
val requestId = RequestIdUtil.requestId
|
||||
val requestId = RequestIdUtil.getRequestId()
|
||||
requestId?.let {
|
||||
// 如果请求id存在,则添加到请求头中
|
||||
template.header(traceProperties.requestIdHeaderName, requestId)
|
||||
@ -1,8 +1,7 @@
|
||||
package com.gewuyou.forgeboot.trace.impl.provider
|
||||
|
||||
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
|
||||
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
|
||||
package com.gewuyou.forgeboot.trace.provider
|
||||
|
||||
import com.gewuyou.forgeboot.common.result.api.RequestIdProvider
|
||||
import com.gewuyou.forgeboot.trace.util.RequestIdUtil
|
||||
|
||||
/**
|
||||
*跟踪请求ID提供商
|
||||
@ -19,6 +18,6 @@ class TraceRequestIdProvider: RequestIdProvider {
|
||||
* @return 请求ID的字符串表示
|
||||
*/
|
||||
override fun getRequestId(): String {
|
||||
return RequestIdUtil.requestId?:throw RuntimeException("requestId is null")
|
||||
return RequestIdUtil.getRequestId()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
com.gewuyou.forgeboot.trace.config.TraceAutoConfiguration
|
||||
3
forgeboot-webmvc/dto/.gitattributes
vendored
3
forgeboot-webmvc/dto/.gitattributes
vendored
@ -1,3 +0,0 @@
|
||||
/gradlew text eol=lf
|
||||
*.bat text eol=crlf
|
||||
*.jar binary
|
||||
40
forgeboot-webmvc/dto/.gitignore
vendored
40
forgeboot-webmvc/dto/.gitignore
vendored
@ -1,40 +0,0 @@
|
||||
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
|
||||
@ -1,18 +0,0 @@
|
||||
plugins{
|
||||
alias(libs.plugins.forgeboot.i18n.keygen)
|
||||
}
|
||||
dependencies {
|
||||
api(project(Modules.I18n.API))
|
||||
api(project(Modules.TRACE.API))
|
||||
implementation(libs.kotlinReflect)
|
||||
compileOnly(libs.springBootDependencies.bom)
|
||||
compileOnly(libs.jackson.annotations)
|
||||
compileOnly(libs.springBootStarter.jpa)
|
||||
compileOnly(libs.springBootStarter.validation)
|
||||
compileOnly(libs.org.mapstruct)
|
||||
}
|
||||
i18nKeyGen {
|
||||
rootPackage.set("com.gewuyou.forgeboot.webmvc.dto.i18n")
|
||||
level.set(3)
|
||||
readPath.set("src/main/resources/i18n/${project.name}")
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
*基本统一响应封装类
|
||||
*
|
||||
* @since 2025-05-30 14:01:14
|
||||
* @author gewuyou
|
||||
*/
|
||||
open class BaseResult<T>(
|
||||
open val code: Any,
|
||||
open val success: Boolean,
|
||||
open val message: String,
|
||||
open val data: T? = null,
|
||||
open val requestId: String? = null,
|
||||
open val extra: Map<String, Any?> = emptyMap(),
|
||||
) : Serializable {
|
||||
|
||||
fun toMutableFlatMap(): MutableMap<String, Any?> {
|
||||
val map = mutableMapOf(
|
||||
"code" to code,
|
||||
"success" to success,
|
||||
"message" to message,
|
||||
"data" to data
|
||||
)
|
||||
if (!requestId.isNullOrBlank()) {
|
||||
map["requestId"] = requestId
|
||||
}
|
||||
map.putAll(extra)
|
||||
return map
|
||||
}
|
||||
|
||||
fun toFlatMap(): Map<String, Any?> = toMutableFlatMap().toMap()
|
||||
}
|
||||
@ -1,112 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto
|
||||
|
||||
/**
|
||||
* 分页结果返回对象
|
||||
*
|
||||
* 通常用于封装分页查询的返回结果,包括总数、页码等信息。
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2024-04-23 下午10:53:04
|
||||
*/
|
||||
open class PageResult<T> {
|
||||
|
||||
/**
|
||||
* 当前页的记录列表
|
||||
*/
|
||||
var records: MutableList<T>? = mutableListOf()
|
||||
|
||||
/**
|
||||
* 总记录数(整个数据集的记录总数)
|
||||
*/
|
||||
var totalRecords: Long = 0L
|
||||
|
||||
/**
|
||||
* 总页数(根据每页大小计算出的总页数)
|
||||
*/
|
||||
var totalPages: Long = 0L
|
||||
|
||||
/**
|
||||
* 当前页码(从1开始计数)
|
||||
*/
|
||||
var currentPage: Long = 1L
|
||||
|
||||
/**
|
||||
* 每页显示的记录数量
|
||||
*/
|
||||
var pageSize: Long = 10L
|
||||
|
||||
/**
|
||||
* 是否存在上一页
|
||||
*/
|
||||
var hasPrevious: Boolean = false
|
||||
|
||||
/**
|
||||
* 是否存在下一页
|
||||
*/
|
||||
var hasNext: Boolean = false
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* 创建一个空的分页结果实例
|
||||
*
|
||||
* @return 新的PageResult实例
|
||||
*/
|
||||
fun <T> of(): PageResult<T> = PageResult()
|
||||
|
||||
/**
|
||||
* 根据指定参数创建分页结果
|
||||
*
|
||||
* @param currentPage 当前页码
|
||||
* @param pageSize 每页大小
|
||||
* @param totalRecords 总记录数
|
||||
* @param records 当前页的记录列表
|
||||
* @return 填充好的PageResult实例
|
||||
*/
|
||||
fun <T> of(currentPage: Long, pageSize: Long, totalRecords: Long, records: MutableList<T>?): PageResult<T> {
|
||||
val result = PageResult<T>()
|
||||
result.records = records
|
||||
result.totalRecords = totalRecords
|
||||
result.pageSize = pageSize
|
||||
result.currentPage = currentPage
|
||||
result.totalPages = (totalRecords + pageSize - 1) / pageSize
|
||||
result.hasPrevious = currentPage > 1
|
||||
result.hasNext = currentPage < result.totalPages
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定参数创建分页结果(支持Int参数)
|
||||
*
|
||||
* @param currentPage 当前页码
|
||||
* @param pageSize 每页大小
|
||||
* @param totalRecords 总记录数
|
||||
* @param records 当前页的记录列表
|
||||
* @return 填充好的PageResult实例
|
||||
*/
|
||||
fun <T> of(currentPage: Int, pageSize: Int, totalRecords: Long, records: MutableList<T>?): PageResult<T> {
|
||||
return of(currentPage.toLong(), pageSize.toLong(), totalRecords, records)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个仅设置页码和页面大小的分页结果
|
||||
*
|
||||
* @param currentPage 当前页码
|
||||
* @param pageSize 每页大小
|
||||
* @return 部分填充的PageResult实例
|
||||
*/
|
||||
fun <T> of(currentPage: Long, pageSize: Long): PageResult<T> {
|
||||
val result = PageResult<T>()
|
||||
result.currentPage = currentPage
|
||||
result.pageSize = pageSize
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个空的分页结果实例
|
||||
*
|
||||
* @return 新的PageResult实例
|
||||
*/
|
||||
fun <T> empty(): PageResult<T> = PageResult()
|
||||
}
|
||||
}
|
||||
@ -1,143 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto
|
||||
|
||||
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
|
||||
|
||||
// 默认成功响应信息
|
||||
val defaultOkResponseInformation = object : ResponseInformation {
|
||||
/**
|
||||
* 响应状态码,用于表示响应的状态
|
||||
*/
|
||||
override fun responseStateCode(): Int {
|
||||
return 200
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应消息,用于提供更详细的响应信息
|
||||
*/
|
||||
override fun responseMessage(): String {
|
||||
return "success"
|
||||
}
|
||||
}
|
||||
|
||||
// 默认失败响应信息
|
||||
val defaultFailureResponseInformation = object : ResponseInformation {
|
||||
/**
|
||||
* 响应状态码,用于表示响应的状态
|
||||
*/
|
||||
override fun responseStateCode(): Int {
|
||||
return 400
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应消息,用于提供更详细的响应信息
|
||||
*/
|
||||
override fun responseMessage(): String {
|
||||
return "failure"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一响应封装类
|
||||
*
|
||||
* @since 2025-05-03 16:04:42
|
||||
*/
|
||||
data class R<T>(
|
||||
override val code: Int,
|
||||
override val success: Boolean,
|
||||
override val message: String,
|
||||
override val data: T? = null,
|
||||
override val requestId: String? = null,
|
||||
override val extra: Map<String, Any?> = emptyMap(),
|
||||
) : BaseResult<T>(code, success, message, data, requestId, extra) {
|
||||
companion object {
|
||||
private fun buildExtraMap(extenders: List<ResultExtender>): Map<String, Any?> {
|
||||
return mutableMapOf<String, Any?>().apply {
|
||||
extenders.forEach { it.extend(this) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建成功响应对象
|
||||
*
|
||||
* @param info 响应信息对象
|
||||
* @param data 响应数据
|
||||
* @param requestIdProvider 请求ID提供者
|
||||
* @param extenders 扩展信息提供者列表
|
||||
* @return 成功响应对象
|
||||
*/
|
||||
fun <T> success(
|
||||
info: ResponseInformation = defaultOkResponseInformation,
|
||||
data: T? = null,
|
||||
requestIdProvider: RequestIdProvider? = null,
|
||||
extenders: List<ResultExtender> = emptyList(),
|
||||
): R<T> {
|
||||
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
||||
val extra = buildExtraMap(extenders)
|
||||
return R(info.responseStateCode(), true, info.responseMessage(), data, reqId, extra)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败响应对象
|
||||
*
|
||||
* @param info 响应信息对象
|
||||
* @param data 响应数据
|
||||
* @param requestIdProvider 请求ID提供者
|
||||
* @param extenders 扩展信息提供者列表
|
||||
* @return 失败响应对象
|
||||
*/
|
||||
fun <T> failure(
|
||||
info: ResponseInformation = defaultFailureResponseInformation,
|
||||
data: T? = null,
|
||||
requestIdProvider: RequestIdProvider? = null,
|
||||
extenders: List<ResultExtender> = emptyList(),
|
||||
): R<T> {
|
||||
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
||||
val extra = buildExtraMap(extenders)
|
||||
return R(info.responseStateCode(), false, info.responseMessage(), data, reqId, extra)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建成功响应对象
|
||||
*
|
||||
* @param code 响应码
|
||||
* @param message 消息
|
||||
* @param data 响应数据
|
||||
* @param requestIdProvider 请求ID提供者
|
||||
* @param extenders 扩展信息提供者列表
|
||||
* @return 成功响应对象
|
||||
*/
|
||||
fun <T> success(
|
||||
code: Int = 200,
|
||||
message: String = "success",
|
||||
data: T? = null,
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败响应对象
|
||||
*
|
||||
* @param code 响应码
|
||||
* @param message 消息
|
||||
* @param data 响应数据
|
||||
* @param requestIdProvider 请求ID提供者
|
||||
* @param extenders 扩展信息提供者列表
|
||||
* @return 失败响应对象
|
||||
*/
|
||||
fun <T> failure(
|
||||
code: Int = 400,
|
||||
message: String = "failure",
|
||||
data: T? = null,
|
||||
requestIdProvider: RequestIdProvider? = null,
|
||||
extenders: List<ResultExtender> = emptyList(),
|
||||
): R<T> {
|
||||
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
||||
val extra = buildExtraMap(extenders)
|
||||
return R(code, false, message, data, reqId, extra)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto
|
||||
|
||||
/**
|
||||
* 响应信息接口
|
||||
*
|
||||
* 该接口定义了响应信息的标准结构,包括响应状态码和响应消息
|
||||
* 主要用于规范响应数据的格式,以确保响应数据的一致性和可解析性
|
||||
*
|
||||
* @since 2025-05-30 13:27:27
|
||||
* @author gewuyou
|
||||
*/
|
||||
interface ResponseInformation {
|
||||
/**
|
||||
* 响应状态码,用于表示响应的状态
|
||||
*/
|
||||
fun responseStateCode(): Int
|
||||
|
||||
/**
|
||||
* 响应消息,用于提供更详细的响应信息
|
||||
*/
|
||||
fun responseMessage(): String
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.enums.SortDirection
|
||||
|
||||
|
||||
/**
|
||||
* 排序条件
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-01-16 16:11:47
|
||||
*/
|
||||
class SortCondition {
|
||||
/**
|
||||
* 排序字段
|
||||
*/
|
||||
var field: String = "createdAt"
|
||||
|
||||
/**
|
||||
* 排序方向
|
||||
*/
|
||||
var direction: SortDirection = SortDirection.DESC
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.enums
|
||||
|
||||
/**
|
||||
* 排序方向枚举
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-01-16 17:56:01
|
||||
*/
|
||||
enum class SortDirection {
|
||||
ASC, DESC
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.extension
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.PageResult
|
||||
import org.springframework.data.domain.Page
|
||||
|
||||
/**
|
||||
* 分页扩展
|
||||
*
|
||||
* 该扩展函数用于将Spring Data的Page对象转换为自定义的PageResult对象
|
||||
* 主要目的是为了统一分页数据的封装格式,便于在不同层次之间传递和使用
|
||||
*
|
||||
* @param <T> 泛型参数,表示分页数据中的具体类型
|
||||
* @return 返回转换后的PageResult对象,包含分页查询的结果
|
||||
* @since 2025-05-29 21:51:52
|
||||
* @author gewuyou
|
||||
*/
|
||||
fun <T> Page<T>.toPageResult(): PageResult<T> {
|
||||
// 将当前分页信息和数据内容封装到自定义的PageResult对象中
|
||||
return PageResult.of(
|
||||
currentPage = this.number.toLong(),
|
||||
pageSize = this.size.toLong(),
|
||||
totalRecords = this.totalElements,
|
||||
records = this.content
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,123 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.extension
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.enums.SortDirection
|
||||
import com.gewuyou.forgeboot.webmvc.dto.request.PageQueryReq
|
||||
import jakarta.persistence.criteria.CriteriaBuilder
|
||||
import jakarta.persistence.criteria.Predicate
|
||||
import jakarta.persistence.criteria.Root
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.domain.Sort
|
||||
|
||||
/**
|
||||
* 标记属性用于模糊匹配查询条件
|
||||
*
|
||||
* 该注解应用于实体类属性,表示在构建查询条件时使用LIKE操作符进行模糊匹配
|
||||
*/
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Like
|
||||
|
||||
/**
|
||||
* 标记属性用于精确匹配查询条件
|
||||
*
|
||||
* 该注解应用于实体类属性,表示在构建查询条件时使用EQUAL操作符进行精确匹配
|
||||
*/
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Equal
|
||||
|
||||
/**
|
||||
* 标记属性用于IN集合查询条件
|
||||
*
|
||||
* 该注解应用于实体类属性,表示在构建查询条件时使用IN操作符进行集合匹配
|
||||
*/
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class In
|
||||
|
||||
/**
|
||||
* 标记属性用于OR逻辑分组
|
||||
*
|
||||
* 该注解应用于实体类属性,表示该属性的查询条件需要与其他标记相同value值的属性
|
||||
* 进行OR逻辑组合,不同value值的组之间使用AND逻辑连接
|
||||
*
|
||||
* @property value 分组标识符,相同value的注解属性会被组合为一个OR条件组
|
||||
*/
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class OrGroup(val value: String)
|
||||
|
||||
/**
|
||||
* 分页查询扩展类
|
||||
*
|
||||
* 提供分页和查询条件构建的扩展方法,用于将 PageQueryReq 转换为 Spring Data 的分页对象,
|
||||
* 并生成通用的查询条件谓词列表。
|
||||
*
|
||||
* @since 2025-01-17
|
||||
* @author gewuyou
|
||||
*/
|
||||
fun <T> PageQueryReq<T>.toPageable(defaultSort: Sort.Order = Sort.Order.desc("createdAt")): Pageable {
|
||||
val orders = when {
|
||||
// 检查排序条件
|
||||
sortConditions.isNotEmpty() -> {
|
||||
sortConditions.map {
|
||||
if (it.direction == SortDirection.ASC) Sort.Order.asc(it.field) else Sort.Order.desc(it.field)
|
||||
}
|
||||
}
|
||||
// 检查是否有单字段排序
|
||||
sortBy.isNotBlank() -> {
|
||||
listOf(
|
||||
if (sortDirection == SortDirection.ASC) Sort.Order.asc(sortBy) else Sort.Order.desc(sortBy)
|
||||
)
|
||||
}
|
||||
// 如果都没有则默认按创建时间排序
|
||||
else -> listOf(defaultSort)
|
||||
}
|
||||
return PageRequest.of(currentPage - 1, pageSize, Sort.by(orders))
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件谓词列表
|
||||
*
|
||||
* 根据请求参数生成标准查询条件,包括时间范围、启用状态和删除状态等常用过滤条件。
|
||||
*
|
||||
* @param builder CriteriaBuilder 标准查询构造器
|
||||
* @param root Root<S> 查询的根对象
|
||||
* @param createAtName 创建时间字段名,默认 "createdAt"
|
||||
* @param enabledName 是否启用字段名,默认 "enabled"
|
||||
* @param deletedName 是否删除字段名,默认 "deleted"
|
||||
* @return MutableList<Predicate> 查询条件谓词列表
|
||||
*/
|
||||
fun <T, S> PageQueryReq<T>.getPredicates(
|
||||
builder: CriteriaBuilder,
|
||||
root: Root<S>,
|
||||
createAtName: String = "createdAt",
|
||||
enabledName: String = "enabled",
|
||||
deletedName: String = "deleted"
|
||||
): MutableList<Predicate> {
|
||||
val predicates = mutableListOf<Predicate>()
|
||||
|
||||
// 添加开始日期条件
|
||||
startDate?.let {
|
||||
predicates.add(builder.greaterThanOrEqualTo(root.get(createAtName), it))
|
||||
}
|
||||
|
||||
// 添加结束日期条件
|
||||
endDate?.let {
|
||||
predicates.add(builder.lessThanOrEqualTo(root.get(createAtName), it))
|
||||
}
|
||||
|
||||
// 添加是否启用条件
|
||||
enabled?.let {
|
||||
predicates.add(builder.equal(root.get<Boolean>(enabledName), it))
|
||||
}
|
||||
|
||||
// 添加是否删除条件
|
||||
deleted.let {
|
||||
predicates.add(builder.equal(root.get<Boolean>(deletedName), it))
|
||||
}
|
||||
|
||||
return predicates
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.extension
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.PageResult
|
||||
|
||||
/**
|
||||
* 分页结果扩展
|
||||
*
|
||||
* 该扩展函数用于将分页结果中的每个元素转换为另一种类型,同时保持分页信息不变
|
||||
* 它接受一个转换函数作为参数,该函数定义了如何将分页结果中的每个元素从类型 T 转换为类型 R
|
||||
*
|
||||
* @param transform 转换函数,用于将分页结果中的每个元素从类型 T 转换为类型 R
|
||||
* @return 返回一个新的分页结果对象,其中包含转换后的元素
|
||||
* @since 2025-05-29 22:05:37
|
||||
* @author gewuyou
|
||||
*/
|
||||
fun <T, R> PageResult<T>.map(transform: (T) -> R): PageResult<R> {
|
||||
return PageResult.of(
|
||||
currentPage = this.currentPage,
|
||||
pageSize = this.pageSize,
|
||||
totalRecords = this.totalRecords,
|
||||
records = this.records?.map(transform)?.toMutableList() ?: mutableListOf()
|
||||
)
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.extension
|
||||
|
||||
import jakarta.persistence.criteria.Predicate
|
||||
|
||||
/**
|
||||
* 在Predicate列表中添加条件,仅当字符串值不为空且不为空白时执行。
|
||||
*
|
||||
* @param value 待检查的字符串值
|
||||
* @param block 一个接受字符串并返回Predicate的lambda表达式
|
||||
*/
|
||||
inline fun MutableList<Predicate>.addIfNotBlank(
|
||||
value: String?,
|
||||
block: (String) -> Predicate
|
||||
) {
|
||||
if (!value.isNullOrBlank()) {
|
||||
add(block(value))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在Predicate列表中添加条件,仅当非字符串值不为空时执行。
|
||||
*
|
||||
* @param value 待检查的非字符串值
|
||||
* @param block 一个接受非字符串值并返回Predicate的lambda表达式
|
||||
*/
|
||||
inline fun <T> MutableList<Predicate>.addIfNotNull(
|
||||
value: T?,
|
||||
block: (T) -> Predicate
|
||||
) {
|
||||
if (value != null) {
|
||||
add(block(value))
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.mapper
|
||||
|
||||
import org.mapstruct.BeanMapping
|
||||
import org.mapstruct.MappingTarget
|
||||
import org.mapstruct.NullValuePropertyMappingStrategy
|
||||
|
||||
/**
|
||||
*Base Mapper (基础映射器)
|
||||
*
|
||||
* @since 2025-05-30 22:50:18
|
||||
* @author gewuyou
|
||||
*/
|
||||
interface BaseMapper<T,S> {
|
||||
/**
|
||||
* 合并 source 到 target,忽略 null 值
|
||||
*/
|
||||
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
|
||||
fun mergeIgnoreNull(@MappingTarget target: T, source: S)
|
||||
|
||||
/**
|
||||
* 全量覆盖合并(source 字段即使为 null 也覆盖 target)
|
||||
*/
|
||||
fun overwriteMerge(@MappingTarget target: T, source: S)
|
||||
|
||||
/**
|
||||
* 拷贝 source 到新对象
|
||||
*/
|
||||
fun copy(source: S): T
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.dto.mapper
|
||||
|
||||
/**
|
||||
* 转换 映射器
|
||||
*
|
||||
* 定义了一个转换映射器接口,用于在实体类和数据传输对象(DTO)之间进行转换。
|
||||
* 这个接口定义了四个基本转换方法:实体到DTO、DTO到实体、实体列表到DTO列表和DTO列表到实体列表。
|
||||
*
|
||||
* @param <Entity> 实体类类型
|
||||
* @param <Dto> 数据传输对象类型
|
||||
*
|
||||
* @since 2025-05-30 22:53:35
|
||||
* @author gewuyou
|
||||
*/
|
||||
interface ConversionMapper<Entity, Dto>{
|
||||
/**
|
||||
* 将实体对象转换为DTO对象
|
||||
*
|
||||
* @param entity 实体对象
|
||||
* @return 转换后的DTO对象
|
||||
*/
|
||||
fun toDto(entity: Entity): Dto
|
||||
|
||||
/**
|
||||
* 将DTO对象转换为实体对象
|
||||
*
|
||||
* @param dto DTO对象
|
||||
* @return 转换后的实体对象
|
||||
*/
|
||||
fun toEntity(dto: Dto): Entity
|
||||
|
||||
/**
|
||||
* 将实体对象列表转换为DTO对象列表
|
||||
*
|
||||
* @param entityList 实体对象列表
|
||||
* @return 转换后的DTO对象列表
|
||||
*/
|
||||
fun toDtoList(entityList: List<Entity>): List<Dto>
|
||||
|
||||
/**
|
||||
* 将DTO对象列表转换为实体对象列表
|
||||
*
|
||||
* @param dtoList DTO对象列表
|
||||
* @return 转换后的实体对象列表
|
||||
*/
|
||||
fun toEntityList(dtoList: List<Dto>): List<Entity>
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.request
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
import jakarta.validation.constraints.NotEmpty
|
||||
|
||||
/**
|
||||
* 根据id列表删除请求
|
||||
*
|
||||
* 该类用于封装删除操作中需要传入的id列表,确保删除操作能够接收一个明确且不为空的id集合
|
||||
* 它使用了泛型T,使得它可以适用于不同类型的id(例如Long,String等)
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-01-18 17:39:18
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
open class DeleteByIdsReq<T>(
|
||||
/**
|
||||
* 待删除的实体id列表
|
||||
*
|
||||
* 这个字段是删除操作的核心参数,它不能为空,以确保至少有一个id被指定用于删除
|
||||
* 使用@NotNull注解来确保在序列化和反序列化过程中,该字段不能为空
|
||||
*
|
||||
* @param ids 实体的唯一标识符列表,用于指定哪些实体应当被删除
|
||||
*/
|
||||
@field:NotEmpty(message = "The list of Ids requested to be removed cannot be empty!")
|
||||
var ids: List<T>,
|
||||
)
|
||||
@ -1,88 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.request
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
import com.gewuyou.forgeboot.webmvc.dto.SortCondition
|
||||
import com.gewuyou.forgeboot.webmvc.dto.enums.SortDirection
|
||||
import jakarta.validation.constraints.Min
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* 分页查询条件请求实体类
|
||||
*
|
||||
* 用于通用分页、排序、关键字搜索、日期范围与状态过滤。
|
||||
* 支持自定义泛型过滤器实体。
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-01-16 16:01:12
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
open class PageQueryReq<T> {
|
||||
|
||||
/**
|
||||
* 当前页码(默认1)
|
||||
*/
|
||||
@field:Min(1)
|
||||
var currentPage: Int = 1
|
||||
|
||||
/**
|
||||
* 每页条数(默认10)
|
||||
*/
|
||||
@field:Min(1)
|
||||
var pageSize: Int = 10
|
||||
|
||||
/**
|
||||
* 排序字段(单一字段)
|
||||
*/
|
||||
var sortBy: String = "createdAt"
|
||||
|
||||
/**
|
||||
* 排序方向(单一字段,ASC或DESC)
|
||||
*/
|
||||
var sortDirection: SortDirection = SortDirection.DESC
|
||||
|
||||
/**
|
||||
* 排序条件实体类(支持多字段排序)
|
||||
*/
|
||||
var sortConditions: MutableList<SortCondition> = mutableListOf()
|
||||
|
||||
/**
|
||||
* 关键字搜索,常用于模糊查询
|
||||
*/
|
||||
var keyword: String? = null
|
||||
|
||||
/**
|
||||
* 自定义过滤条件实体类
|
||||
*/
|
||||
var filter: T? = null
|
||||
|
||||
/**
|
||||
* 开始日期
|
||||
*/
|
||||
var startDate: Instant? = null
|
||||
|
||||
/**
|
||||
* 结束日期
|
||||
*/
|
||||
var endDate: Instant? = null
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
var enabled: Boolean? = null
|
||||
|
||||
/**
|
||||
* 是否删除
|
||||
*/
|
||||
var deleted: Boolean = false
|
||||
|
||||
/**
|
||||
* 计算分页偏移量
|
||||
*/
|
||||
fun getOffset(): Int = (currentPage - 1) * pageSize
|
||||
|
||||
/**
|
||||
* 校验日期范围是否合法(开始时间不能晚于结束时间)
|
||||
*/
|
||||
fun isDateRangeValid(): Boolean {
|
||||
return startDate == null || endDate == null || !startDate!!.isAfter(endDate)
|
||||
}
|
||||
}
|
||||
@ -1,104 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.util
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.Equal
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.In
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.Like
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.OrGroup
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.getPredicates
|
||||
import com.gewuyou.forgeboot.webmvc.dto.request.PageQueryReq
|
||||
import jakarta.persistence.criteria.Path
|
||||
import jakarta.persistence.criteria.Predicate
|
||||
import jakarta.persistence.criteria.Root
|
||||
import org.springframework.data.jpa.domain.Specification
|
||||
import kotlin.collections.isNotEmpty
|
||||
import kotlin.collections.toTypedArray
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
|
||||
/**
|
||||
* 动态构建 JPA Specification 的工具类
|
||||
*
|
||||
* 支持字段注解控制查询方式(@Like, @Equal, @In)
|
||||
* 支持嵌套字段路径(如 "user.name")
|
||||
* 支持 OR 条件组(@OrGroup("groupName"))
|
||||
* @since 2025-05-28 23:37:17
|
||||
* @author gewuyou
|
||||
*/
|
||||
object DynamicSpecificationBuilder {
|
||||
|
||||
/**
|
||||
* 构建动态查询 Specification
|
||||
*
|
||||
* @param query PageQuery<T> 查询参数对象
|
||||
* @param createAtName 时间字段名
|
||||
* @param enabledName 是否启用字段名
|
||||
* @param deletedName 是否删除字段名
|
||||
*/
|
||||
inline fun <reified T : Any, reified E> build(
|
||||
query: PageQueryReq<T>,
|
||||
createAtName: String = "createdAt",
|
||||
enabledName: String = "enabled",
|
||||
deletedName: String = "deleted",
|
||||
): Specification<E> {
|
||||
return Specification { root, _, builder ->
|
||||
val predicates = mutableListOf<Predicate>()
|
||||
|
||||
// 1. 加入基本条件(时间、启用、删除)
|
||||
predicates += query.getPredicates(builder, root, createAtName, enabledName, deletedName)
|
||||
|
||||
// 2. 处理 filter 字段
|
||||
val filterObj = query.filter ?: return@Specification builder.and(*predicates.toTypedArray())
|
||||
|
||||
// 3. OR 分组
|
||||
val orGroups = mutableMapOf<String, MutableList<Predicate>>()
|
||||
|
||||
T::class.memberProperties.forEach { prop ->
|
||||
prop.isAccessible = true
|
||||
val value = prop.get(filterObj) ?: return@forEach
|
||||
|
||||
val fieldPath = prop.name
|
||||
val path = getPath(root, fieldPath)
|
||||
|
||||
val predicate = when {
|
||||
prop.findAnnotation<Like>() != null && value is String ->
|
||||
builder.like(path as Path<String>, "%$value%")
|
||||
|
||||
prop.findAnnotation<Equal>() != null ->
|
||||
builder.equal(path, value)
|
||||
|
||||
prop.findAnnotation<In>() != null && value is Collection<*> ->
|
||||
path.`in`(value)
|
||||
|
||||
// 默认策略:非空值执行 equal
|
||||
else -> builder.equal(path, value)
|
||||
}
|
||||
|
||||
val orGroup = prop.findAnnotation<OrGroup>()?.value
|
||||
if (orGroup != null) {
|
||||
orGroups.getOrPut(orGroup) { mutableListOf() }.add(predicate)
|
||||
} else {
|
||||
predicates.add(predicate)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 添加 OR 条件组
|
||||
orGroups.values.forEach { group ->
|
||||
if (group.isNotEmpty()) {
|
||||
predicates.add(builder.or(*group.toTypedArray()))
|
||||
}
|
||||
}
|
||||
builder.and(*predicates.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持嵌套字段路径解析,例如 "user.name"
|
||||
*/
|
||||
fun <E> getPath(root: Root<E>, pathStr: String): Path<*> {
|
||||
return pathStr.split(".").fold(root as Path<*>) { path, part ->
|
||||
path.get<Any>(part)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
forgeboot.webmvc.dto.result.responseInfo.fail=失败
|
||||
forgeboot.webmvc.dto.result.responseInfo.ok=成功
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
forgeboot.webmvc.dto.result.responseInfo.fail=failed
|
||||
forgeboot.webmvc.dto.result.responseInfo.ok=success
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
forgeboot.webmvc.dto.result.responseInfo.fail=失败
|
||||
forgeboot.webmvc.dto.result.responseInfo.ok=成功
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
/gradlew text eol=lf
|
||||
*.bat text eol=crlf
|
||||
*.jar binary
|
||||
40
forgeboot-webmvc/exception-i18n/.gitignore
vendored
40
forgeboot-webmvc/exception-i18n/.gitignore
vendored
@ -1,40 +0,0 @@
|
||||
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
|
||||
@ -1,18 +0,0 @@
|
||||
plugins{
|
||||
alias(libs.plugins.forgeboot.i18n.keygen)
|
||||
alias(libs.plugins.kotlin.plugin.spring)
|
||||
}
|
||||
dependencies {
|
||||
implementation(project(Modules.Core.EXTENSION))
|
||||
api(project(Modules.I18n.STARTER))
|
||||
api(project(Modules.TRACE.STARTER))
|
||||
implementation(project(Modules.Webmvc.DTO))
|
||||
compileOnly(libs.springBootStarter.validation)
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
kapt(libs.springBoot.configuration.processor)
|
||||
}
|
||||
i18nKeyGen {
|
||||
rootPackage.set("com.gewuyou.forgeboot.webmvc.extension.i18n")
|
||||
readPath.set("src/main/resources/i18n/${project.name}")
|
||||
level.set(3)
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.exception.i18n.config
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.i18n.api.MessageResolver
|
||||
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
|
||||
import com.gewuyou.forgeboot.webmvc.exception.i18n.config.entities.WebMvcExceptionI18nProperties
|
||||
import com.gewuyou.forgeboot.webmvc.exception.i18n.handler.GlobalExceptionHandler
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.annotation.Order
|
||||
|
||||
/**
|
||||
*Web MVC 异常自动配置
|
||||
*
|
||||
* @since 2025-05-13 11:48:01
|
||||
* @author gewuyou
|
||||
*/
|
||||
@EnableConfigurationProperties(WebMvcExceptionI18nProperties::class)
|
||||
@Configuration
|
||||
class WebMvcExceptionAutoConfiguration {
|
||||
/**
|
||||
*默认消息解析器
|
||||
*
|
||||
* @since 2025-05-03 16:21:43
|
||||
* @author gewuyou
|
||||
*/
|
||||
@Bean
|
||||
@Order(Int.MAX_VALUE)
|
||||
@ConditionalOnMissingBean
|
||||
fun defaultMessageResolver(): MessageResolver = MessageResolver { code, _ -> code }
|
||||
|
||||
/**
|
||||
*默认请求ID提供商
|
||||
*
|
||||
* @since 2025-05-03 16:22:18
|
||||
* @author gewuyou
|
||||
*/
|
||||
@Bean
|
||||
@Order(Int.MAX_VALUE)
|
||||
@ConditionalOnMissingBean
|
||||
fun defaultRequestIdProvider(): RequestIdProvider = RequestIdProvider { "" }
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
fun i18nGlobalExceptionHandler(
|
||||
webMvcExceptionI18nProperties: WebMvcExceptionI18nProperties,
|
||||
messageResolver: MessageResolver,
|
||||
requestIdProvider: RequestIdProvider,
|
||||
): GlobalExceptionHandler {
|
||||
log.info("本地化全局异常处理程序创建成功!")
|
||||
return GlobalExceptionHandler(
|
||||
webMvcExceptionI18nProperties,
|
||||
messageResolver,
|
||||
requestIdProvider
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.exception.i18n.config.entities
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.extension.i18n.I18nKeys
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
|
||||
/**
|
||||
* Web Mvc异常属性
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-05-13 11:06:46
|
||||
*/
|
||||
@ConfigurationProperties("forgeboot.webmvc.exception.i18n")
|
||||
class WebMvcExceptionI18nProperties {
|
||||
/**
|
||||
* 设置其他通用外部异常的错误代码
|
||||
*
|
||||
* @param otherGeneralExternalExceptionErrorCode 其他通用外部异常的错误代码
|
||||
*/
|
||||
var otherGeneralExternalExceptionErrorCode: Int = 500
|
||||
|
||||
|
||||
/**
|
||||
* 设置其他通用外部异常的错误消息
|
||||
*
|
||||
* @param otherGeneralExternalExceptionErrorMessage 其他通用外部异常的错误消息
|
||||
*/
|
||||
var otherGeneralExternalExceptionErrorMessage: String = I18nKeys.Forgeboot.Webmvc.Exception.INVALID_SERVER_ERROR
|
||||
|
||||
/**
|
||||
* 设置默认验证异常的错误代码
|
||||
*
|
||||
* @param defaultValidationExceptionErrorCode 默认验证异常的错误代码
|
||||
*/
|
||||
var defaultValidationExceptionErrorCode: Int = 400
|
||||
|
||||
|
||||
/**
|
||||
* 设置默认验证异常的错误消息
|
||||
*
|
||||
* @param defaultValidationExceptionErrorMessage 默认验证异常的错误消息
|
||||
*/
|
||||
var defaultValidationExceptionErrorMessage: String = I18nKeys.Forgeboot.Webmvc.Exception.ILLEGAL_PARAMETER
|
||||
|
||||
/**
|
||||
* 设置默认验证异常的字段错误消息
|
||||
*
|
||||
* @param defaultValidationExceptionFieldErrorMessage 默认验证异常的字段错误消息
|
||||
*/
|
||||
var defaultValidationExceptionFieldErrorMessage: String = I18nKeys.Forgeboot.Webmvc.Exception.ILLEGAL_PARAMETER
|
||||
|
||||
/**
|
||||
* 设置默认无效参数异常的错误代码
|
||||
*
|
||||
* @param defaultInvalidParameterErrorCode 默认无效参数异常的错误代码
|
||||
*/
|
||||
var defaultInvalidParameterErrorCode: Int = 400
|
||||
|
||||
/**
|
||||
* 设置默认无效参数异常的错误消息
|
||||
*
|
||||
* @param defaultInvalidParameterErrorMessage 默认无效参数异常的错误消息
|
||||
*/
|
||||
var defaultInvalidParameterErrorMessage: String = I18nKeys.Forgeboot.Webmvc.Exception.ILLEGAL_PARAMETER
|
||||
|
||||
/**
|
||||
* 设置默认内部服务器错误异常的错误代码
|
||||
*
|
||||
* @param defaultInternalServerErrorCode 默认内部服务器错误异常的错误代码
|
||||
*/
|
||||
var defaultInternalServerErrorCode: Int = 500
|
||||
|
||||
|
||||
/**
|
||||
* 设置默认内部服务器错误异常的错误消息
|
||||
*
|
||||
* @param defaultInternalServerErrorMessage 默认内部服务器错误异常的错误消息
|
||||
*/
|
||||
var defaultInternalServerErrorMessage: String = I18nKeys.Forgeboot.Webmvc.Exception.INVALID_SERVER_ERROR
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.exception.i18n.core
|
||||
|
||||
import com.gewuyou.forgeboot.i18n.api.I18nResponseInformation
|
||||
import com.gewuyou.forgeboot.i18n.impl.exception.I18nBaseException
|
||||
|
||||
|
||||
/**
|
||||
* i18n全局异常: 继承I18nBaseException,如果其它模块需要抛出全局异常,则继承此类
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2024-11-23 16:45:10
|
||||
*/
|
||||
open class GlobalException : I18nBaseException {
|
||||
/**
|
||||
* 构造函数:初始化全局异常
|
||||
*
|
||||
* @param i18nResponseInformation 响应信息,包含错误代码和消息
|
||||
*/
|
||||
constructor(i18nResponseInformation: I18nResponseInformation) : super(i18nResponseInformation)
|
||||
|
||||
/**
|
||||
* 构造函数:初始化全局异常,并包含原始异常
|
||||
*
|
||||
* @param i18nResponseInformation 响应信息,包含错误代码和消息
|
||||
* @param cause 原始异常
|
||||
*/
|
||||
constructor(i18nResponseInformation: I18nResponseInformation, cause: Throwable) : super(i18nResponseInformation, cause)
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.exception.core
|
||||
|
||||
import com.gewuyou.forgeboot.i18n.api.I18nInternalInformation
|
||||
|
||||
|
||||
/**
|
||||
* 内部异常
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2024-11-24 21:14:03
|
||||
*/
|
||||
open class InternalException : RuntimeException {
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
val errorMessage: String
|
||||
|
||||
/**
|
||||
* 可选(国际化内部错误信息码)
|
||||
*/
|
||||
@Transient
|
||||
val i18nInternalInformation: I18nInternalInformation?
|
||||
|
||||
/**
|
||||
* 构造一个新的运行时异常,其详细消息为`null`。
|
||||
* 原因尚未初始化,可以随后通过调用[.initCause]进行初始化。
|
||||
*
|
||||
* @param errorMessage 详细消息
|
||||
*/
|
||||
constructor(errorMessage: String) {
|
||||
this.errorMessage = errorMessage
|
||||
this.i18nInternalInformation = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的详细消息和原因构造新的运行时异常。
|
||||
*
|
||||
* @param errorMessage 详细消息
|
||||
* @param cause 异常的原因
|
||||
*/
|
||||
constructor(errorMessage: String, cause: Throwable?) : super(errorMessage, cause) {
|
||||
this.errorMessage = errorMessage
|
||||
this.i18nInternalInformation = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的详细消息和国际化内部错误信息码构造新的运行时异常。
|
||||
*
|
||||
* @param errorMessage 详细消息
|
||||
* @param i18nInternalInformation 国际化内部错误信息码
|
||||
*/
|
||||
constructor(errorMessage: String, i18nInternalInformation: I18nInternalInformation?) {
|
||||
this.errorMessage = errorMessage
|
||||
this.i18nInternalInformation = i18nInternalInformation
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的详细消息、原因和国际化内部错误信息码构造新的运行时异常。
|
||||
*GlobalExceptionHandler
|
||||
* @param errorMessage 详细消息
|
||||
* @param cause 异常的原因
|
||||
* @param i18nInternalInformation 国际化内部错误信息码
|
||||
*/
|
||||
constructor(errorMessage: String, cause: Throwable?, i18nInternalInformation: I18nInternalInformation?) : super(
|
||||
errorMessage,
|
||||
cause
|
||||
) {
|
||||
this.errorMessage = errorMessage
|
||||
this.i18nInternalInformation = i18nInternalInformation
|
||||
}
|
||||
}
|
||||
@ -1,165 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.exception.i18n.handler
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.core.extension.log
|
||||
import com.gewuyou.forgeboot.i18n.api.MessageResolver
|
||||
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
|
||||
import com.gewuyou.forgeboot.webmvc.dto.I18nResult
|
||||
import com.gewuyou.forgeboot.webmvc.exception.i18n.config.entities.WebMvcExceptionI18nProperties
|
||||
import com.gewuyou.forgeboot.webmvc.exception.i18n.core.GlobalException
|
||||
import com.gewuyou.forgeboot.webmvc.exception.core.InternalException
|
||||
|
||||
|
||||
import jakarta.validation.ConstraintViolationException
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||
|
||||
/**
|
||||
* i18n全局异常处理类
|
||||
*
|
||||
* 该类用于统一处理整个应用中抛出的各种异常,以提供统一的错误响应格式
|
||||
* 它通过使用@RestControllerAdvice注解来标识这是一个全局异常处理类
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2024-04-13 上午12:22:18
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
class GlobalExceptionHandler(
|
||||
private val webMvcExceptionI18nProperties: WebMvcExceptionI18nProperties,
|
||||
private val messageResolver: MessageResolver,
|
||||
private val requestIdProvider: RequestIdProvider,
|
||||
) {
|
||||
/**
|
||||
* 异常处理器
|
||||
*
|
||||
* 用于处理除特定异常外的所有其他异常
|
||||
*
|
||||
* @param e 异常
|
||||
* @return 响应
|
||||
* @since 2024/4/13 上午12:29
|
||||
*/
|
||||
@ExceptionHandler(Exception::class)
|
||||
fun handleOtherException(e: Exception): I18nResult<String> {
|
||||
log.error("other exception:", e)
|
||||
return I18nResult.failure(
|
||||
webMvcExceptionI18nProperties.otherGeneralExternalExceptionErrorCode,
|
||||
webMvcExceptionI18nProperties.otherGeneralExternalExceptionErrorMessage,
|
||||
null, null, messageResolver, requestIdProvider
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 @Valid 和 @Validated 校验失败抛出的 MethodArgumentNotValidException 异常
|
||||
*
|
||||
* 该方法首先尝试返回字段错误信息,如果没有字段错误则尝试返回全局错误信息,
|
||||
* 如果都没有则返回默认的校验异常信息
|
||||
*
|
||||
* @param ex 异常
|
||||
* @return 响应信息
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException::class)
|
||||
fun handleValidationExceptions(ex: MethodArgumentNotValidException): I18nResult<String> {
|
||||
// 返回字段错误
|
||||
for (fieldError in ex.bindingResult.fieldErrors) {
|
||||
return I18nResult.failure(
|
||||
HttpStatus.BAD_REQUEST.value(),
|
||||
fieldError.defaultMessage ?: webMvcExceptionI18nProperties.defaultValidationExceptionFieldErrorMessage,
|
||||
null,
|
||||
null,
|
||||
messageResolver,
|
||||
requestIdProvider
|
||||
)
|
||||
}
|
||||
// 返回全局错误
|
||||
for (objectError in ex.bindingResult.globalErrors) {
|
||||
return I18nResult.failure(
|
||||
HttpStatus.BAD_REQUEST.value(),
|
||||
objectError.defaultMessage ?: webMvcExceptionI18nProperties.defaultValidationExceptionErrorMessage,
|
||||
null,
|
||||
null,
|
||||
messageResolver,
|
||||
requestIdProvider
|
||||
)
|
||||
}
|
||||
return I18nResult.failure(
|
||||
webMvcExceptionI18nProperties.defaultValidationExceptionErrorCode,
|
||||
webMvcExceptionI18nProperties.defaultValidationExceptionErrorMessage,
|
||||
null,
|
||||
null,
|
||||
messageResolver,
|
||||
requestIdProvider
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 JSR 303/JSR 380 校验失败抛出的 ConstraintViolationException 异常
|
||||
*
|
||||
* 该方法遍历约束违规信息,并返回第一个错误信息如果存在多个错误,
|
||||
* 否则返回默认的无效参数错误信息
|
||||
*
|
||||
* @param ex 异常
|
||||
* @return 响应信息
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException::class)
|
||||
fun handleConstraintViolationException(ex: ConstraintViolationException): I18nResult<String> {
|
||||
for (constraintViolation in ex.constraintViolations) {
|
||||
return I18nResult.failure(
|
||||
HttpStatus.BAD_REQUEST.value(), constraintViolation.message,
|
||||
null, null, messageResolver, requestIdProvider
|
||||
)
|
||||
}
|
||||
return I18nResult.failure(
|
||||
webMvcExceptionI18nProperties.defaultInvalidParameterErrorCode,
|
||||
webMvcExceptionI18nProperties.defaultInvalidParameterErrorMessage,
|
||||
null,
|
||||
null,
|
||||
messageResolver,
|
||||
requestIdProvider
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* 用于处理全局异常(GlobalException),返回错误代码和国际化消息代码
|
||||
*
|
||||
* @param e 异常
|
||||
* @return 返回的结果
|
||||
* @since 2024/4/13 下午1:56
|
||||
*/
|
||||
@ExceptionHandler(GlobalException::class)
|
||||
fun handleGlobalException(e: GlobalException): I18nResult<String> {
|
||||
return I18nResult.failure(
|
||||
e.errorCode, e.errorI18nMessageCode,
|
||||
null, e.errorI18nMessageArgs, messageResolver, requestIdProvider
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部异常处理器
|
||||
*
|
||||
* 用于处理内部异常(InternalException),记录异常信息并返回默认的内部服务器错误信息
|
||||
*
|
||||
* @param e 异常
|
||||
* @return 返回的结果
|
||||
*/
|
||||
@ExceptionHandler(InternalException::class)
|
||||
fun handleGlobalException(e: InternalException): I18nResult<String> {
|
||||
log.error("内部异常: 异常信息: {}", e.errorMessage, e)
|
||||
e.i18nInternalInformation?.responseI8nMessageCode?.let {
|
||||
log.error(
|
||||
"i18nMessage: {}", messageResolver.resolve(it, e.i18nInternalInformation.responseI8nMessageArgs)
|
||||
)
|
||||
}
|
||||
return I18nResult.failure(
|
||||
webMvcExceptionI18nProperties.defaultInternalServerErrorCode,
|
||||
webMvcExceptionI18nProperties.defaultInternalServerErrorMessage,
|
||||
null,
|
||||
null,
|
||||
messageResolver,
|
||||
requestIdProvider
|
||||
)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user