feat(plugin) 添加插件支持

This commit is contained in:
gewuyou 2025-07-24 08:11:34 +00:00
parent bf1983ef62
commit f3b571bf96
58 changed files with 1821 additions and 171 deletions

View File

@ -1,23 +1,51 @@
# forgeboot
This project uses [Gradle](https://gradle.org/).
To build and run the application, use the *Gradle* tool window by clicking the Gradle icon in the right-hand toolbar,
or run it directly from the terminal:
多模块 Spring Boot 工具与 Starter 集合
* Run `./gradlew run` to build and run the application.
* Run `./gradlew build` to only build the application.
* Run `./gradlew check` to run all checks, including tests.
* Run `./gradlew clean` to clean all build outputs.
## 项目概览
Note the usage of the Gradle Wrapper (`./gradlew`).
This is the suggested way to use Gradle in production projects.
`forgeboot` 是一套面向 Spring Boot 的通用基础库,包含 Banner、缓存、上下文传播、核心工具、国际化、
调用链追踪、Web MVC 公共组件以及示例模块。旨在帮助团队快速搭建企业级微服务应用。
[Learn more about the Gradle Wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html).
## 模块列表
[Learn more about Gradle tasks](https://docs.gradle.org/current/userguide/command_line_interface.html#common_tasks).
| 模块名 | 功能简介 |
|-------------------------------------------------|------------------------------|
| forgeboot-banner | 启动横幅 Banner 管理 |
| forgeboot-cache-spring-boot-starter | 缓存自动配置 |
| forgeboot-context-spring-boot-starter | 上下文传播 |
| forgeboot-core | 核心扩展与序列化 |
| forgeboot-i18n-spring-boot-starter | 国际化支持 |
| forgeboot-trace-spring-boot-starter | 分布式调用链追踪 |
| forgeboot-webmvc-spring-boot-starter | Web MVC 公共组件集合 |
| forgeboot-demo | 各模块功能示例 |
This project follows the suggested multi-module setup and consists of the `app` and `utils` subprojects.
The shared build logic was extracted to a convention plugin located in `buildSrc`.
## 快速开始
This project uses a version catalog (see `gradle/libs.versions.toml`) to declare and version dependencies
and both a build cache and a configuration cache (see `gradle.properties`).
1. 克隆仓库:
```bash
git clone https://github.com/GeWuYou/forgeboot.git
```
2. 构建所有模块:
```bash
./gradlew build
```
## 构建与发布
- **构建**
```bash
./gradlew clean build
```
- **发布到 Maven 仓库**
已配置 `maven-publish``axion-release`,只需执行:
```bash
./gradlew release
```
## 贡献指南
欢迎提交 Issue 和 PR共同完善各模块功能与文档。
## 许可
Apache-2.0

View File

@ -117,7 +117,19 @@ object Modules {
"${AUTHORIZE}:forgeboot-security-authorize-autoconfigure"
}
}
object Plugin {
private const val PLUGIN = ":forgeboot-plugin"
const val CORE = "${PLUGIN}:forgeboot-plugin-core"
const val SPRING = "${PLUGIN}:forgeboot-plugin-spring"
}
object Demo{
const val ROOT = ":forgeboot-demo"
private const val ROOT = ":forgeboot-demo"
object Plugin{
private const val PLUGIN = "${ROOT}:forgeboot-plugin-demo"
const val API = "${PLUGIN}:forgeboot-plugin-demo-api"
const val IMPL = "${PLUGIN}:forgeboot-plugin-demo-impl"
const val SERVER = "${PLUGIN}:forgeboot-plugin-demo-server"
}
}
}

View File

@ -0,0 +1,57 @@
# forgeboot-banner
Spring Boot Banner 启动横幅模块
## 简介
`forgeboot-banner` 提供了一个可插拔的启动横幅Banner框架与 Spring Boot 无缝集成,支持多种横幅渲染策略与配置项。你可以自定义文本、图片或多策略组合的 Banner在应用启动时显示。
## 核心功能
- 可通过 `forgeboot.banner.strategy` 配置多种渲染策略(随机、按环境、按时间等)。
- 支持文本和图片两种 Banner 类型。
- 提供默认实现,也可自定义 `BannerStrategy``BannerProvider`
- 自动集成 Spring Boot启动时自动渲染。
## 引入依赖
使用 Maven
```xml
<dependency>
<groupId>io.github.gewuyou</groupId>
<artifactId>forgeboot-banner</artifactId>
<version>${version}</version>
</dependency>
```
使用 Gradle
```groovy
implementation "io.github.gewuyou:forgeboot-banner:${version}"
```
## 子模块说明
- **forgeboot-banner-api**:核心接口与配置类(`BannerStrategy`, `BannerProvider`, `BannerProperties`)。
- **forgeboot-banner-impl**:默认实现,包括文件加载、日志输出等逻辑。
- **forgeboot-banner-launcher**:启动器模块,负责在 Spring Boot 启动阶段触发 Banner 渲染。
## 快速开始
1. 在 `application.yml` 中启用并配置:
```yaml
forgeboot:
banner:
enabled: true
strategy: RANDOM
text-location: classpath:/banner.txt
image-location: classpath:/banner.png
```
2. 启动应用,查看控制台或日志中的横幅效果。
## 构建
```bash
./gradlew :forgeboot-banner:build
```
## 许可
Apache-2.0

54
forgeboot-cache/README.md Normal file
View File

@ -0,0 +1,54 @@
# forgeboot-cache-spring-boot-starter
Spring Boot 缓存自动配置模块
## 简介
`forgeboot-cache-spring-boot-starter` 提供基于 Spring Cache 的一站式缓存解决方案,封装了常用的缓存接口、默认实现及自动配置。
## 核心功能
- **forgeboot-cache-api**:缓存接口定义,统一 `CacheManager``Cache` 操作。
- **forgeboot-cache-impl**:默认内存或 Redis 实现。
- **forgeboot-cache-autoconfigure**Spring Boot 自动配置,读取 `forgeboot.cache.*` 配置属性,自动创建 `CacheManager`
## 引入依赖
使用 Maven
```xml
<dependency>
<groupId>io.github.gewuyou</groupId>
<artifactId>forgeboot-cache-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
```
使用 Gradle
```groovy
implementation "io.github.gewuyou:forgeboot-cache-spring-boot-starter:${version}"
```
## 配置示例
```yaml
forgeboot:
cache:
type: redis
redis:
host: localhost
port: 6379
caffeine:
spec: maximumSize=1000,expireAfterAccess=5m
```
## 快速开始
1. 添加依赖并配置缓存类型。
2. 在 Service 方法上使用 `@Cacheable("cacheName")``@CacheEvict` 等注解即可生效。
## 构建
```bash
./gradlew :forgeboot-cache:build
```
## 许可
Apache-2.0

103
forgeboot-context/README.md Normal file
View File

