From c22389d584743379c9a12491067d9f87e3157843 Mon Sep 17 00:00:00 2001 From: gewuyou <1063891901@qq.com> Date: Sat, 26 Apr 2025 14:04:44 +0800 Subject: [PATCH] feat(webmvc): Add version control support - Added ApiVersion annotations for version control of controllers and methods - Implement VersionAutoConfiguration automatic configuration class - Add ApiVersionRequestMappingHandlerMapping to handle version mapping - Update build configuration, support Spring Boot and version control - Added Git-related configuration files --- build.gradle.kts | 162 ++++++++++++------ buildSrc/src/main/kotlin/Modules.kt | 1 + buildSrc/src/main/kotlin/ProjectFlags.kt | 2 + config/publishing.gradle.kts | 0 ...ourceTask.gradle => sourceTask.gradle.kts} | 14 +- forgeboot-webmvc/.gitattributes | 3 + forgeboot-webmvc/.gitignore | 40 +++++ forgeboot-webmvc/build.gradle.kts | 8 + .../.gitattributes | 3 + .../.gitignore | 40 +++++ .../build.gradle.kts | 8 + .../webmvc/version/annotation/ApiVersion.kt | 16 ++ .../config/VersionAutoConfiguration.kt | 28 +++ .../ApiVersionRequestMappingHandlerMapping.kt | 79 +++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + gradle/libs.versions.toml | 2 + settings.gradle.kts | 5 +- 17 files changed, 347 insertions(+), 65 deletions(-) create mode 100644 config/publishing.gradle.kts rename config/tasks/{sourceTask.gradle => sourceTask.gradle.kts} (58%) create mode 100644 forgeboot-webmvc/.gitattributes create mode 100644 forgeboot-webmvc/.gitignore create mode 100644 forgeboot-webmvc/build.gradle.kts create mode 100644 forgeboot-webmvc/forgeboot-webmvc-version-starter/.gitattributes create mode 100644 forgeboot-webmvc/forgeboot-webmvc-version-starter/.gitignore create mode 100644 forgeboot-webmvc/forgeboot-webmvc-version-starter/build.gradle.kts create mode 100644 forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/kotlin/com/gewuyou/forgeboot/webmvc/version/annotation/ApiVersion.kt create mode 100644 forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/kotlin/com/gewuyou/forgeboot/webmvc/version/config/VersionAutoConfiguration.kt create mode 100644 forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/kotlin/com/gewuyou/forgeboot/webmvc/version/mapping/ApiVersionRequestMappingHandlerMapping.kt create mode 100644 forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/build.gradle.kts b/build.gradle.kts index 745350c..95b2077 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,38 +8,38 @@ plugins { // Kotlin Spring 支持 alias(libs.plugins.kotlin.plugin.spring) } -//scmVersion { -// tag { -// prefix.set("") // 不加 v,生成 1.0.1 而不是 v1.0.1 -// } -// versionIncrementer("incrementMinorIfNotOnRelease") -// hooks { -// pre("fileUpdate", mapOf( -// "file" to "README.md", -// "pattern" to "version: (.*)", -// "replacement" to "version: \$version" -// )) -// pre("commit") -// post("push") -// } -// -//} -//version = scmVersion.version +scmVersion { + tag { + prefix.set("") // 不加 v,生成 1.0.1 而不是 v1.0.1 + } + versionIncrementer("incrementMinorIfNotOnRelease") + hooks { + pre( + "fileUpdate", mapOf( + "file" to "README.md", + "pattern" to "version: (.*)", + "replacement" to "version: \$version" + ) + ) + pre("commit") + post("push") + } +} +version = scmVersion.version val configDir = "$rootDir/config/" val tasksDir = "$configDir/tasks/" -//apply { -// from(file("$tasksDir/gradleTask.gradle")) -// allprojects { // 设置全局属性 ext { set(ProjectFlags.IS_ROOT_MODULE, false) + set(ProjectFlags.USE_SPRING_BOOT_BOM, false) + set(ProjectFlags.USE_SPRING_BOOT_WEB, false) } afterEvaluate { - if(project.getPropertyByBoolean(ProjectFlags.IS_ROOT_MODULE)){ + if (project.getPropertyByBoolean(ProjectFlags.IS_ROOT_MODULE)) { /** * 由于 Kotlin 插件引入时会自动添加依赖,但根项目不需要 Kotlin 依赖,因此需要排除 Kotlin 依赖 */ @@ -48,58 +48,106 @@ allprojects { } } } + project.group = "com.gewuyou.forgeboot" } subprojects { -// afterEvaluate { -// if (project.getPropertyByBoolean(ProjectFlags.USE_GRPC)) { -// dependencies{ -// implementation(platform(libs.grpc.bom)) -// // gRPC Stub -// implementation(libs.grpc.stub) -// } -// } -// if (project.getPropertyByBoolean(ProjectFlags.USE_SPRING_BOOT)){ -// dependencies{ -// implementation(platform(libs.springBootDependencies.bom)) -// compileOnly(libs.springBootStarter.web) -// } -// } -// } + version = rootProject.version + afterEvaluate { + if(project.getPropertyByBoolean(ProjectFlags.USE_SPRING_BOOT_BOM)){ + dependencies{ + implementation(platform(libs.springBootDependencies.bom)) + } + } + if (project.getPropertyByBoolean(ProjectFlags.USE_SPRING_BOOT_WEB)){ + dependencies{ + compileOnly(libs.springBootStarter.web) + } + } + } val libs = rootProject.libs apply { plugin(libs.plugins.java.get().pluginId) plugin(libs.plugins.maven.publish.get().pluginId) plugin(libs.plugins.kotlin.jvm.get().pluginId) + plugin(libs.plugins.axionRelease.get().pluginId) // plugin(libs.plugins.spring.dependency.management.get().pluginId) // 导入仓库配置 from(file("$configDir/repositories.gradle.kts")) // 导入源代码任务 - from(file("$tasksDir/sourceTask.gradle")) + from(file("$tasksDir/sourceTask.gradle.kts")) // // 导入发布配置 -// from(file("$configDir/publishing.gradle")) +// from(file("$configDir/publishing.gradle.kts")) } - dependencies { + publishing { + repositories { + maven { + name = "localRepo" + url = uri("file://D:/Maven/mvn_repository") + } + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/GeWuYou/forgeboot") + credentials { + username = System.getenv("GITHUB_USERNAME") + password = System.getenv("GITHUB_TOKEN") + } + } + } + publications { + create("mavenJava") { + val projectName = project.name + from(components["java"]) + groupId = project.group.toString() + artifactId = projectName + version = project.version.toString() - } - configure { - toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) + pom { + name.set(projectName) + description.set("Part of Forgeboot Starters") + url.set("https://github.com/GeWuYou/forgeboot") + + licenses { + license { + name.set("Apache-2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0") + } + } + developers { + developer { + id.set("gewuyou") + name.set("gewuyou") + email.set("gewuyou1024@gmail.com") + } + } + scm { + connection.set("scm:git:git://github.com/GeWuYou/forgeboot.git") + developerConnection.set("scm:git:ssh://github.com/GeWuYou/forgeboot.git") + url.set("https://github.com/GeWuYou/forgeboot") + } + } + } + } + + + dependencies { + + } + configure { + toolchain { + languageVersion.set(JavaLanguageVersion.of(21)) + } + } + tasks.withType { + isEnabled = true + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } + tasks.named("test") { + useJUnitPlatform() } } -// kotlin { -// compilerOptions { -// freeCompilerArgs.addAll("-Xjsr305=strict") -// } -// } - tasks.withType { - isEnabled = true - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - } - tasks.named("test") { - useJUnitPlatform() - } -} + +} fun Project.getPropertyByBoolean(key: String): Boolean { return properties[key]?.toString()?.toBoolean() ?: false -} +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Modules.kt b/buildSrc/src/main/kotlin/Modules.kt index 413249f..1b5edc3 100644 --- a/buildSrc/src/main/kotlin/Modules.kt +++ b/buildSrc/src/main/kotlin/Modules.kt @@ -8,6 +8,7 @@ object Modules { object Webmvc { + const val STARTER = ":forgeboot-webmvc" const val VERSION_STARTER = ":forgeboot-webmvc:forgeboot-webmvc-version-starter" const val LOGGER_STARTER = ":forgeboot-webmvc:forgeboot-webmvc-logger-starter" } diff --git a/buildSrc/src/main/kotlin/ProjectFlags.kt b/buildSrc/src/main/kotlin/ProjectFlags.kt index 4e12102..8cdba14 100644 --- a/buildSrc/src/main/kotlin/ProjectFlags.kt +++ b/buildSrc/src/main/kotlin/ProjectFlags.kt @@ -1,3 +1,5 @@ object ProjectFlags { const val IS_ROOT_MODULE = "isRootModule" + const val USE_SPRING_BOOT_BOM = "useSpringBootBom" + const val USE_SPRING_BOOT_WEB = "useSpringBootWeb" } \ No newline at end of file diff --git a/config/publishing.gradle.kts b/config/publishing.gradle.kts new file mode 100644 index 0000000..e69de29 diff --git a/config/tasks/sourceTask.gradle b/config/tasks/sourceTask.gradle.kts similarity index 58% rename from config/tasks/sourceTask.gradle rename to config/tasks/sourceTask.gradle.kts index 5637861..9b41435 100644 --- a/config/tasks/sourceTask.gradle +++ b/config/tasks/sourceTask.gradle.kts @@ -1,17 +1,19 @@ // This task creates a jar file with the source code of the project -tasks.register("sourceTask", Jar, { Jar jar -> + +tasks.register("sourceTask") { logger.info("正在配置${project.name}源代码 jar 文件...") - // 单独添加 Kotlin 源代码目录 - from(sourceSets.main.kotlin.srcDirs) { - into("kotlin") // 将 Kotlin 源代码放入子目录 kotlin + // 收集所有源代码(包括 Kotlin 和 Java) + val sourceSet = project.extensions.getByType()["main"] + from(sourceSet.allSource) { + into("sources") // 将所有源代码放入子目录 sources } archiveClassifier.set("sources") // 设置生成文件的分类标识 logger.info("正在创建${project.name}源代码 jar 文件...") logger.info("创建${project.name}源代码 jar 文件完成!") -}).configure { - group = "build" +}.configure { + group = "source" description = "使用项目的源代码创建源代码 jar 文件" } \ No newline at end of file diff --git a/forgeboot-webmvc/.gitattributes b/forgeboot-webmvc/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/forgeboot-webmvc/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/forgeboot-webmvc/.gitignore b/forgeboot-webmvc/.gitignore new file mode 100644 index 0000000..5a979af --- /dev/null +++ b/forgeboot-webmvc/.gitignore @@ -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 diff --git a/forgeboot-webmvc/build.gradle.kts b/forgeboot-webmvc/build.gradle.kts new file mode 100644 index 0000000..a51e0e0 --- /dev/null +++ b/forgeboot-webmvc/build.gradle.kts @@ -0,0 +1,8 @@ +extra { + // 标记为根项目 + setProperty(ProjectFlags.IS_ROOT_MODULE, true) +} + +dependencies { + +} diff --git a/forgeboot-webmvc/forgeboot-webmvc-version-starter/.gitattributes b/forgeboot-webmvc/forgeboot-webmvc-version-starter/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/forgeboot-webmvc/forgeboot-webmvc-version-starter/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/forgeboot-webmvc/forgeboot-webmvc-version-starter/.gitignore b/forgeboot-webmvc/forgeboot-webmvc-version-starter/.gitignore new file mode 100644 index 0000000..5a979af --- /dev/null +++ b/forgeboot-webmvc/forgeboot-webmvc-version-starter/.gitignore @@ -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 diff --git a/forgeboot-webmvc/forgeboot-webmvc-version-starter/build.gradle.kts b/forgeboot-webmvc/forgeboot-webmvc-version-starter/build.gradle.kts new file mode 100644 index 0000000..db66158 --- /dev/null +++ b/forgeboot-webmvc/forgeboot-webmvc-version-starter/build.gradle.kts @@ -0,0 +1,8 @@ +extra { + // 标记为根项目 + setProperty(ProjectFlags.USE_SPRING_BOOT_BOM, true) + setProperty(ProjectFlags.USE_SPRING_BOOT_WEB, true) +} +dependencies { + +} diff --git a/forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/kotlin/com/gewuyou/forgeboot/webmvc/version/annotation/ApiVersion.kt b/forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/kotlin/com/gewuyou/forgeboot/webmvc/version/annotation/ApiVersion.kt new file mode 100644 index 0000000..cde5f8e --- /dev/null +++ b/forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/kotlin/com/gewuyou/forgeboot/webmvc/version/annotation/ApiVersion.kt @@ -0,0 +1,16 @@ +package com.gewuyou.forgeboot.webmvc.version.annotation + +/** + *API 版本注解 + * 可用于类或方法上,用于标识 API 的版本号。方法上的注解优先级高于类上的注解。 + * @since 2025-02-04 20:23:34 + * @author gewuyou + */ +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class ApiVersion ( + /** + * API 版本号值 (如 v1, v2, v3 等)默认值为 v1 + */ + vararg val value: String = ["v1"] +) \ No newline at end of file diff --git a/forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/kotlin/com/gewuyou/forgeboot/webmvc/version/config/VersionAutoConfiguration.kt b/forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/kotlin/com/gewuyou/forgeboot/webmvc/version/config/VersionAutoConfiguration.kt new file mode 100644 index 0000000..3ccf12b --- /dev/null +++ b/forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/kotlin/com/gewuyou/forgeboot/webmvc/version/config/VersionAutoConfiguration.kt @@ -0,0 +1,28 @@ +package com.gewuyou.forgeboot.webmvc.version.config + +import com.gewuyou.forgeboot.webmvc.version.mapping.ApiVersionRequestMappingHandlerMapping +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +/** + *版本自动配置 + * + * @since 2025-04-26 13:09:58 + * @author gewuyou + */ +@Configuration +open class VersionAutoConfiguration { + /** + * 创建并配置一个 ApiVersionRequestMappingHandlerMapping 实例 + * + * 该方法通过注解 @Bean 标记,表明此方法会返回一个由 Spring 管理的 Bean 实例 + * ApiVersionRequestMappingHandlerMapping 是一个自定义的 HandlerMapping, + * 它支持基于 API 版本的请求映射处理,这对于需要版本控制的 RESTful API 设计非常有用 + * + * @return ApiVersionRequestMappingHandlerMapping 实例,用于处理基于 API 版本的请求映射 + */ + @Bean + open fun apiVersionRequestMappingHandlerMapping(): ApiVersionRequestMappingHandlerMapping { + return ApiVersionRequestMappingHandlerMapping() + } +} \ No newline at end of file diff --git a/forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/kotlin/com/gewuyou/forgeboot/webmvc/version/mapping/ApiVersionRequestMappingHandlerMapping.kt b/forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/kotlin/com/gewuyou/forgeboot/webmvc/version/mapping/ApiVersionRequestMappingHandlerMapping.kt new file mode 100644 index 0000000..043b2c0 --- /dev/null +++ b/forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/kotlin/com/gewuyou/forgeboot/webmvc/version/mapping/ApiVersionRequestMappingHandlerMapping.kt @@ -0,0 +1,79 @@ +package com.gewuyou.forgeboot.webmvc.version.mapping + + +import com.gewuyou.forgeboot.webmvc.version.annotation.ApiVersion +import org.springframework.core.annotation.AnnotatedElementUtils +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.servlet.mvc.method.RequestMappingInfo +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping +import java.lang.reflect.Method + +/** + *API 版本请求映射处理程序映射 + * + * @since 2025-02-04 20:30:44 + * @author gewuyou + */ +class ApiVersionRequestMappingHandlerMapping : RequestMappingHandlerMapping() { + /** + * 判断是否处理特定类型的Bean + * 仅处理标注了 @RestController 注解的类 + * @param beanType 要判断的Bean类型 + * @return 如果类型标注了 @RestController,则返回true,否则返回false + */ + override fun isHandler(beanType: Class<*>): Boolean { + // 仅处理标注了 @RestController 注解的类 + return AnnotatedElementUtils.hasAnnotation(beanType, RestController::class.java) + } + + /** + * 获取方法的映射信息 + * 首先尝试从方法上获取 @ApiVersion 注解,如果不存在,则尝试从类上获取 + * 如果存在 @ApiVersion 注解,则会根据注解中的版本信息来组合新地映射路径 + * @param method 方法对象 + * @param handlerType 处理器类型 + * @return 可能包含版本信息的 RequestMappingInfo,如果不存在 @ApiVersion 注解,则返回原始的映射信息 + */ + override fun getMappingForMethod(method: Method, handlerType: Class<*>): RequestMappingInfo? { + // 获取原始的 @RequestMapping 信息 + val requestMappingInfo = super.getMappingForMethod(method, handlerType) ?: return null + // 优先获取方法上的注解 + val methodApiVersion = AnnotatedElementUtils.findMergedAnnotation(method, ApiVersion::class.java) + // 当方法注解存在时构建并返回 RequestMappingInfo + methodApiVersion?.let { + return combineVersionMappings(requestMappingInfo, it.value) + } + // 获取类上的 @ApiVersion 注解 + val classApiVersion = AnnotatedElementUtils.findMergedAnnotation(handlerType, ApiVersion::class.java) + classApiVersion?.let { + return combineVersionMappings(requestMappingInfo, it.value) + } + // 当类注解不存在时返回原始的 RequestMappingInfo + return requestMappingInfo + } + + /** + * 组合版本路径,支持多个版本 + * @param originalMapping 原始的 RequestMappingInfo + * @param versions 版本数组 + * @return 组合后的 RequestMappingInfo + */ + private fun combineVersionMappings( + originalMapping: RequestMappingInfo, + versions: Array + ): RequestMappingInfo { + return versions + .map { + // 加上版本前缀 + RequestMappingInfo + .paths("/api/$it") + .build() + }.reduce { + // 组合 + acc, mapping -> + acc.combine(mapping) + } + // 组合原始 RequestMapping + .combine(originalMapping) + } +} \ No newline at end of file diff --git a/forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..b36dde4 --- /dev/null +++ b/forgeboot-webmvc/forgeboot-webmvc-version-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.gewuyou.forgeboot.webmvc.version.config.VersionAutoConfiguration \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 999cd47..2347aba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,6 +15,8 @@ kotlinxDatetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.r kotlinxSerialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJSON-version" } kotlinxCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines-version" } +springBootStarter-web = { group = "org.springframework.boot", name = "spring-boot-starter-web" } +springBootDependencies-bom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "spring-boot-version" } # Libraries can be bundled together for easier import [bundles] diff --git a/settings.gradle.kts b/settings.gradle.kts index 638893b..a135d3b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,5 +27,6 @@ include( ":forgeboot-webmvc:forgeboot-webmvc-version-starter", ":forgeboot-webmvc:forgeboot-webmvc-logger-starter" ) -project(":forgeboot-webmvc:forgeboot-webmvc-version-starter").name = "forgeboot-webmvc-version-starter" -project(":forgeboot-webmvc:forgeboot-webmvc-logger-starter").name = "forgeboot-webmvc-logger-starter" \ No newline at end of file +project(":forgeboot-webmvc").name = "forgeboot-webmvc-spring-boot-starter" +project(":forgeboot-webmvc:forgeboot-webmvc-version-starter").name = "forgeboot-webmvc-version-spring-boot-starter" +project(":forgeboot-webmvc:forgeboot-webmvc-logger-starter").name = "forgeboot-webmvc-logger-spring-boot-starter" \ No newline at end of file