mirror of
https://github.moeyy.xyz/https://github.com/GeWuYou/forgeboot
synced 2025-10-28 04:14:24 +08:00
Compare commits
31 Commits
88f016dad2
...
58593f7b21
| Author | SHA1 | Date | |
|---|---|---|---|
| 58593f7b21 | |||
| 6e6099b02b | |||
| 5c74c42a24 | |||
| 7c914e23af | |||
| 78ca098488 | |||
| 567e7cc2b7 | |||
| 050c611dd6 | |||
| ad837f5d17 | |||
| 98573e5860 | |||
| 439e00552e | |||
| ba88f3e9d3 | |||
| fff96d18bb | |||
| 3893d2ec38 | |||
| e2cc447ae3 | |||
| 48eab9c7db | |||
| ab035cce14 | |||
| 530936b76a | |||
| b4cfc9865c | |||
| 1fa28e4c57 | |||
| ff8593007d | |||
| d357a3d754 | |||
| 35a0f5eb32 | |||
| 7b020fc6d4 | |||
| fa9a5f9039 | |||
| 6ea0bab287 | |||
| d8fe54db38 | |||
| d31e47d1f8 | |||
| b41d9b5c46 | |||
| b58cb6b339 | |||
| b44b5a1570 | |||
| 48228574be |
@ -8,6 +8,13 @@ plugins {
|
|||||||
alias(libs.plugins.axionRelease)
|
alias(libs.plugins.axionRelease)
|
||||||
// Kotlin Spring 支持
|
// Kotlin Spring 支持
|
||||||
alias(libs.plugins.kotlin.plugin.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 版本插件
|
// 配置 SCM 版本插件
|
||||||
@ -15,7 +22,7 @@ scmVersion {
|
|||||||
tag {
|
tag {
|
||||||
prefix.set("") // 不加 v,生成 1.0.1 而不是 v1.0.1
|
prefix.set("") // 不加 v,生成 1.0.1 而不是 v1.0.1
|
||||||
}
|
}
|
||||||
versionIncrementer("incrementMinorIfNotOnRelease")
|
versionIncrementer("incrementPatch")
|
||||||
hooks {
|
hooks {
|
||||||
pre(
|
pre(
|
||||||
"fileUpdate", mapOf(
|
"fileUpdate", mapOf(
|
||||||
@ -57,14 +64,12 @@ allprojects {
|
|||||||
|
|
||||||
// 子项目配置
|
// 子项目配置
|
||||||
subprojects {
|
subprojects {
|
||||||
version = rootProject.version
|
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
val isRootModule = project.getPropertyByBoolean(ProjectFlags.IS_ROOT_MODULE)
|
val isRootModule = project.getPropertyByBoolean(ProjectFlags.IS_ROOT_MODULE)
|
||||||
val isStarterModule = project.name.contains("starter")
|
|
||||||
val parentProject = project.parent
|
val parentProject = project.parent
|
||||||
// 让父项目引入子项目
|
// 让父项目引入子项目
|
||||||
parentProject?.dependencies?.add("api", project(project.path))
|
parentProject?.dependencies?.add("api", project(project.path))
|
||||||
if (isStarterModule && !isRootModule) {
|
if (!isRootModule) {
|
||||||
// Starter 模块依赖配置
|
// Starter 模块依赖配置
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(platform(libs.springBootDependencies.bom))
|
implementation(platform(libs.springBootDependencies.bom))
|
||||||
@ -75,25 +80,41 @@ subprojects {
|
|||||||
}
|
}
|
||||||
// 应用插件和配置
|
// 应用插件和配置
|
||||||
val libs = rootProject.libs
|
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 {
|
apply {
|
||||||
plugin(libs.plugins.java.get().pluginId)
|
plugin(libs.plugins.java.get().pluginId)
|
||||||
plugin(libs.plugins.javaLibrary.get().pluginId)
|
plugin(libs.plugins.javaLibrary.get().pluginId)
|
||||||
plugin(libs.plugins.maven.publish.get().pluginId)
|
plugin(libs.plugins.maven.publish.get().pluginId)
|
||||||
plugin(libs.plugins.kotlin.jvm.get().pluginId)
|
plugin(libs.plugins.kotlin.jvm.get().pluginId)
|
||||||
plugin(libs.plugins.axionRelease.get().pluginId)
|
plugin(libs.plugins.axionRelease.get().pluginId)
|
||||||
|
plugin(libs.plugins.kotlin.kapt.get().pluginId)
|
||||||
// 导入仓库配置
|
// 导入仓库配置
|
||||||
from(file("$configDir/repositories.gradle.kts"))
|
from(file("$configDir/repositories.gradle.kts"))
|
||||||
// 导入源代码任务
|
|
||||||
from(file("$tasksDir/sourceTask.gradle.kts"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发布配置
|
// 发布配置
|
||||||
publishing {
|
publishing {
|
||||||
repositories {
|
repositories {
|
||||||
// 本地仓库
|
// 本地仓库
|
||||||
maven {
|
maven {
|
||||||
name = "localRepo"
|
name = "localRepo"
|
||||||
url = uri("file://D:/Maven/mvn_repository")
|
url = uri("file://D:/Config/Jrebel/.jrebel/.m2/repository")
|
||||||
}
|
}
|
||||||
// GitHub Packages 仓库
|
// GitHub Packages 仓库
|
||||||
maven {
|
maven {
|
||||||
|
|||||||
@ -13,3 +13,13 @@ dependencies {
|
|||||||
// Add a dependency on the Kotlin Gradle plugin, so that convention plugins can apply it.
|
// Add a dependency on the Kotlin Gradle plugin, so that convention plugins can apply it.
|
||||||
implementation(libs.kotlinGradlePlugin)
|
implementation(libs.kotlinGradlePlugin)
|
||||||
}
|
}
|
||||||
|
gradlePlugin {
|
||||||
|
plugins {
|
||||||
|
register("forgeboot-i18n-key-gen") {
|
||||||
|
id = "i18n-key-gen"
|
||||||
|
implementationClass = "I18nKeyGenPlugin"
|
||||||
|
description =
|
||||||
|
"提供一个用于生成 i18n key文件 的插件"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
184
buildSrc/src/main/kotlin/I18nKeyGenPlugin.kt
Normal file
184
buildSrc/src/main/kotlin/I18nKeyGenPlugin.kt
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/**
|
||||||
|
* 本地化键生成插件
|
||||||
|
*
|
||||||
|
* 该插件用于自动生成本地化键的代码,以简化国际化(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,17 +9,25 @@ object Modules {
|
|||||||
|
|
||||||
object Webmvc {
|
object Webmvc {
|
||||||
const val STARTER = ":forgeboot-webmvc-spring-boot-starter"
|
const val STARTER = ":forgeboot-webmvc-spring-boot-starter"
|
||||||
const val VERSION_STARTER = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-version-spring-boot-starter"
|
const val DTO = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-dto"
|
||||||
const val LOGGER_STARTER = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-logger-spring-boot-starter"
|
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"
|
||||||
}
|
}
|
||||||
object Core{
|
object Core{
|
||||||
const val ROOT = ":forgeboot-core"
|
const val ROOT = ":forgeboot-core"
|
||||||
const val EXTENSION = ":forgeboot-core:forgeboot-core-extension"
|
const val EXTENSION = ":forgeboot-core:forgeboot-core-extension"
|
||||||
}
|
}
|
||||||
object Common {
|
object I18n {
|
||||||
const val ROOT = ":forgeboot-common"
|
const val STARTER = ":forgeboot-i18n-spring-boot-starter"
|
||||||
const val RESULT = ":forgeboot-common:forgeboot-common-result"
|
const val API = ":forgeboot-i18n-spring-boot-starter:forgeboot-i18n-api"
|
||||||
const val RESULT_IMPL = ":forgeboot-common:forgeboot-common-result:forgeboot-common-result-impl"
|
const val IMPL = ":forgeboot-i18n-spring-boot-starter:forgeboot-i18n-impl"
|
||||||
const val RESULT_API = ":forgeboot-common:forgeboot-common-result:forgeboot-common-result-api"
|
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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
object ProjectFlags {
|
object ProjectFlags {
|
||||||
const val IS_ROOT_MODULE = "isRootModule"
|
const val IS_ROOT_MODULE = "isRootModule"
|
||||||
|
const val IS_PUBLISH_MODULE = "isPublishModule"
|
||||||
|
const val ARTIFACT_ID = "artifactId"
|
||||||
}
|
}
|
||||||
@ -8,12 +8,6 @@ repositories {
|
|||||||
isAllowInsecureProtocol = true
|
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 {
|
maven {
|
||||||
url = uri("https://maven.aliyun.com/repository/public/")
|
url = uri("https://maven.aliyun.com/repository/public/")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
// 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 文件"
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
extra {
|
|
||||||
// 标记为根项目
|
|
||||||
setProperty(ProjectFlags.IS_ROOT_MODULE, true)
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
extra {
|
|
||||||
// 标记为根项目
|
|
||||||
setProperty(ProjectFlags.IS_ROOT_MODULE, true)
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(project(Modules.Common.RESULT_API))
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
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 {
|
dependencies {
|
||||||
val libs = rootProject.libs
|
val libs = rootProject.libs
|
||||||
implementation(libs.slf4j.api)
|
compileOnly(libs.slf4j.api)
|
||||||
}
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
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 }
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
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,12 +2,6 @@ package com.gewuyou.forgeboot.core.extension
|
|||||||
|
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
/**
|
|
||||||
*日志扩展类
|
|
||||||
*
|
|
||||||
* @since 2025-01-02 12:49:13
|
|
||||||
* @author gewuyou
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志扩展
|
* 日志扩展
|
||||||
@ -1,8 +1,6 @@
|
|||||||
dependencies {
|
extra{
|
||||||
implementation(project(Modules.Core.EXTENSION))
|
setProperty(ProjectFlags.IS_ROOT_MODULE,true)
|
||||||
implementation(project(Modules.Common.RESULT_API))
|
}
|
||||||
|
dependencies {
|
||||||
compileOnly(libs.springBootStarter.web)
|
|
||||||
// Spring Boot WebFlux
|
|
||||||
compileOnly(libs.springBootStarter.webflux)
|
|
||||||
}
|
}
|
||||||
|
|||||||
8
forgeboot-i18n/forgeboot-i18n-api/build.gradle.kts
Normal file
8
forgeboot-i18n/forgeboot-i18n-api/build.gradle.kts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly(platform(libs.springBootDependencies.bom))
|
||||||
|
// compileOnly(platform(libs.springCloudDependencies.bom))
|
||||||
|
// compileOnly(libs.springBootStarter.web)
|
||||||
|
compileOnly(libs.springBootStarter.webflux)
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.gewuyou.forgeboot.i18n.entity
|
package com.gewuyou.forgeboot.i18n.api
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内部信息(用于扩展项目内的i18n信息)
|
* 内部信息(用于扩展项目内的i18n信息)
|
||||||
@ -6,10 +6,16 @@ package com.gewuyou.forgeboot.i18n.entity
|
|||||||
* @author gewuyou
|
* @author gewuyou
|
||||||
* @since 2024-11-26 17:14:15
|
* @since 2024-11-26 17:14:15
|
||||||
*/
|
*/
|
||||||
interface InternalInformation {
|
interface I18nInternalInformation {
|
||||||
/**
|
/**
|
||||||
* 获取i18n响应信息code
|
* 获取i18n响应信息code
|
||||||
* @return 响应信息 code
|
* @return 响应信息 code
|
||||||
*/
|
*/
|
||||||
val responseI8nMessageCode: String?
|
val responseI8nMessageCode: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取i18n响应信息参数
|
||||||
|
* @return 响应信息 参数数组
|
||||||
|
*/
|
||||||
|
val responseI8nMessageArgs: Array<Any>?
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.gewuyou.forgeboot.common.result.api
|
package com.gewuyou.forgeboot.i18n.api
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应信息
|
* 响应信息
|
||||||
@ -6,7 +6,7 @@ package com.gewuyou.forgeboot.common.result.api
|
|||||||
* @author gewuyou
|
* @author gewuyou
|
||||||
* @since 2025-05-03 16:31:59
|
* @since 2025-05-03 16:31:59
|
||||||
*/
|
*/
|
||||||
interface ResponseInformation {
|
interface I18nResponseInformation {
|
||||||
/**
|
/**
|
||||||
* 获取响应码
|
* 获取响应码
|
||||||
* @return 响应码
|
* @return 响应码
|
||||||
@ -18,4 +18,10 @@ interface ResponseInformation {
|
|||||||
* @return 响应信息 code
|
* @return 响应信息 code
|
||||||
*/
|
*/
|
||||||
val responseI8nMessageCode: String
|
val responseI8nMessageCode: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取i18n响应信息参数
|
||||||
|
* @return 响应信息 参数数组
|
||||||
|
*/
|
||||||
|
val responseI8nMessageArgs: Array<Any>?
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.gewuyou.forgeboot.common.result.api
|
package com.gewuyou.forgeboot.i18n.api
|
||||||
/**
|
/**
|
||||||
* 消息解析器接口
|
* 消息解析器接口
|
||||||
*
|
*
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package com.gewuyou.forgeboot.i18n.api
|
||||||
|
|
||||||
|
import org.springframework.web.server.WebFilter
|
||||||
|
|
||||||
|
interface WebFluxLocaleResolver: WebFilter
|
||||||
@ -0,0 +1 @@
|
|||||||
|
spring.application.name=forgeboot-i18n-api
|
||||||
10
forgeboot-i18n/forgeboot-i18n-autoconfigure/build.gradle.kts
Normal file
10
forgeboot-i18n/forgeboot-i18n-autoconfigure/build.gradle.kts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
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,12 +1,12 @@
|
|||||||
package com.gewuyou.forgeboot.i18n.config
|
package com.gewuyou.forgeboot.i18n.autoconfigure
|
||||||
|
|
||||||
|
|
||||||
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.core.extension.log
|
||||||
import com.gewuyou.forgeboot.i18n.config.entity.I18nProperties
|
import com.gewuyou.forgeboot.i18n.api.MessageResolver
|
||||||
import com.gewuyou.forgeboot.i18n.resolver.I18nMessageResolver
|
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 jakarta.servlet.http.HttpServletRequest
|
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.ConditionalOnClass
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||||
@ -38,12 +38,11 @@ open class I18nAutoConfiguration(
|
|||||||
* 配置并创建一个国际化的消息源
|
* 配置并创建一个国际化的消息源
|
||||||
*
|
*
|
||||||
* 此方法首先会扫描指定路径下所有的国际化属性文件,然后将这些文件路径设置到消息源中
|
* 此方法首先会扫描指定路径下所有的国际化属性文件,然后将这些文件路径设置到消息源中
|
||||||
* 如果项目中还没有名为 [MESSAGE_SOURCE_BEAN_NAME] 的消息源 bean,则会创建一个
|
|
||||||
*
|
*
|
||||||
* @return MessageSource 国际化消息源
|
* @return MessageSource 国际化消息源
|
||||||
*/
|
*/
|
||||||
@Bean(name = [MESSAGE_SOURCE_BEAN_NAME])
|
@Bean
|
||||||
@ConditionalOnMissingBean(name = [MESSAGE_SOURCE_BEAN_NAME])
|
@ConditionalOnMissingBean
|
||||||
open fun messageSource(): MessageSource {
|
open fun messageSource(): MessageSource {
|
||||||
log.info("开始加载 I18n 配置...")
|
log.info("开始加载 I18n 配置...")
|
||||||
val messageSource = ReloadableResourceBundleMessageSource()
|
val messageSource = ReloadableResourceBundleMessageSource()
|
||||||
@ -62,13 +61,13 @@ open class I18nAutoConfiguration(
|
|||||||
* 该方法通过Spring的条件注解有选择性地创建一个MessageResolver实例
|
* 该方法通过Spring的条件注解有选择性地创建一个MessageResolver实例
|
||||||
* 主要用于解决国际化消息的解析问题
|
* 主要用于解决国际化消息的解析问题
|
||||||
*
|
*
|
||||||
* @param forgebootI18nMessageSource 一个MessageSource实例,用于解析国际化消息
|
* @param i18nMessageSource 一个MessageSource实例,用于解析国际化消息
|
||||||
* @return 返回一个MessageResolver实例,用于在国际化的环境中解析消息
|
* @return 返回一个MessageResolver实例,用于在国际化的环境中解析消息
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean(MessageResolver::class)
|
@ConditionalOnMissingBean(MessageResolver::class)
|
||||||
open fun i18nMessageResolver(@Qualifier(MESSAGE_SOURCE_BEAN_NAME) forgebootI18nMessageSource: MessageSource): MessageResolver {
|
open fun i18nMessageResolver(i18nMessageSource: MessageSource): MessageResolver {
|
||||||
return I18nMessageResolver(forgebootI18nMessageSource)
|
return I18nMessageResolver(i18nMessageSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -164,11 +163,4 @@ open class I18nAutoConfiguration(
|
|||||||
log.info("创建 WebFlux 区域设置解析器...")
|
log.info("创建 WebFlux 区域设置解析器...")
|
||||||
return ReactiveLocaleResolver(i18nProperties)
|
return ReactiveLocaleResolver(i18nProperties)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* 消息源 bean 的名称
|
|
||||||
*/
|
|
||||||
const val MESSAGE_SOURCE_BEAN_NAME: String = "forgebootI18nMessageSource"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.gewuyou.forgeboot.i18n.config
|
package com.gewuyou.forgeboot.i18n.autoconfigure
|
||||||
|
|
||||||
import com.gewuyou.forgeboot.core.extension.log
|
import com.gewuyou.forgeboot.core.extension.log
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
com.gewuyou.forgeboot.i18n.autoconfigure.I18nAutoConfiguration
|
||||||
|
com.gewuyou.forgeboot.i18n.autoconfigure.I18nWebConfiguration
|
||||||
7
forgeboot-i18n/forgeboot-i18n-impl/build.gradle.kts
Normal file
7
forgeboot-i18n/forgeboot-i18n-impl/build.gradle.kts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
dependencies {
|
||||||
|
compileOnly(platform(libs.springBootDependencies.bom))
|
||||||
|
compileOnly(platform(libs.springCloudDependencies.bom))
|
||||||
|
compileOnly(libs.springBootStarter.web)
|
||||||
|
compileOnly(libs.springBootStarter.webflux)
|
||||||
|
compileOnly(project(Modules.I18n.API))
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
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"
|
||||||
|
}
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
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,7 +1,9 @@
|
|||||||
package com.gewuyou.forgeboot.i18n.filter
|
package com.gewuyou.forgeboot.i18n.impl.filter
|
||||||
|
|
||||||
|
|
||||||
import com.gewuyou.forgeboot.i18n.config.entity.I18nProperties
|
import com.gewuyou.forgeboot.i18n.api.WebFluxLocaleResolver
|
||||||
|
import com.gewuyou.forgeboot.i18n.impl.config.I18nProperties
|
||||||
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.context.i18n.LocaleContextHolder
|
import org.springframework.context.i18n.LocaleContextHolder
|
||||||
import org.springframework.util.StringUtils
|
import org.springframework.util.StringUtils
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package com.gewuyou.forgeboot.i18n.resolver
|
package com.gewuyou.forgeboot.i18n.impl.resolver
|
||||||
|
|
||||||
import com.gewuyou.forgeboot.common.result.api.MessageResolver
|
|
||||||
|
import com.gewuyou.forgeboot.i18n.api.MessageResolver
|
||||||
import org.springframework.context.MessageSource
|
import org.springframework.context.MessageSource
|
||||||
import org.springframework.context.i18n.LocaleContextHolder
|
import org.springframework.context.i18n.LocaleContextHolder
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
spring.application.name=forgeboot-i18n-impl-starter
|
||||||
@ -1,63 +0,0 @@
|
|||||||
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,21 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
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,5 +0,0 @@
|
|||||||
package com.gewuyou.forgeboot.i18n.filter
|
|
||||||
|
|
||||||
import org.springframework.web.server.WebFilter
|
|
||||||
|
|
||||||
interface WebFluxLocaleResolver:WebFilter
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
com.gewuyou.forgeboot.i18n.config.I18nAutoConfiguration
|
|
||||||
com.gewuyou.forgeboot.i18n.config.I18nWebConfiguration
|
|
||||||
@ -1,14 +1,6 @@
|
|||||||
dependencies {
|
extra{
|
||||||
implementation(project(Modules.Core.EXTENSION))
|
setProperty(ProjectFlags.IS_ROOT_MODULE,true)
|
||||||
implementation(project(Modules.Common.RESULT_API))
|
}
|
||||||
|
dependencies {
|
||||||
// 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,4 +1,3 @@
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.gewuyou.forgeboot.common.result.api
|
package com.gewuyou.forgeboot.trace.api
|
||||||
/**
|
/**
|
||||||
* 请求ID提供商接口
|
* 请求ID提供商接口
|
||||||
*
|
*
|
||||||
@ -0,0 +1 @@
|
|||||||
|
spring.application.name=api
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
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,22 +1,18 @@
|
|||||||
package com.gewuyou.forgeboot.trace.config
|
package com.gewuyou.forgeboot.trace.autoconfig
|
||||||
|
|
||||||
import com.gewuyou.forgeboot.common.result.api.RequestIdProvider
|
|
||||||
import com.gewuyou.forgeboot.trace.config.entities.TraceProperties
|
import com.gewuyou.forgeboot.core.extension.log
|
||||||
import com.gewuyou.forgeboot.trace.decorator.RequestIdTaskDecorator
|
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
|
||||||
import com.gewuyou.forgeboot.trace.filter.ReactiveRequestIdFilter
|
import com.gewuyou.forgeboot.trace.impl.config.TraceProperties
|
||||||
import com.gewuyou.forgeboot.trace.filter.RequestIdFilter
|
import com.gewuyou.forgeboot.trace.impl.decorator.RequestIdTaskDecorator
|
||||||
import com.gewuyou.forgeboot.trace.filter.WebClientRequestIdFilter
|
import com.gewuyou.forgeboot.trace.impl.filter.ReactiveRequestIdFilter
|
||||||
import com.gewuyou.forgeboot.trace.interceptor.FeignRequestIdInterceptor
|
import com.gewuyou.forgeboot.trace.impl.filter.RequestIdFilter
|
||||||
import com.gewuyou.forgeboot.trace.provider.TraceRequestIdProvider
|
import com.gewuyou.forgeboot.trace.impl.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.ConditionalOnMissingBean
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||||
import org.springframework.cloud.openfeign.FeignAutoConfiguration
|
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.web.reactive.function.client.WebClient
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 跟踪自动配置
|
* 跟踪自动配置
|
||||||
@ -38,7 +34,10 @@ open class TraceAutoConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(name = ["spring.main.web-application-type"], havingValue = "servlet", matchIfMissing = true)
|
@ConditionalOnProperty(name = ["spring.main.web-application-type"], havingValue = "servlet", matchIfMissing = true)
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
open fun requestIdFilter(traceProperties: TraceProperties): RequestIdFilter = RequestIdFilter(traceProperties)
|
open fun requestIdFilter(traceProperties: TraceProperties): RequestIdFilter {
|
||||||
|
log.info("RequestIdFilter 已创建!")
|
||||||
|
return RequestIdFilter(traceProperties)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring WebFlux 过滤器(仅当 Spring WebFlux 存在时生效)
|
* Spring WebFlux 过滤器(仅当 Spring WebFlux 存在时生效)
|
||||||
@ -50,8 +49,10 @@ open class TraceAutoConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(name = ["spring.main.web-application-type"], havingValue = "reactive")
|
@ConditionalOnProperty(name = ["spring.main.web-application-type"], havingValue = "reactive")
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
open fun reactiveRequestIdFilter(traceProperties: TraceProperties): ReactiveRequestIdFilter =
|
open fun reactiveRequestIdFilter(traceProperties: TraceProperties): ReactiveRequestIdFilter {
|
||||||
ReactiveRequestIdFilter(traceProperties)
|
log.info("ReactiveRequestIdFilter 已创建!")
|
||||||
|
return ReactiveRequestIdFilter(traceProperties)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求ID提供者(用于生成请求ID)
|
* 请求ID提供者(用于生成请求ID)
|
||||||
@ -61,20 +62,10 @@ open class TraceAutoConfiguration {
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean(RequestIdProvider::class)
|
@ConditionalOnMissingBean(RequestIdProvider::class)
|
||||||
open fun traceRequestIdProvider(): TraceRequestIdProvider = TraceRequestIdProvider()
|
open fun traceRequestIdProvider(): TraceRequestIdProvider {
|
||||||
|
log.info("TraceRequestIdProvider 已创建!")
|
||||||
/**
|
return TraceRequestIdProvider()
|
||||||
* Feign 拦截器(仅当 Feign 存在时生效)
|
}
|
||||||
*
|
|
||||||
* 该拦截器用于在Feign客户端中传递请求ID
|
|
||||||
* @param traceProperties 跟踪配置属性
|
|
||||||
* @return FeignRequestIdInterceptor实例
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnClass(FeignAutoConfiguration::class)
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
open fun feignRequestIdInterceptor(traceProperties: TraceProperties): FeignRequestIdInterceptor =
|
|
||||||
FeignRequestIdInterceptor(traceProperties)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 线程池装饰器(用于 @Async)
|
* 线程池装饰器(用于 @Async)
|
||||||
@ -85,19 +76,9 @@ open class TraceAutoConfiguration {
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
open fun requestIdTaskDecorator(traceProperties: TraceProperties): RequestIdTaskDecorator =
|
open fun requestIdTaskDecorator(traceProperties: TraceProperties): RequestIdTaskDecorator {
|
||||||
RequestIdTaskDecorator(traceProperties)
|
log.info("RequestIdTaskDecorator 已创建!")
|
||||||
|
return RequestIdTaskDecorator(traceProperties)
|
||||||
/**
|
|
||||||
* 配置 WebClient 并自动添加请求 ID 过滤器(仅在 WebClient 已存在时引入)
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnBean(WebClient.Builder::class) // 如果 WebClient.Builder 已存在,则添加过滤器
|
|
||||||
open fun webClientRequestIdFilter(
|
|
||||||
webClientBuilder: WebClient.Builder,
|
|
||||||
traceProperties: TraceProperties
|
|
||||||
): WebClient.Builder {
|
|
||||||
// 在现有 WebClient 配置中添加请求 ID 过滤器
|
|
||||||
return webClientBuilder.filter(WebClientRequestIdFilter(traceProperties))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
com.gewuyou.forgeboot.trace.autoconfig.TraceAutoConfiguration
|
||||||
|
com.gewuyou.forgeboot.trace.autoconfig.WebClientTraceAutoConfiguration
|
||||||
|
com.gewuyou.forgeboot.trace.autoconfig.FeignTraceAutoConfiguration
|
||||||
12
forgeboot-trace/forgeboot-trace-impl/build.gradle.kts
Normal file
12
forgeboot-trace/forgeboot-trace-impl/build.gradle.kts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
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,6 +1,6 @@
|
|||||||
package com.gewuyou.forgeboot.trace.decorator
|
package com.gewuyou.forgeboot.trace.impl.decorator
|
||||||
|
|
||||||
import com.gewuyou.forgeboot.trace.config.entities.TraceProperties
|
import com.gewuyou.forgeboot.trace.impl.config.TraceProperties
|
||||||
import org.slf4j.MDC
|
import org.slf4j.MDC
|
||||||
import org.springframework.core.task.TaskDecorator
|
import org.springframework.core.task.TaskDecorator
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.gewuyou.forgeboot.trace.extension
|
package com.gewuyou.forgeboot.trace.impl.extension
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import org.springframework.http.HttpMethod
|
import org.springframework.http.HttpMethod
|
||||||
@ -1,11 +1,12 @@
|
|||||||
package com.gewuyou.forgeboot.trace.filter
|
package com.gewuyou.forgeboot.trace.impl.filter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import com.gewuyou.forgeboot.core.extension.log
|
import com.gewuyou.forgeboot.core.extension.log
|
||||||
import com.gewuyou.forgeboot.trace.config.entities.TraceProperties
|
import com.gewuyou.forgeboot.trace.impl.config.TraceProperties
|
||||||
import com.gewuyou.forgeboot.trace.extension.isSkipRequest
|
import com.gewuyou.forgeboot.trace.impl.extension.isSkipRequest
|
||||||
import com.gewuyou.forgeboot.trace.util.RequestIdUtil
|
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
|
||||||
|
|
||||||
import org.slf4j.MDC
|
import org.slf4j.MDC
|
||||||
import org.springframework.web.server.ServerWebExchange
|
import org.springframework.web.server.ServerWebExchange
|
||||||
import org.springframework.web.server.WebFilter
|
import org.springframework.web.server.WebFilter
|
||||||
@ -36,10 +37,10 @@ class ReactiveRequestIdFilter(
|
|||||||
val requestIdMdcKey = traceProperties.requestIdMdcKey
|
val requestIdMdcKey = traceProperties.requestIdMdcKey
|
||||||
// 尝试从请求头中获取 requestId,如果存在则设置到 RequestIdUtil 中,否则生成一个新的 requestId
|
// 尝试从请求头中获取 requestId,如果存在则设置到 RequestIdUtil 中,否则生成一个新的 requestId
|
||||||
request.headers[requestIdHeader]?.let {
|
request.headers[requestIdHeader]?.let {
|
||||||
it.firstOrNull()?.let(RequestIdUtil::setRequestId) ?: RequestIdUtil.generateRequestId()
|
it.firstOrNull()?.let(RequestIdUtil::requestId::set) ?: RequestIdUtil.generateRequestId()
|
||||||
} ?: RequestIdUtil.generateRequestId()
|
} ?: RequestIdUtil.generateRequestId()
|
||||||
// 获取当前的 requestId
|
// 获取当前的 requestId
|
||||||
val currentRequestId = RequestIdUtil.getRequestId()
|
val currentRequestId = RequestIdUtil.requestId
|
||||||
// 将 requestId 设置到日志中
|
// 将 requestId 设置到日志中
|
||||||
MDC.put(requestIdMdcKey, currentRequestId)
|
MDC.put(requestIdMdcKey, currentRequestId)
|
||||||
log.info("设置 Request id: $currentRequestId")
|
log.info("设置 Request id: $currentRequestId")
|
||||||
@ -53,7 +54,7 @@ class ReactiveRequestIdFilter(
|
|||||||
return chain.filter(mutatedExchange)
|
return chain.filter(mutatedExchange)
|
||||||
// ✅ 让 Reactor 线程也能获取 requestId
|
// ✅ 让 Reactor 线程也能获取 requestId
|
||||||
// 将 requestId 写入 Reactor 的上下文中,以便在异步处理中也能访问
|
// 将 requestId 写入 Reactor 的上下文中,以便在异步处理中也能访问
|
||||||
.contextWrite { ctx -> ctx.put(requestIdMdcKey, currentRequestId) }
|
.contextWrite { ctx -> ctx.put(requestIdMdcKey, currentRequestId!!) }
|
||||||
.doFinally {
|
.doFinally {
|
||||||
// 清理 MDC 中的 requestId,避免内存泄漏
|
// 清理 MDC 中的 requestId,避免内存泄漏
|
||||||
MDC.remove(requestIdMdcKey)
|
MDC.remove(requestIdMdcKey)
|
||||||
@ -1,10 +1,11 @@
|
|||||||
package com.gewuyou.forgeboot.trace.filter
|
package com.gewuyou.forgeboot.trace.impl.filter
|
||||||
|
|
||||||
|
|
||||||
import com.gewuyou.forgeboot.core.extension.log
|
import com.gewuyou.forgeboot.core.extension.log
|
||||||
import com.gewuyou.forgeboot.trace.config.entities.TraceProperties
|
import com.gewuyou.forgeboot.trace.impl.config.TraceProperties
|
||||||
import com.gewuyou.forgeboot.trace.extension.isSkipRequest
|
import com.gewuyou.forgeboot.trace.impl.extension.isSkipRequest
|
||||||
import com.gewuyou.forgeboot.trace.util.RequestIdUtil
|
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain
|
import jakarta.servlet.FilterChain
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import jakarta.servlet.http.HttpServletResponse
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
@ -39,13 +40,13 @@ class RequestIdFilter(
|
|||||||
try {
|
try {
|
||||||
// 尝试从请求头中获取 requestId
|
// 尝试从请求头中获取 requestId
|
||||||
request.getHeader(requestIdHeader)?.also(
|
request.getHeader(requestIdHeader)?.also(
|
||||||
RequestIdUtil::setRequestId
|
RequestIdUtil::requestId::set
|
||||||
) ?: run {
|
) ?: run {
|
||||||
// 如果没有,则生成新的 requestId
|
// 如果没有,则生成新的 requestId
|
||||||
RequestIdUtil.generateRequestId()
|
RequestIdUtil.generateRequestId()
|
||||||
}
|
}
|
||||||
// 获取 requestId
|
// 获取 requestId
|
||||||
val requestId = RequestIdUtil.getRequestId()
|
val requestId = RequestIdUtil.requestId
|
||||||
// 将requestId 设置到日志中
|
// 将requestId 设置到日志中
|
||||||
MDC.put(requestIdMdcKey, requestId)
|
MDC.put(requestIdMdcKey, requestId)
|
||||||
log.info("设置 Request id: $requestId")
|
log.info("设置 Request id: $requestId")
|
||||||
@ -1,9 +1,10 @@
|
|||||||
package com.gewuyou.forgeboot.trace.filter
|
package com.gewuyou.forgeboot.trace.impl.filter
|
||||||
|
|
||||||
import com.gewuyou.forgeboot.core.extension.log
|
import com.gewuyou.forgeboot.core.extension.log
|
||||||
import com.gewuyou.forgeboot.trace.config.entities.TraceProperties
|
import com.gewuyou.forgeboot.trace.impl.config.TraceProperties
|
||||||
import com.gewuyou.forgeboot.trace.extension.isSkipRequest
|
import com.gewuyou.forgeboot.trace.impl.extension.isSkipRequest
|
||||||
import com.gewuyou.forgeboot.trace.util.RequestIdUtil
|
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
|
||||||
|
|
||||||
import org.slf4j.MDC
|
import org.slf4j.MDC
|
||||||
import org.springframework.web.reactive.function.client.ClientRequest
|
import org.springframework.web.reactive.function.client.ClientRequest
|
||||||
import org.springframework.web.reactive.function.client.ClientResponse
|
import org.springframework.web.reactive.function.client.ClientResponse
|
||||||
@ -35,10 +36,10 @@ class WebClientRequestIdFilter(
|
|||||||
val requestIdMdcKey = traceProperties.requestIdMdcKey
|
val requestIdMdcKey = traceProperties.requestIdMdcKey
|
||||||
// 尝试从请求头中获取 requestId,如果存在则设置到 RequestIdUtil 中,否则生成一个新的 requestId
|
// 尝试从请求头中获取 requestId,如果存在则设置到 RequestIdUtil 中,否则生成一个新的 requestId
|
||||||
request.headers()[requestIdHeader]?.let {
|
request.headers()[requestIdHeader]?.let {
|
||||||
it.firstOrNull()?.let(RequestIdUtil::setRequestId) ?: RequestIdUtil.generateRequestId()
|
it.firstOrNull()?.let(RequestIdUtil::requestId::set) ?: RequestIdUtil.generateRequestId()
|
||||||
} ?: RequestIdUtil.generateRequestId()
|
} ?: RequestIdUtil.generateRequestId()
|
||||||
// 获取当前的 requestId
|
// 获取当前的 requestId
|
||||||
val currentRequestId = RequestIdUtil.getRequestId()
|
val currentRequestId = RequestIdUtil.requestId
|
||||||
// 将 requestId 设置到日志中
|
// 将 requestId 设置到日志中
|
||||||
MDC.put(requestIdMdcKey, currentRequestId)
|
MDC.put(requestIdMdcKey, currentRequestId)
|
||||||
log.info("设置 Request id: $currentRequestId")
|
log.info("设置 Request id: $currentRequestId")
|
||||||
@ -1,10 +1,10 @@
|
|||||||
package com.gewuyou.forgeboot.trace.interceptor
|
package com.gewuyou.forgeboot.trace.impl.interceptor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import com.gewuyou.forgeboot.core.extension.log
|
import com.gewuyou.forgeboot.core.extension.log
|
||||||
import com.gewuyou.forgeboot.trace.config.entities.TraceProperties
|
import com.gewuyou.forgeboot.trace.impl.config.TraceProperties
|
||||||
import com.gewuyou.forgeboot.trace.util.RequestIdUtil
|
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
|
||||||
import feign.RequestInterceptor
|
import feign.RequestInterceptor
|
||||||
import feign.RequestTemplate
|
import feign.RequestTemplate
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ class FeignRequestIdInterceptor(
|
|||||||
) : RequestInterceptor {
|
) : RequestInterceptor {
|
||||||
override fun apply(template: RequestTemplate) {
|
override fun apply(template: RequestTemplate) {
|
||||||
// 尝试获取当前请求的请求id
|
// 尝试获取当前请求的请求id
|
||||||
val requestId = RequestIdUtil.getRequestId()
|
val requestId = RequestIdUtil.requestId
|
||||||
requestId?.let {
|
requestId?.let {
|
||||||
// 如果请求id存在,则添加到请求头中
|
// 如果请求id存在,则添加到请求头中
|
||||||
template.header(traceProperties.requestIdHeaderName, requestId)
|
template.header(traceProperties.requestIdHeaderName, requestId)
|
||||||
@ -1,7 +1,8 @@
|
|||||||
package com.gewuyou.forgeboot.trace.provider
|
package com.gewuyou.forgeboot.trace.impl.provider
|
||||||
|
|
||||||
|
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
|
||||||
|
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
|
||||||
|
|
||||||
import com.gewuyou.forgeboot.common.result.api.RequestIdProvider
|
|
||||||
import com.gewuyou.forgeboot.trace.util.RequestIdUtil
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*跟踪请求ID提供商
|
*跟踪请求ID提供商
|
||||||
@ -18,6 +19,6 @@ class TraceRequestIdProvider: RequestIdProvider {
|
|||||||
* @return 请求ID的字符串表示
|
* @return 请求ID的字符串表示
|
||||||
*/
|
*/
|
||||||
override fun getRequestId(): String {
|
override fun getRequestId(): String {
|
||||||
return RequestIdUtil.getRequestId()
|
return RequestIdUtil.requestId?:throw RuntimeException("requestId is null")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
spring.application.name=impl
|
||||||
@ -1,54 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
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 +0,0 @@
|
|||||||
com.gewuyou.forgeboot.trace.config.TraceAutoConfiguration
|
|
||||||
3
forgeboot-webmvc/dto/.gitattributes
vendored
Normal file
3
forgeboot-webmvc/dto/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/gradlew text eol=lf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.jar binary
|
||||||
40
forgeboot-webmvc/dto/.gitignore
vendored
Normal file
40
forgeboot-webmvc/dto/.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Kotlin ###
|
||||||
|
.kotlin
|
||||||
18
forgeboot-webmvc/dto/build.gradle.kts
Normal file
18
forgeboot-webmvc/dto/build.gradle.kts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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}")
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
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,47 +1,107 @@
|
|||||||
package com.gewuyou.forgeboot.common.result
|
package com.gewuyou.forgeboot.webmvc.dto
|
||||||
|
|
||||||
import com.gewuyou.forgeboot.common.result.api.MessageResolver
|
|
||||||
import com.gewuyou.forgeboot.common.result.api.RequestIdProvider
|
import com.gewuyou.forgeboot.i18n.api.MessageResolver
|
||||||
import com.gewuyou.forgeboot.common.result.api.ResponseInformation
|
import com.gewuyou.forgeboot.i18n.api.I18nResponseInformation
|
||||||
import com.gewuyou.forgeboot.common.result.api.ResultExtender
|
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
|
||||||
import com.gewuyou.forgeboot.common.result.impl.DefaultMessageResolver
|
import com.gewuyou.forgeboot.webmvc.dto.i18n.I18nKeys
|
||||||
import com.gewuyou.forgeboot.common.result.impl.DefaultRequestIdProvider
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*默认请求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()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统一响应封装类
|
* 统一响应封装类
|
||||||
*
|
*
|
||||||
* @since 2025-05-03 16:04:42
|
* @since 2025-05-03 16:04:42
|
||||||
*/
|
*/
|
||||||
data class R<T>(
|
data class I18nResult<T>(
|
||||||
val code: Int,
|
override val code: Int,
|
||||||
val success: Boolean,
|
override val success: Boolean,
|
||||||
val message: String,
|
override val message: String,
|
||||||
val data: T? = null,
|
override val data: T? = null,
|
||||||
val requestId: String? = null,
|
override val requestId: String? = null,
|
||||||
val extra: Map<String, Any?> = emptyMap()
|
override val extra: Map<String, Any?> = emptyMap(),
|
||||||
) {
|
) : BaseResult<T>(code, success, message, data, requestId, extra) {
|
||||||
/**
|
|
||||||
* 转换为可变 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 {
|
companion object {
|
||||||
private fun buildExtraMap(extenders: List<ResultExtender>): Map<String, Any?> {
|
private fun buildExtraMap(extenders: List<ResultExtender>): Map<String, Any?> {
|
||||||
return mutableMapOf<String, Any?>().apply {
|
return mutableMapOf<String, Any?>().apply {
|
||||||
@ -55,23 +115,21 @@ data class R<T>(
|
|||||||
* @param info 响应信息对象
|
* @param info 响应信息对象
|
||||||
* @param data 响应数据
|
* @param data 响应数据
|
||||||
* @param messageResolver 消息解析器
|
* @param messageResolver 消息解析器
|
||||||
* @param i18bArgs 国际化参数
|
|
||||||
* @param requestIdProvider 请求ID提供者
|
* @param requestIdProvider 请求ID提供者
|
||||||
* @param extenders 扩展信息提供者列表
|
* @param extenders 扩展信息提供者列表
|
||||||
* @return 成功响应对象
|
* @return 成功响应对象
|
||||||
*/
|
*/
|
||||||
fun <T> success(
|
fun <T> success(
|
||||||
info: ResponseInformation,
|
info: I18nResponseInformation = defaultOkI18nResponseInformation,
|
||||||
data: T? = null,
|
data: T? = null,
|
||||||
messageResolver: MessageResolver? = null,
|
messageResolver: MessageResolver? = null,
|
||||||
i18bArgs: Array<Any>? = null,
|
|
||||||
requestIdProvider: RequestIdProvider? = null,
|
requestIdProvider: RequestIdProvider? = null,
|
||||||
extenders: List<ResultExtender> = emptyList()
|
extenders: List<ResultExtender> = emptyList(),
|
||||||
): R<T> {
|
): I18nResult<T> {
|
||||||
val msg = (messageResolver ?: DefaultMessageResolver).resolve(info.responseI8nMessageCode, i18bArgs)
|
val msg = (messageResolver ?: DefaultMessageResolver).resolve(info.responseI8nMessageCode, info.responseI8nMessageArgs)
|
||||||
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
||||||
val extra = buildExtraMap(extenders)
|
val extra = buildExtraMap(extenders)
|
||||||
return R(info.responseCode, true, msg, data, reqId, extra)
|
return I18nResult(info.responseCode, true, msg, data, reqId, extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,23 +138,21 @@ data class R<T>(
|
|||||||
* @param info 响应信息对象
|
* @param info 响应信息对象
|
||||||
* @param data 响应数据
|
* @param data 响应数据
|
||||||
* @param messageResolver 消息解析器
|
* @param messageResolver 消息解析器
|
||||||
* @param i18bArgs 国际化参数
|
|
||||||
* @param requestIdProvider 请求ID提供者
|
* @param requestIdProvider 请求ID提供者
|
||||||
* @param extenders 扩展信息提供者列表
|
* @param extenders 扩展信息提供者列表
|
||||||
* @return 失败响应对象
|
* @return 失败响应对象
|
||||||
*/
|
*/
|
||||||
fun <T> failure(
|
fun <T> failure(
|
||||||
info: ResponseInformation,
|
info: I18nResponseInformation = defaultFailureI18nResponseInformation,
|
||||||
data: T? = null,
|
data: T? = null,
|
||||||
messageResolver: MessageResolver? = null,
|
messageResolver: MessageResolver? = null,
|
||||||
i18bArgs: Array<Any>? = null,
|
|
||||||
requestIdProvider: RequestIdProvider? = null,
|
requestIdProvider: RequestIdProvider? = null,
|
||||||
extenders: List<ResultExtender> = emptyList()
|
extenders: List<ResultExtender> = emptyList(),
|
||||||
): R<T> {
|
): I18nResult<T> {
|
||||||
val msg = (messageResolver ?: DefaultMessageResolver).resolve(info.responseI8nMessageCode, i18bArgs)
|
val msg = (messageResolver ?: DefaultMessageResolver).resolve(info.responseI8nMessageCode, info.responseI8nMessageArgs)
|
||||||
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
||||||
val extra = buildExtraMap(extenders)
|
val extra = buildExtraMap(extenders)
|
||||||
return R(info.responseCode, false, msg, data, reqId, extra)
|
return I18nResult(info.responseCode, false, msg, data, reqId, extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -118,12 +174,12 @@ data class R<T>(
|
|||||||
i18nArgs: Array<Any>? = null,
|
i18nArgs: Array<Any>? = null,
|
||||||
messageResolver: MessageResolver? = null,
|
messageResolver: MessageResolver? = null,
|
||||||
requestIdProvider: RequestIdProvider? = null,
|
requestIdProvider: RequestIdProvider? = null,
|
||||||
extenders: List<ResultExtender> = emptyList()
|
extenders: List<ResultExtender> = emptyList(),
|
||||||
): R<T> {
|
): I18nResult<T> {
|
||||||
val msg = (messageResolver ?: DefaultMessageResolver).resolve(messageCode, i18nArgs)
|
val msg = (messageResolver ?: DefaultMessageResolver).resolve(messageCode, i18nArgs)
|
||||||
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
||||||
val extra = buildExtraMap(extenders)
|
val extra = buildExtraMap(extenders)
|
||||||
return R(code, true, msg, data, reqId, extra)
|
return I18nResult(code, true, msg, data, reqId, extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -145,12 +201,12 @@ data class R<T>(
|
|||||||
i18nArgs: Array<Any>? = null,
|
i18nArgs: Array<Any>? = null,
|
||||||
messageResolver: MessageResolver? = null,
|
messageResolver: MessageResolver? = null,
|
||||||
requestIdProvider: RequestIdProvider? = null,
|
requestIdProvider: RequestIdProvider? = null,
|
||||||
extenders: List<ResultExtender> = emptyList()
|
extenders: List<ResultExtender> = emptyList(),
|
||||||
): R<T> {
|
): I18nResult<T> {
|
||||||
val msg = (messageResolver ?: DefaultMessageResolver).resolve(messageCode, i18nArgs)
|
val msg = (messageResolver ?: DefaultMessageResolver).resolve(messageCode, i18nArgs)
|
||||||
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
val reqId = (requestIdProvider ?: DefaultRequestIdProvider).getRequestId()
|
||||||
val extra = buildExtraMap(extenders)
|
val extra = buildExtraMap(extenders)
|
||||||
return R(code, false, msg, data, reqId, extra)
|
return I18nResult(code, false, msg, data, reqId, extra)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,143 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.gewuyou.forgeboot.webmvc.dto
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应信息接口
|
||||||
|
*
|
||||||
|
* 该接口定义了响应信息的标准结构,包括响应状态码和响应消息
|
||||||
|
* 主要用于规范响应数据的格式,以确保响应数据的一致性和可解析性
|
||||||
|
*
|
||||||
|
* @since 2025-05-30 13:27:27
|
||||||
|
* @author gewuyou
|
||||||
|
*/
|
||||||
|
interface ResponseInformation {
|
||||||
|
/**
|
||||||
|
* 响应状态码,用于表示响应的状态
|
||||||
|
*/
|
||||||
|
fun responseStateCode(): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应消息,用于提供更详细的响应信息
|
||||||
|
*/
|
||||||
|
fun responseMessage(): String
|
||||||
|
}
|
||||||
@ -1,4 +1,6 @@
|
|||||||
package com.gewuyou.forgeboot.common.result.api
|
package com.gewuyou.forgeboot.webmvc.dto
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 结果扩展器
|
* 结果扩展器
|
||||||
*
|
*
|
||||||
@ -18,4 +20,4 @@ fun interface ResultExtender {
|
|||||||
* @param resultMap 一个包含结果数据的可变映射,可以在此方法中对其进行修改
|
* @param resultMap 一个包含结果数据的可变映射,可以在此方法中对其进行修改
|
||||||
*/
|
*/
|
||||||
fun extend(resultMap: MutableMap<String, Any?>)
|
fun extend(resultMap: MutableMap<String, Any?>)
|
||||||
}
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.gewuyou.forgeboot.webmvc.dto.enums
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序方向枚举
|
||||||
|
*
|
||||||
|
* @author gewuyou
|
||||||
|
* @since 2025-01-16 17:56:01
|
||||||
|
*/
|
||||||
|
enum class SortDirection {
|
||||||
|
ASC, DESC
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,123 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
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()
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
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>
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
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>,
|
||||||
|
)
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
forgeboot.webmvc.dto.result.responseInfo.fail=失败
|
||||||
|
forgeboot.webmvc.dto.result.responseInfo.ok=成功
|
||||||
|
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
forgeboot.webmvc.dto.result.responseInfo.fail=failed
|
||||||
|
forgeboot.webmvc.dto.result.responseInfo.ok=success
|
||||||
|
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
forgeboot.webmvc.dto.result.responseInfo.fail=失败
|
||||||
|
forgeboot.webmvc.dto.result.responseInfo.ok=成功
|
||||||
|
|
||||||
3
forgeboot-webmvc/exception-i18n/.gitattributes
vendored
Normal file
3
forgeboot-webmvc/exception-i18n/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/gradlew text eol=lf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.jar binary
|
||||||
40
forgeboot-webmvc/exception-i18n/.gitignore
vendored
Normal file
40
forgeboot-webmvc/exception-i18n/.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Kotlin ###
|
||||||
|
.kotlin
|
||||||
18
forgeboot-webmvc/exception-i18n/build.gradle.kts
Normal file
18
forgeboot-webmvc/exception-i18n/build.gradle.kts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
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