@ -0,0 +1,103 @@
# forgeboot-context-spring-boot-starter
> 🧠 请求上下文感知模块支持在多线程、Reactor、协程等异步场景中透明传递上下文字段如 traceId、userId、token 等。
---
## 🧩 简介
`forgeboot-context-spring-boot-starter` 是 ForgeBoot 提供的基础上下文传播模块,旨在解决异步环境下上下文(如用户信息、请求信息)无法传递的问题。
支持多执行模型ThreadLocal、Reactor Context、Kotlin Coroutine Context无缝上下文共享并通过统一接口抽象、自动装配、可扩展的字段注入机制提升服务内的数据一致性与可维护性。
---
## ✨ 核心功能
- ✅ 支持上下文字段自动注入与解析Header、MDC、自定义
- ✅ 支持多执行模型(线程 / 协程 / Reactor上下文透明传递
- ✅ 提供统一 `ContextHolder` 访问入口
- ✅ 提供字段注册器与注入策略 SPI 扩展
- ✅ 支持 Spring WebServlet、WebFlux、Feign、RestTemplate 等组件
- ✅ 与 trace 模块无缝集成,自动传播 traceId/requestId
---
## 📦 引入依赖
使用 Maven
```xml
<dependency>
<groupId>io.github.gewuyou</groupId>
<artifactId>forgeboot-context-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
```
使用 Gradle
```groovy
implementation "io.github.gewuyou:forgeboot-context-spring-boot-starter:${version}"
```
---
## 🚀 快速开始
### ✅ 基础使用
```kotlin
@RestController
class DemoController(private val contextHolder: ContextHolder) {
@GetMapping("/demo")
fun demo(): String {
val traceId = contextHolder.get("traceId")
return "traceId: $traceId"
}
}
```
---
## 🔌 可扩展点
你可以通过实现以下接口来自定义字段注入行为:
- `ContextProcessor`:用于从请求中提取字段
- `ContextFieldContributor`:用于注册要注入的上下文字段
示例:
```kotlin
@Component
class CustomContextFieldContributor : ContextFieldContributor {
override fun contribute(registry: FieldRegistry) {
registry.register("tenantId", Scope.THREAD_LOCAL)
}
}
```
---
## ⚙️ 构建方式
使用 Gradle 命令进行构建发布:
```bash
./gradlew :forgeboot-context:build
```
---
## 📄 许可
本项目采用 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 开源协议。
---
## 📬 联系作者
由 [@gewuyou](https://github.com/gewuyou) 维护。
欢迎提交 Issue 或 PR 改进本模块!

40
forgeboot-core/README.md Normal file
View File

@ -0,0 +1,40 @@
# forgeboot-core
通用核心库
## 简介
`forgeboot-core` 包含项目中通用的扩展、工具与序列化支持,例如通用枚举处理、序列化接口、基础配置等。
## 核心功能
- **forgeboot-core-extension**:公共扩展函数、集合工具类等。
- **forgeboot-core-serialization**Jackson/Kotlin 序列化自定义注册与模块。
## 引入依赖
Maven
```xml
<dependency>
<groupId>io.github.gewuyou</groupId>
<artifactId>forgeboot-core</artifactId>
<version>${version}</version>
</dependency>
```
使用 Gradle
```groovy
implementation "io.github.gewuyou:forgeboot-core:${version}"
```
## 快速开始
在 Spring Boot 中引入后,自动注册序列化模块,可直接使用扩展函数。
## 构建
```bash
./gradlew :forgeboot-core:build
```
## 许可
Apache-2.0

28
forgeboot-demo/README.md Normal file
View File

@ -0,0 +1,28 @@
# forgeboot-demo
示例与演示模块
## 简介
`forgeboot-demo` 包含各模块功能的示例,帮助快速了解如何在项目中使用 `forgeboot-trace`, `forgeboot-cache` 等组件。
## 快速开始
1. 进入示例模块:
```bash
cd forgeboot-demo/forgeboot-trace-demo
```
2. 运行示例:
```bash
./gradlew bootRun
```
3. 访问 `http://localhost:8080/trace-demo` 查看效果。
## 构建
```bash
./gradlew :forgeboot-demo:build
```
## 许可
Apache-2.0

View File

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

View File

@ -0,0 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Kotlin ###
.kotlin

View File

@ -0,0 +1,5 @@
dependencies {
}

View File

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

View File

@ -0,0 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Kotlin ###
.kotlin

View File

@ -0,0 +1,7 @@
plugins {
alias(libs.plugins.kotlin.kapt)
}
dependencies {
kapt(libs.org.pf4j)
implementation(libs.org.pf4j)
}

View File

@ -0,0 +1,11 @@
package com.gewuyou.forgeboot.plugin.demo.api
/**
*问候提供者
*
* @since 2025-07-24 15:09:07
* @author gewuyou
*/
fun interface GreetingProvider {
fun message(): String
}

View File

@ -0,0 +1,13 @@
package com.gewuyou.forgeboot.plugin.demo.api
import org.pf4j.ExtensionPoint
/**
*问候服务
*
* @since 2025-07-23 14:04:12
* @author gewuyou
*/
fun interface GreetingService: ExtensionPoint {
fun greet(name: String): String
}

View File

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

View File

@ -0,0 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Kotlin ###
.kotlin

View File

@ -0,0 +1,11 @@
plugins {
alias(libs.plugins.kotlin.kapt)
alias(libs.plugins.kotlin.plugin.spring)
}
dependencies {
kapt(libs.org.pf4j)
implementation(project(Modules.Demo.Plugin.API))
implementation(project(Modules.Plugin.CORE))
implementation(project(Modules.Plugin.SPRING))
implementation(libs.springBoot.starter)
}

View File

@ -0,0 +1,21 @@
package com.gewuyou.forgeboot.plugin.demo.impl
import com.gewuyou.forgeboot.plugin.demo.api.GreetingProvider
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
*插件配置
*
* @since 2025-07-24 14:15:45
* @author gewuyou
*/
@Configuration
class PluginConfig{
@Bean
fun simpleGreetingService(
greetingProvider: GreetingProvider
): SimpleGreetingService {
return SimpleGreetingService(greetingProvider)
}
}

View File

@ -0,0 +1,28 @@
package com.gewuyou.forgeboot.plugin.demo.impl
import com.gewuyou.forgeboot.plugin.spring.manager.SpringPluginManager
import org.pf4j.PluginWrapper
import org.pf4j.spring.SpringPlugin
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
/**
* 简单的问候插件
*
* 插件主类必须继承 SpringPlugin 才能在 PF4J + Spring 框架中被识别
*
* @author gewuyou
* @since 2025-07-24 12:12:21
*/
class SimpleGreetingPlugin(
private val pluginWrapper: PluginWrapper,
) : SpringPlugin(pluginWrapper) {
override fun createApplicationContext(): ApplicationContext {
return AnnotationConfigApplicationContext().apply { ->
classLoader = pluginWrapper.pluginClassLoader
parent = (pluginWrapper.pluginManager as SpringPluginManager).applicationContext
register(PluginConfig::class.java)
refresh()
}
}
}

View File

@ -0,0 +1,18 @@
package com.gewuyou.forgeboot.plugin.demo.impl
import com.gewuyou.forgeboot.plugin.demo.api.GreetingProvider
import com.gewuyou.forgeboot.plugin.demo.api.GreetingService
import org.pf4j.Extension
/**
*简单的问候服务
*
* @since 2025-07-24 12:08:15
* @author gewuyou
*/
@Extension
class SimpleGreetingService (
private val greetingProvider: GreetingProvider
): GreetingService {
override fun greet(name: String) = "${greetingProvider.message()}, $name from plugin!"
}

View File

@ -0,0 +1,4 @@
pluginId: simple-greeting
version: 1.0.0
pluginClass: com.gewuyou.forgeboot.plugin.demo.impl.SimpleGreetingPlugin
pluginDescription: A greeting plugin

View File

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

View File

@ -0,0 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Kotlin ###
.kotlin

View File

@ -0,0 +1,6 @@
dependencies {
implementation(project(Modules.Demo.Plugin.API))
implementation(project(Modules.Plugin.CORE))
implementation(project(Modules.Plugin.SPRING))
implementation(libs.springBootStarter.web)
}

View File

@ -0,0 +1,11 @@
package com.gewuyou.forgeboot.plugin.demo.server
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class ForgebootPluginDemoServerApplication
fun main(args: Array<String>) {
runApplication<ForgebootPluginDemoServerApplication>(*args)
}

View File

@ -0,0 +1,22 @@
package com.gewuyou.forgeboot.plugin.demo.server.controller
import com.gewuyou.forgeboot.plugin.demo.api.GreetingService
import org.springframework.context.annotation.Lazy
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
/**
*演示控制器
*
* @since 2025-07-24 12:37:26
* @author gewuyou
*/
@RestController
class DemoController(
@Lazy
private val greetingService: GreetingService
) {
@GetMapping("/plugin/greet")
fun greet(@RequestParam("name") name: String) = greetingService.greet(name)
}

View File

@ -0,0 +1,15 @@
package com.gewuyou.forgeboot.plugin.demo.server.provider
import com.gewuyou.forgeboot.plugin.demo.api.GreetingProvider
import org.springframework.stereotype.Component
/**
*服务器问候提供商
*
* @since 2025-07-24 15:10:38
* @author gewuyou
*/
@Component
class ServerGreetingProvider: GreetingProvider {
override fun message(): String = "服务器问候"
}

View File

@ -0,0 +1,3 @@
forgeboot:
plugin:
path: .\forgeboot-demo\forgeboot-plugin-demo\forgeboot-plugin-demo-server\plugins

49
forgeboot-i18n/README.md Normal file
View File

@ -0,0 +1,49 @@
# forgeboot-i18n-spring-boot-starter
国际化 (i18n) 支持模块
## 简介
`forgeboot-i18n-spring-boot-starter` 提供消息资源加载、动态刷新、注解支持等国际化能力。
## 核心功能
- **forgeboot-i18n-api**:定义消息服务接口。
- **forgeboot-i18n-impl**:基于 Spring `MessageSource` 的默认实现,支持多语言资源。
- **forgeboot-i18n-autoconfigure**:自动配置 `MessageSource`、配置属性读取。
## 引入依赖
Maven
```xml
<dependency>
<groupId>io.github.gewuyou</groupId>
<artifactId>forgeboot-i18n-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
```
使用 Gradle
```groovy
implementation "io.github.gewuyou:forgeboot-i18n-spring-boot-starter:${version}"
```
## 快速开始
`application.yml` 中添加:
```yaml
forgeboot:
i18n:
basename: messages
default-locale: en_US
cache-seconds: 3600
```
通过 `@Autowired Messages messages; messages.get("key");` 获取消息。
## 构建
```bash
./gradlew :forgeboot-i18n:build
```
## 许可
Apache-2.0

3
forgeboot-plugin/.gitattributes vendored Normal file
View File

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

40
forgeboot-plugin/.gitignore vendored Normal file
View File

@ -0,0 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Kotlin ###
.kotlin

View File

@ -0,0 +1,4 @@
dependencies {
}

View File

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

View File

@ -0,0 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Kotlin ###
.kotlin

View File

@ -0,0 +1,9 @@
dependencies {
api(libs.org.yaml.snakeyaml)
api(libs.org.pf4j)
}
// 全局排除 slf4j-reload4j
configurations.all {
exclude(group = "org.slf4j", module = "slf4j-reload4j")
}

View File

@ -0,0 +1,85 @@
package com.gewuyou.forgeboot.plugin.core.finder
import com.gewuyou.forgeboot.plugin.core.mapper.YamlPluginDescriptorMapper
import org.pf4j.PluginDescriptor
import org.pf4j.PluginDescriptorFinder
import org.pf4j.PluginRuntimeException
import org.pf4j.util.FileUtils
import org.yaml.snakeyaml.LoaderOptions
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.constructor.Constructor
import java.io.IOException
import java.io.InputStreamReader
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
/**
* 映射YAML插件描述符查找器
*
* @since 2025-07-22 22:46:42
* @author gewuyou
*/
class MappableYamlPluginDescriptorFinder<T : PluginDescriptor, R>(
private val metadataClass: Class<R>,
private val descriptorMapper: YamlPluginDescriptorMapper<T, R>,
private val yamlFileName: String = "plugin.yml"
) : PluginDescriptorFinder {
/**
* 判断插件路径是否适用当前查找器
*
* @param pluginPath 插件路径
* @return 如果路径存在且为目录或 ZIP/JAR 文件则返回 true
*/
override fun isApplicable(pluginPath: Path): Boolean {
return Files.exists(pluginPath) && (Files.isDirectory(pluginPath) || FileUtils.isZipOrJarFile(pluginPath))
}
/**
* 查找并解析插件描述符
*
* @param pluginPath 插件路径
* @return 解析后的插件描述符
*/
override fun find(pluginPath: Path): PluginDescriptor {
val path = getYamlPath(pluginPath)
if (!Files.exists(path)) {
throw PluginRuntimeException("Cannot find descriptor file: $path")
}
val metadata = loadYamlFromPath(path)
return descriptorMapper.mapFrom(metadata)
}
/**
* 获取 YAML 文件的路径
*
* @param pluginPath 插件路径
* @return YAML 文件的路径
*/
private fun getYamlPath(pluginPath: Path): Path {
return if (Files.isDirectory(pluginPath)) {
pluginPath.resolve(Paths.get(yamlFileName))
} else {
try {
FileUtils.getPath(pluginPath, yamlFileName)
} catch (e: IOException) {
throw PluginRuntimeException(e)
}
}
}
/**
* 从指定路径加载 YAML 数据
*
* @param path YAML 文件路径
* @return 解析后的对象
*/
private fun loadYamlFromPath(path: Path): R {
val inputStream = Files.newInputStream(path)
val reader = InputStreamReader(inputStream, Charsets.UTF_8)
val yaml = Yaml(Constructor(metadataClass, LoaderOptions()))
return yaml.load<R>(reader)
?: throw PluginRuntimeException("Failed to load YAML from: $path")
}
}

View File

@ -0,0 +1,98 @@
package com.gewuyou.forgeboot.plugin.core.finder
import org.pf4j.PluginDescriptor
import org.pf4j.PluginDescriptorFinder
import org.pf4j.PluginRuntimeException
import org.pf4j.util.FileUtils
import org.yaml.snakeyaml.LoaderOptions
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.constructor.Constructor
import java.io.IOException
import java.io.InputStreamReader
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
/**
* 强类型泛型YAML插件描述符查找器
* 用于从指定路径中查找并解析YAML格式的插件描述符文件
*
* @param descriptorClass 插件描述符的类类型用于解析YAML文件时构造对应的对象
* @param yamlFileName YAML文件的名称默认为"plugin.yml"
* @since 2025-07-22 22:26:20
* @author gewuyou
*/
open class TypedYamlPluginDescriptorFinder<T : PluginDescriptor>(
protected val descriptorClass: Class<T>,
protected val yamlFileName: String = "plugin.yml",
) : PluginDescriptorFinder {
/**
* 检查给定的插件路径是否适用于此查找器
*
* @param pluginPath 插件的路径
* @return 如果路径存在且为目录或ZIP/JAR文件则返回true否则返回false
*/
override fun isApplicable(pluginPath: Path): Boolean {
return Files.exists(pluginPath) && (Files.isDirectory(pluginPath) || FileUtils.isZipOrJarFile(pluginPath))
}
/**
* 在给定的插件路径中查找并解析插件描述符
*
* @param pluginPath 插件的路径
* @return 解析后的PluginDescriptor对象
* @throws PluginRuntimeException 如果无法找到描述符文件则抛出此异常
*/
override fun find(pluginPath: Path): PluginDescriptor {
val path = getYamlPath(pluginPath)
if (!Files.exists(path)) {
throw PluginRuntimeException("Cannot find descriptor file: $path")
}
return parseYamlDescriptor(path)
}
/**
* 获取YAML描述符文件的路径
*
* @param pluginPath 插件的路径
* @return 描述符文件的路径
* @throws PluginRuntimeException 如果路径不存在则抛出此异常
*/
private fun getYamlPath(pluginPath: Path): Path {
if (Files.isDirectory(pluginPath)) {
return pluginPath.resolve(Paths.get(yamlFileName))
}
// 处理ZIP或JAR文件的情况获取其中的YAML文件路径
try {
return FileUtils.getPath(pluginPath, yamlFileName)
} catch (e: IOException) {
throw PluginRuntimeException(e)
}
}
/**
* 解析YAML格式的插件描述符
*
* @param path 插件描述符的路径
* @return 解析后的PluginDescriptor对象
*/
protected open fun parseYamlDescriptor(path: Path): PluginDescriptor {
return loadYamlFromPath(path)
}
/**
* 从指定路径加载YAML文件并解析为指定的类型
*
* @param path YAML文件的路径
* @return 解析后的对象类型为T
* @throws PluginRuntimeException 如果加载或解析YAML失败则抛出此异常
*/
protected fun loadYamlFromPath(path: Path): T {
val inputStream = Files.newInputStream(path)
val reader = InputStreamReader(inputStream, Charsets.UTF_8)
val yaml = Yaml(Constructor(descriptorClass, LoaderOptions()))
return yaml.load<T>(reader)
?: throw PluginRuntimeException("Failed to load YAML from: $path")
}
}

View File

@ -0,0 +1,77 @@
package com.gewuyou.forgeboot.plugin.core.finder
import org.pf4j.*
import org.pf4j.util.FileUtils
import org.yaml.snakeyaml.LoaderOptions
import org.yaml.snakeyaml.Yaml
import java.io.InputStreamReader
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
/**
*YAML插件描述符查找器
*
* @since 2025-07-24 13:24:18
* @author gewuyou
*/
class YamlPluginDescriptorFinder(
private val yamlFileName: String = "plugin.yml",
) : PluginDescriptorFinder {
class YamlDescriptor {
var pluginId: String? = null
var pluginDescription: String? = null
var pluginClass: String? = null
var version: String? = null
var provider: String? = null
var dependencies: String? = null
var requires: String = "*" // SemVer format
var license: String? = null
}
override fun isApplicable(pluginPath: Path): Boolean {
return Files.exists(pluginPath) && (Files.isDirectory(pluginPath) || FileUtils.isZipOrJarFile(pluginPath))
}
override fun find(pluginPath: Path): PluginDescriptor {
val descriptorPath = getYamlPath(pluginPath)
if (!Files.exists(descriptorPath)) {
throw PluginRuntimeException("YAML descriptor file not found: $descriptorPath")
}
val yaml = Yaml(LoaderOptions())
val reader = InputStreamReader(Files.newInputStream(descriptorPath), Charsets.UTF_8)
val yamlDescriptor = yaml.loadAs(reader, YamlDescriptor::class.java)
return DefaultPluginDescriptor(
yamlDescriptor.pluginId,
yamlDescriptor.pluginDescription,
yamlDescriptor.pluginClass,
yamlDescriptor.version,
yamlDescriptor.requires,
yamlDescriptor.provider,
yamlDescriptor.license
).apply {
yamlDescriptor.dependencies
?.split(",")
?.map { it.trim() }
?.filter { it.isNotEmpty() }
?.forEach { addDependency(PluginDependency(it)) }
}
}
private fun getYamlPath(pluginPath: Path): Path {
return if (Files.isDirectory(pluginPath)) {
pluginPath.resolve(Paths.get(yamlFileName))
} else {
try {
FileUtils.getPath(pluginPath, yamlFileName)
} catch (e: Exception) {
throw PluginRuntimeException(e)
}
}
}
}

View File

@ -0,0 +1,25 @@
package com.gewuyou.forgeboot.plugin.core.mapper
import org.pf4j.PluginDescriptor
/**
* YAML插件描述符映射器
*
* 该函数式接口用于将一种数据结构R类型映射为插件描述符对象T类型继承自PluginDescriptor
* 主要用于插件系统中对不同格式的描述信息进行统一转换
*
* @param T 插件描述符类型必须继承自 PluginDescriptor
* @param R 源数据类型用于提供映射所需的原始信息
*
* @since 2025-07-22 22:45:25
* @author gewuyou
*/
fun interface YamlPluginDescriptorMapper<T : PluginDescriptor, R> {
/**
* 将给定的源数据对象映射为插件描述符实例
*
* @param metadata 提供映射所需数据的源对象
* @return 返回映射生成的插件描述符对象T类型
*/
fun mapFrom(metadata: R): T
}

View File

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

View File

@ -0,0 +1,37 @@
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/

View File

@ -0,0 +1,11 @@
dependencies {
api(libs.org.yaml.snakeyaml)
api(libs.org.pf4jSpring)
compileOnly(libs.springBoot.starter)
implementation(project(Modules.Plugin.CORE))
implementation(project(Modules.Core.EXTENSION))
}
// 全局排除 slf4j-reload4j
configurations.all {
exclude(group = "org.slf4j", module = "slf4j-reload4j")
}

View File

@ -0,0 +1,48 @@
package com.gewuyou.forgeboot.plugin.spring
import org.pf4j.PluginWrapper
import org.pf4j.spring.SpringPlugin
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
/**
* 隔离的Spring插件
*
* 该抽象类继承自SpringPlugin提供了独立的Spring应用上下文
* 使得每个插件可以在自己的上下文中管理Bean实现插件间的隔离
*
* @property pluginWrapper 插件包装器提供插件的基本信息和类加载器
*
* @since 2025-07-24 15:26:45
* @author gewuyou
*/
abstract class IsolatedSpringPlugin(
private val pluginWrapper: PluginWrapper
) : SpringPlugin(pluginWrapper) {
/**
* 创建应用上下文
*
* 该方法覆盖了父类的实现创建一个独立的AnnotationConfigApplicationContext
* 设置插件的类加载器并注册插件特定的配置类
*
* @return 配置完成的ApplicationContext实例
*/
override fun createApplicationContext(): ApplicationContext {
return AnnotationConfigApplicationContext().apply {
classLoader = pluginWrapper.pluginClassLoader
register(pluginConfigurationClass())
refresh()
}
}
/**
* 获取插件配置类
*
* 抽象方法子类需要提供插件特定的配置类
* 该配置类将被注册到插件的应用上下文中
*
* @return 插件配置类的Class对象
*/
abstract fun pluginConfigurationClass(): Class<*>
}

View File

@ -0,0 +1,54 @@
package com.gewuyou.forgeboot.plugin.spring
import org.pf4j.PluginWrapper
import org.pf4j.spring.SpringPlugin
import org.pf4j.spring.SpringPluginManager
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.core.env.ConfigurableEnvironment
/**
* 合并的Spring插件
*
* 该抽象类继承自SpringPlugin与IsolatedSpringPlugin不同
* 它创建的插件应用上下文与主应用上下文建立父子关系
* 使得插件可以访问主应用上下文中的Bean实现插件与主应用的合并
*
* @property pluginWrapper 插件包装器提供插件的基本信息和类加载器
*
* @since 2025-07-24 15:35:52
* @author gewuyou
*/
abstract class MergedSpringPlugin(
private val pluginWrapper: PluginWrapper
) : SpringPlugin(pluginWrapper) {
/**
* 创建应用上下文
*
* 该方法覆盖了父类的实现创建一个与主应用上下文有关联的AnnotationConfigApplicationContext
* 设置插件的类加载器建立父子上下文关系并注册插件特定的配置类
*
* @return 配置完成的ApplicationContext实例
*/
override fun createApplicationContext(): ApplicationContext {
return AnnotationConfigApplicationContext().apply {
classLoader = pluginWrapper.pluginClassLoader
parent = (pluginWrapper.pluginManager as SpringPluginManager).applicationContext
// 保证配置文件能读取
environment = parent?.environment as ConfigurableEnvironment
register(pluginConfigurationClass())
refresh()
}
}
/**
* 获取插件配置类
*
* 抽象方法子类需要提供插件特定的配置类
* 该配置类将被注册到插件的应用上下文中
*
* @return 插件配置类的Class对象
*/
abstract fun pluginConfigurationClass(): Class<*>
}

View File

@ -0,0 +1,52 @@
package com.gewuyou.forgeboot.plugin.spring.config
import com.gewuyou.forgeboot.plugin.spring.manager.SpringPluginManager
import org.pf4j.DefaultPluginManager.PLUGINS_DIR_CONFIG_PROPERTY_NAME
import org.pf4j.ExtensionFactory
import org.pf4j.PluginManager
import org.pf4j.spring.SingletonSpringExtensionFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.nio.file.Paths
/**
* 插件Spring自动配置类
*
* 该类用于在Spring环境中自动配置插件管理器及相关功能
* 提供了插件管理器的Bean定义以及插件集成的初始化逻辑
*
* @since 2025-07-23 13:01:27
* @author gewuyou
*/
@Configuration
class PluginSpringAutoConfiguration() {
/**
* 提供一个默认的插件管理器Bean
*
* 当Spring容器中尚未定义PluginManager类型的Bean时
* 该方法将创建并配置一个SpringPluginManager实例作为默认插件管理器
* 插件路径通过@Value注解从配置文件中解析若未配置则使用默认值
*
* @param pluginPath 插件存放路径从配置文件中读取默认值为"plugins"
* @return 返回一个配置好的PluginManager实例用于管理插件的生命周期和功能
*/
@Bean
@ConditionalOnMissingBean
fun pluginManager(@Value("\${forgeboot.plugin.path}") pluginPath: String = PLUGINS_DIR_CONFIG_PROPERTY_NAME): PluginManager {
return SpringPluginManager(listOf(Paths.get(pluginPath)))
}
/**
* 创建并配置ExtensionFactory实例
* 该方法在插件管理器初始化之后调用用于创建扩展工厂进一步扩展系统功能
*
* @param pluginManager 已初始化并启动的插件管理器用于扩展工厂的构建
* @return ExtensionFactory 初始化后的扩展工厂实例
*/
@Bean
@ConditionalOnMissingBean
fun extensionFactory(pluginManager: PluginManager): ExtensionFactory {
return SingletonSpringExtensionFactory(pluginManager)
}
}

View File

@ -0,0 +1,25 @@
package com.gewuyou.forgeboot.plugin.spring.manager
import com.gewuyou.forgeboot.plugin.core.finder.YamlPluginDescriptorFinder
import org.pf4j.CompoundPluginDescriptorFinder
import org.pf4j.ManifestPluginDescriptorFinder
import org.pf4j.PropertiesPluginDescriptorFinder
import org.pf4j.spring.SpringPluginManager
import java.nio.file.Path
/**
*Spring插件管理器
*
* @since 2025-07-24 13:15:40
* @author gewuyou
*/
open class SpringPluginManager(
pluginsRoots: List<Path>,
) : SpringPluginManager(pluginsRoots) {
override fun createPluginDescriptorFinder(): CompoundPluginDescriptorFinder {
return CompoundPluginDescriptorFinder()
.add(YamlPluginDescriptorFinder())
.add(PropertiesPluginDescriptorFinder())
.add(ManifestPluginDescriptorFinder())
}
}

View File

@ -0,0 +1 @@
com.gewuyou.forgeboot.plugin.spring.config.PluginSpringAutoConfiguration

84
forgeboot-trace/README.md Normal file
View File

@ -0,0 +1,84 @@
# forgeboot-trace-spring-boot-starter
> 🔍 分布式调用链追踪模块,基于上下文感知系统实现 requestId / traceId 的自动生成、注入与传播适配协程、Reactor、线程池等多种执行模型。
---
## 🧩 简介
`forgeboot-trace-spring-boot-starter` 是 ForgeBoot 提供的轻量级调用链追踪模块,致力于在服务内部及分布式场景中提供统一的 traceId 管理机制。
该模块依赖 `forgeboot-context` 实现 traceId 的透明传递支持与日志系统MDC、Web 框架Servlet / WebFlux无缝集成。
---
## ✨ 核心功能
- ✅ 支持全局唯一的 traceId/requestId 自动生成与注入
- ✅ 与日志系统集成MDC 支持)
- ✅ 支持从 HTTP 请求头中自动提取 traceId
- ✅ 基于 `forgeboot-context` 实现跨线程 / 协程 / Reactor 任务 traceId 传递
- ✅ 可配置的 traceId 生成策略(内置 UUID支持扩展如 OpenTelemetry
- ✅ 适配 Spring Boot 自动装配,开箱即用
---
## 📦 引入依赖
使用 Maven
```xml
<dependency>
<groupId>io.github.gewuyou</groupId>
<artifactId>forgeboot-trace-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
```
使用 Gradle
```groovy
implementation "io.github.gewuyou:forgeboot-trace-spring-boot-starter:${version}"
```
---
## 🚀 快速开始
1. 引入依赖并启用自动配置Spring Boot 自动生效)
2. 在日志配置中添加 traceId以 logback 为例):
```xml
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level traceId=%X{traceId} %logger{36} - %msg%n</pattern>
```
3. 可在任何业务代码中通过上下文访问:
```kotlin
val traceId = ContextHolder.get("traceId")
```
---
## ⚙️ 构建方式
使用 Gradle 命令进行构建发布:
```bash
./gradlew :forgeboot-trace:build
```
---
## 📄 许可
本项目采用 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 开源协议。
---
## 📬 联系作者
由 [@gewuyou](https://github.com/gewuyou) 维护。
欢迎提交 Issue 或 PR 改进本模块!

View File

@ -0,0 +1,47 @@
# forgeboot-webmvc-spring-boot-starter
Web MVC 通用组件集合
## 简介
`forgeboot-webmvc-spring-boot-starter` 提供常用 Web 层组件如版本管理、统一日志、全局异常处理、DTO 封装、参数校验、接口规范等。
## 核心模块
- **forgeboot-webmvc-version-spring-boot-starter**API 版本控制
- **forgeboot-webmvc-logger-spring-boot-starter**:请求/响应日志 AOP
- **forgeboot-webmvc-exception-spring-boot-starter**:全局异常处理
- **forgeboot-webmvc-exception-i18n-spring-boot-starter**:异常国际化
- **forgeboot-webmvc-dto**:统一返回结构与分页工具
- **forgeboot-webmvc-validation**:请求校验封装
- **forgeboot-webmvc-spec**:自动生成接口规范文档
## 引入依赖
Maven
```xml
<dependency>
<groupId>io.github.gewuyou</groupId>
<artifactId>forgeboot-webmvc-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
```
使用 Gradle
```groovy
implementation "io.github.gewuyou:forgeboot-webmvc-spring-boot-starter:${version}"
```
## 快速开始
- 在 `application.yml` 中配置各模块开关和策略。
- 使用 `BaseResult<T>``PageResult<T>` 封装返回值。
- Controller 中使用 `@ApiVersion`, `@Validated` 等注解。
## 构建
```bash
./gradlew :forgeboot-webmvc:build
```
## 许可
Apache-2.0

View File

@ -0,0 +1,197 @@
# forgeboot-webmvc-dto
> 📦 Web 项目通用 DTO 模块,包含统一响应封装、分页/排序/过滤模型、JPA 查询构造扩展等内容。
---
## 🧩 简介
`forgeboot-webmvc-dto` 是 ForgeBoot 提供的 Web 项目 DTO 抽象模块,封装了常用的分页、排序、状态过滤等通用接口与请求模型,并提供统一的响应结构与查询构建工具,助力快速开发一致性良好的接口风格。
---
## ✨ 核心功能
- ✅ 标准化 API 响应结构:`BaseResult` / `PageResult` / `R`
- ✅ 通用分页/排序/过滤参数模型:`Pageable`, `Sortable`, `StatusFilterable`
- ✅ 查询构件组合接口:`QueryComponent` + 多种 Filterable 接口
- ✅ JPA 查询扩展支持:`JpaPredicateExtensions`, `PredicateExtensions`
- ✅ 响应信息国际化扩展:`I18nResult`
- ✅ 日期范围、关键字等实体查询条件模型
---
## 📦 引入依赖
使用 Maven
```xml
<dependency>
<groupId>io.github.gewuyou</groupId>
<artifactId>forgeboot-webmvc-dto</artifactId>
<version>${version}</version>
</dependency>
```
使用 Gradle
```groovy
implementation "io.github.gewuyou:forgeboot-webmvc-dto:${version}"
```
---
## 🚀 快速开始
### ✅ 统一响应使用示例
```kotlin
@GetMapping("/hello")
fun hello(): R<String> {
return R.ok("Hello, world!")
}
```
### ✅ 构建分页查询请求
```kotlin
data class UserPageRequest(
override val currentPage: Int,
override val pageSize: Int,
override val keyword: String?
) : Pageable, KeywordSearchable
```
结合 JPA Predicate 扩展:
```kotlin
fun buildPredicate(request: UserPageRequest): Predicate {
return where {
orLike("username", request.keyword)
andEqual("status", "ACTIVE")
}
}
```
---
## 📘 高级用法
### 🔄 自动封装分页响应
```kotlin
fun <T> toPageResult(page: Page<T>): PageResult<T> {
return PageResult.of(page)
}
```
控制器示例:
```kotlin
@GetMapping("/users")
fun listUsers(request: UserPageRequest): PageResult<UserDTO> {
val page = userService.query(request)
return PageResult.of(page)
}
```
### 🔍 自定义查询组件组合
你可以组合多种查询接口,让请求类直接实现 `QueryComponent`,构建强类型的 DSL 查询请求对象:
```kotlin
data class ProductQueryRequest(
override val currentPage: Int,
override val pageSize: Int,
override val keyword: String?,
override val status: Int?,
override val dateRange: DateRangeCondition?
) : QueryComponent, Pageable, KeywordSearchable, StatusFilterable, DateRangeFilterable
```
一旦实现了这些接口,你就可以直接在 Controller 中使用 `.toJpaQuery()` 方法,无需再手动构建查询逻辑:
```kotlin
@GetMapping("/products")
fun query(request: ProductQueryRequest): PageResult<ProductDTO> {
val (spec, pageRequest) = request.toJpaQuery<ProductEntity>()
val page = productRepository.findAll(spec, pageRequest)
return PageResult.of(page.map { it.toDTO() })
}
```
> ✅ 会自动根据你实现的接口生成分页、排序、关键字搜索、状态过滤、日期范围等查询条件,无需重复写繁琐的 JPA 条件拼接逻辑。
---
## 🔍 JPA 查询构造器扩展
你可以使用 `QueryComponent.toJpaQuery()` 一键将请求对象转换为 JPA 查询规范和分页条件。
### ✅ 示例请求类
```kotlin
data class ProductQueryRequest(
override val currentPage: Int,
override val pageSize: Int,
override val keyword: String?,
override val status: Int?,
override val dateRange: DateRangeCondition?
) : Pageable, Sortable, KeywordSearchable, StatusFilterable, DateRangeFilterable
```
### ✅ 控制器代码(极简)
```kotlin
@GetMapping("/products")
fun query(request: ProductQueryRequest): PageResult<ProductDTO> {
val (spec, pageRequest) = request.toJpaQuery<ProductEntity>()
val page = productRepository.findAll(spec, pageRequest)
return PageResult.of(page.map { it.toDTO() })
}
```
### ✅ 内部处理逻辑(你无需手写)
- 组合关键字模糊查询:`like lower(name)``like lower(description)`
- 处理状态过滤:`where status = ?`
- 构建日期范围:`createdAt >= ? and createdAt <= ?`
- 构建排序条件(多字段或默认排序)
- 构建分页信息(从 `currentPage` 开始,默认每页 10 条)
> 默认行为:如果请求未实现 `Pageable``Sortable`,会使用默认分页第 1 页、每页 10 条,无排序。
---
## 🧩 扩展组件介绍
| 模块 | 说明 |
|--------------------------|----------------------------|
| `BaseResult` | 基础响应模型(带状态码与信息) |
| `PageResult` | 分页响应结构 |
| `R` | 快捷响应构造器(等价于 Result.ok/err |
| `Pageable` | 分页能力接口 |
| `Sortable` | 排序参数接口 |
| `Filterable` | 通用过滤接口(可自定义字段过滤) |
| `QueryComponent` | 查询字段组合容器(支持 DSL 构造) |
| `DateRangeCondition` | 日期范围过滤器 |
| `JpaPredicateExtensions` | JPA 查询构建扩展函数集 |
---
## 📄 许可协议
本项目采用 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 开源协议。
---
## 📬 联系作者
由 [@gewuyou](https://github.com/gewuyou) 维护。
欢迎提交 Issue 或 PR 改进本模块!

View File

@ -9,7 +9,6 @@ dependencies {
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")

View File

@ -1,49 +0,0 @@
package com.gewuyou.forgeboot.webmvc.dto.mapper
import org.mapstruct.BeanMapping
import org.mapstruct.MappingTarget
import org.mapstruct.NullValuePropertyMappingStrategy
/**
* Base Mapper基础映射器
* 提供基础的对象映射操作定义包含合并覆盖合并单个对象拷贝及列表拷贝的方法
*
* @since 2025-05-30 22:50:18
* @author gewuyou
*/
interface BaseMapper<S, T> {
/**
* source 对象中的非 null 属性合并到 target 对象中
* 注意null 值的属性不会覆盖 target 中已有的值
*
* @param target 目标对象将被更新
* @param source 源对象提供需要合并的数据
*/
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
fun mergeIgnoreNull(@MappingTarget target: T, source: S)
/**
* 全量覆盖合并 source target
* 注意即使 source 中的字段为 null也会覆盖 target 中对应的字段
*
* @param target 目标对象将被更新
* @param source 源对象提供需要合并的数据
*/
fun overwriteMerge(@MappingTarget target: T, source: S)
/**
* source 对象的内容拷贝到一个新的 T 类型对象中
*
* @param source 源对象提供数据
* @return 返回一个新的目标类型对象
*/
fun copy(source: S): T
/**
* 将源对象列表中的每个元素拷贝为新的目标类型对象生成一个目标对象列表
*
* @param sources 源对象列表
* @return 返回目标类型对象的列表
*/
fun copyList(sources: List<S>): List<T>
}

View File

@ -1,63 +0,0 @@
package com.gewuyou.forgeboot.webmvc.dto.mapper
import org.mapstruct.BeanMapping
import org.mapstruct.MappingTarget
import org.mapstruct.NullValuePropertyMappingStrategy
/**
* 转换映射器接口
*
* 提供通用的实体类(Entity)与数据传输对象(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>
/**
* 使用非空属性对实体进行部分更新
*
* 注意此操作不会覆盖实体中为空的属性
*
* @param dto 需要用于更新的DTO对象
* @param entity 需要被更新的实体对象
*/
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
fun partialUpdate(dto: Dto, @MappingTarget entity: Entity)
}

View File

@ -0,0 +1,117 @@
# forgeboot-webmvc-spec
> 🧩 Web 项目通用 CRUD 接口规范模块,封装 Repository 与 Service 层的标准增删改查接口与默认实现,简化重复代码。
---
## 🧩 简介
`forgeboot-webmvc-spec` 是 ForgeBoot 提供的统一接口规范模块,旨在为常见的 Web 开发提供可复用的 Repository 和 Service 层增删改查基础接口及实现模板。
通过继承和组合该模块中的接口和基类,可以快速构建具有一致风格的业务组件,提升开发效率、降低重复代码、统一项目结构。
---
## ✨ 核心功能
- ✅ `CrudRepositorySpec<T, ID>`:通用 Repository 接口规范(继承自 `JpaRepository`
- ✅ `CrudServiceSpec<T, ID>`:业务层接口标准化定义
- ✅ `CrudServiceImplSpec<T, ID>`:默认 Service 实现(依赖 Repository
- ✅ 泛型支持 Entity 类型与主键类型的自动推导
- ✅ 与 DTO 模块 (`forgeboot-webmvc-dto`) 无缝协作
---
## 📦 引入依赖
使用 Maven
```xml
<dependency>
<groupId>io.github.gewuyou</groupId>
<artifactId>forgeboot-webmvc-spec</artifactId>
<version>${version}</version>
</dependency>
```
使用 Gradle
```groovy
implementation "io.github.gewuyou:forgeboot-webmvc-spec:${version}"
```
---
## 🚀 快速开始
### ✅ 定义实体类
```kotlin
@Entity
data class Product(
@Id val id: Long,
val name: String,
val status: Int
)
```
### ✅ 创建 Repository 接口
```kotlin
interface ProductRepository : CrudRepositorySpec<Product, Long>
```
### ✅ 创建 Service 接口
```kotlin
interface ProductService : CrudServiceSpec<Product, Long>
```
### ✅ 创建默认实现类
```kotlin
@Service
class ProductServiceImpl(
override val repository: ProductRepository
) : CrudServiceImplSpec<Product, Long>(), ProductService
```
现在你就拥有了以下功能:
- 新增:`save(entity)`
- 修改:`updateById(id, modifier)`
- 删除:`deleteById(id)`
- 查询单个:`findById(id)`
- 查询全部:`findAll()`
---
## 🧩 扩展方法(默认实现中提供)
```kotlin
fun updateById(id: ID, modifier: (T) -> Unit): Boolean
fun existsById(id: ID): Boolean
fun deleteById(id: ID): Boolean
```
可自由覆写或组合使用。
---
## 🧠 与其他模块集成建议
| 模块 | 集成方式说明 |
|------------------------|-------------------------------------------|
| `forgeboot-webmvc-dto` | 可配合 `PageResult`, `QueryComponent` 返回分页数据 |
---
## 📄 许可协议
本项目采用 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 开源协议。
---
## 📬 联系作者
由 [@gewuyou](https://github.com/gewuyou) 维护。
欢迎提交 Issue 或 PR 改进本模块!

View File

@ -17,6 +17,10 @@ slf4j-version = "2.0.17"
map-struct-version="1.6.3"
caffeine-version="3.2.1"
redisson-version="3.50.0"
org-pf4j-version = "3.13.0"
org-pf4j-spring-version = "0.10.0"
org-yaml-snakeyaml-version = "2.4"
org-yaml-snakeyaml-engine-version = "2.9"
[libraries]
kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin-version" }
kotlinxDatetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime-version" }
@ -41,7 +45,7 @@ springBootStarter-redis = { group = "org.springframework.boot", name = "spring-b
redisson-springBootStarter= { group = "org.redisson", name = "redisson-spring-boot-starter", version.ref = "redisson-version" }
springBoot-configuration-processor = { group = "org.springframework.boot", name = "spring-boot-configuration-processor", version.ref = "spring-boot-version" }
springBoot-autoconfigure = { group = "org.springframework.boot", name = "spring-boot-autoconfigure" }
springBoot-starter = { group = "org.springframework.boot", name = "spring-boot-starter" }
springExpression = { group = "org.springframework", name = "spring-expression", version.ref = "spring-framework-version" }
springCloudDependencies-bom = { module = "org.springframework.cloud:spring-cloud-dependencies", version.ref = "spring-cloud-version" }
@ -57,6 +61,10 @@ jackson-module-kotlin = { group = "com.fasterxml.jackson.module", name = "jackso
reactor-core = { group = "io.projectreactor", name = "reactor-core" }
#org
org-mapstruct = { group = "org.mapstruct", name = "mapstruct", version.ref = "map-struct-version" }
org-snakeyaml-snakeyamlEngine = { group = "org.snakeyaml", name = "snakeyaml-engine", version.ref = "org-yaml-snakeyaml-engine-version" }
org-yaml-snakeyaml = { group = "org.yaml", name = "snakeyaml", version.ref = "org-yaml-snakeyaml-version" }
org-pf4j = { group = "org.pf4j", name = "pf4j", version.ref = "org-pf4j-version" }
org-pf4jSpring = { group = "org.pf4j", name = "pf4j-spring", version.ref = "org-pf4j-spring-version" }
# Libraries can be bundled together for easier import
# jwt
jjwt-api = { module = "io.jsonwebtoken:jjwt-api", version.ref = "jjwt-version" }
@ -64,6 +72,8 @@ jjwt-impl = { module = "io.jsonwebtoken:jjwt-impl", version.ref = "jjwt-version"
jjwt-jackson = { module = "io.jsonwebtoken:jjwt-jackson", version.ref = "jjwt-version" }
# com
com-github-benManes-caffeine = { module = "com.github.ben-manes.caffeine:caffeine",version.ref = "caffeine-version" }
# io
[bundles]
kotlinxEcosystem = ["kotlinxDatetime", "kotlinxSerialization", "kotlinxCoroutines-core"]
jacksonAll = [

View File

@ -136,50 +136,25 @@ project(":forgeboot-trace:forgeboot-trace-impl").name = "forgeboot-trace-impl"
project(":forgeboot-trace:forgeboot-trace-autoconfigure").name = "forgeboot-trace-autoconfigure"
//endregion
////region module demo
//region module trace
/**
* Includes and configures projects related to 'forgeboot-trace'
* This module handles distributed tracing functionality.
*/
include(
"forgeboot-demo",
":forgeboot-demo:forgeboot-trace-demo"
":forgeboot-demo:forgeboot-trace-demo",
":forgeboot-demo:forgeboot-plugin-demo",
":forgeboot-demo:forgeboot-plugin-demo:forgeboot-plugin-demo-api",
":forgeboot-demo:forgeboot-plugin-demo:forgeboot-plugin-demo-impl",
":forgeboot-demo:forgeboot-plugin-demo:forgeboot-plugin-demo-server",
)
//endregion
include(
"forgeboot-plugin",
":forgeboot-plugin:forgeboot-plugin-core",
":forgeboot-plugin:forgeboot-plugin-spring",
)
////region module security
///**
// * Includes and configures projects related to 'forgeboot-security'
// * This module handles security-related functionality.
// */
//include(
// "forgeboot-security",
// ":forgeboot-security:forgeboot-security-core",
//
// ":forgeboot-security:forgeboot-security-authenticate",
// ":forgeboot-security:forgeboot-security-authenticate:api",
// ":forgeboot-security:forgeboot-security-authenticate:impl",
// ":forgeboot-security:forgeboot-security-authenticate:autoconfigure",
//
// ":forgeboot-security:forgeboot-security-authorize",
// ":forgeboot-security:forgeboot-security-authorize:api",
// ":forgeboot-security:forgeboot-security-authorize:impl",
// ":forgeboot-security:forgeboot-security-authorize:autoconfigure"
//)
//project(":forgeboot-security").name = "forgeboot-security-spring-boot-starter"
//project(":forgeboot-security:forgeboot-security-core").name = "forgeboot-security-core"
//
//project(":forgeboot-security:forgeboot-security-authenticate").name =
// "forgeboot-security-authenticate-spring-boot-starter"
//project(":forgeboot-security:forgeboot-security-authenticate:api").name = "forgeboot-security-authenticate-api"
//project(":forgeboot-security:forgeboot-security-authenticate:impl").name = "forgeboot-security-authenticate-impl"
//project(":forgeboot-security:forgeboot-security-authenticate:autoconfigure").name =
// "forgeboot-security-authenticate-autoconfigure"
//
//project(":forgeboot-security:forgeboot-security-authorize").name = "forgeboot-security-authorize-spring-boot-starter"
//project(":forgeboot-security:forgeboot-security-authorize:api").name = "forgeboot-security-authorize-api"
//project(":forgeboot-security:forgeboot-security-authorize:impl").name = "forgeboot-security-authorize-impl"
//project(":forgeboot-security:forgeboot-security-authorize:autoconfigure").name =
// "forgeboot-security-authorize-autoconfigure"
//
////endregion
//region module cache
include(