Compare commits

...

3 Commits

Author SHA1 Message Date
9c11b7e2e5 feat(security): 添加安全认证模块
- 新增安全认证相关的多个子模块,包括核心模块、认证模块和授权模块
- 实现了用户认证、权限控制、登录请求解析等功能
- 添加了安全认证的自动配置和相关Bean定义
- 定义了多种认证策略和处理机制,支持灵活的认证流程定制
2025-06-24 17:25:10 +08:00
1ef75d7c17 feat(cache): 新增缓存模块基础结构和功能
- 添加 CacheableEx、CacheEvictEx、CachePutEx 注解用于缓存操作
- 实现 Cache 接口定义缓存基本操作
- 添加 CacheProperties 配置类- 实现 CacheLayer、CacheException 等辅助类
- 添加缓存策略、序列化、管理器等配置类
2025-06-24 17:22:43 +08:00
a6706fd3c4 feat(context): 添加默认值序列化器和上下文持有者
- 新增 JacksonValueSerializer Bean,作为默认的值序列化器
- 新增 ContextHolder Bean,作为默认的上下文持有者
-两个 Bean 都在缺少其他实现时条件性创建
2025-06-24 17:05:05 +08:00
176 changed files with 6902 additions and 13 deletions

View File

@ -11,6 +11,7 @@ plugins {
// Kotlin kapt 支持
alias(libs.plugins.kotlin.kapt)
id(libs.plugins.kotlin.jvm.get().pluginId)
alias(libs.plugins.gradleMavenPublishPlugin)
}
java {
withSourcesJar()
@ -58,7 +59,7 @@ allprojects {
}
}
}
project.group = "com.gewuyou.forgeboot"
project.group = "io.github.gewuyou"
}
// 子项目配置
@ -104,9 +105,15 @@ subprojects {
plugin(libs.plugins.kotlin.jvm.get().pluginId)
plugin(libs.plugins.axionRelease.get().pluginId)
plugin(libs.plugins.kotlin.kapt.get().pluginId)
plugin(libs.plugins.gradleMavenPublishPlugin.get().pluginId)
// 导入仓库配置
from(file("$configDir/repositories.gradle.kts"))
}
// 中央仓库
mavenPublishing {
publishToMavenCentral("DEFAULT", true)
signAllPublications()
}
// 发布配置
publishing {
repositories {
@ -128,9 +135,8 @@ subprojects {
val host = System.getenv("GITEA_HOST")
host?.let {
maven {
isAllowInsecureProtocol = true
name = "Gitea"
url = uri("http://${it}/api/packages/gewuyou/maven")
url = uri("${it}/api/packages/gewuyou/maven")
credentials(HttpHeaderCredentials::class.java) {
name = "Authorization"
value = "token ${System.getenv("GITEA_TOKEN")}"
@ -204,9 +210,88 @@ subprojects {
tasks.named<Test>("test") {
useJUnitPlatform()
}
}
/**
* 注册一个 Gradle 任务用于清理项目中的无用文件。
*
* 该任务会清理以下内容:
* - gradlew 和 gradlew.batGradle 包装器脚本)
* - gradle-wrapper.jar 和 gradle-wrapper.propertiesGradle 包装器库和配置)
* - .gradle 和 gradle 目录Gradle 缓存和配置目录)
* - src/main/resources/application.properties开发环境配置文件
* - src/test 目录(测试资源)
*/
tasks.register<CleanUselessFilesTask>("cleanUselessFiles") {
group = "project"
description = "删除根目录及子模块的无用文件"
includedProjectPaths.set(rootProject.subprojects.map { it.projectDir.absolutePath })
}
// 获取项目布尔属性的辅助函数
/**
* 获取项目的布尔属性值。
*
* 用于从项目属性中获取指定键对应的布尔值,如果未设置或无法解析为布尔值,则返回默认值 false。
*
* @param key 属性键名
* @return 属性对应的布尔值,若不存在或无效则返回 false
*/
fun Project.getPropertyByBoolean(key: String): Boolean {
return properties[key]?.toString()?.toBoolean() ?: false
}
/**
* CleanUselessFilesTask 是一个 Gradle 自定义任务类,用于清理项目中的无用文件。
*
* 该任务会遍历所有子项目,并删除预定义的无用文件和目录。
*/
abstract class CleanUselessFilesTask : DefaultTask() {
/**
* 获取需要清理的项目路径列表。
*
* 每个路径代表一个项目的根目录,任务将基于这些目录查找并删除无用文件。
*/
@get:Input
abstract val includedProjectPaths: ListProperty<String>
/**
* 执行清理操作的主要方法。
*
* 此方法会构建待删除文件列表,并逐个检查是否存在并删除,最后输出删除结果。
*/
@TaskAction
fun clean() {
val targets = mutableListOf<File>()
// 构建需要清理的文件和目录列表
includedProjectPaths.get().forEach { path ->
val projectDir = File(path)
targets.add(projectDir.resolve("gradlew"))
targets.add(projectDir.resolve("gradlew.bat"))
targets.add(projectDir.resolve("gradle/wrapper/gradle-wrapper.jar"))
targets.add(projectDir.resolve("gradle/wrapper/gradle-wrapper.properties"))
targets.add(projectDir.resolve(".gradle"))
targets.add(projectDir.resolve("gradle"))
targets.add(projectDir.resolve("src/main/resources/application.properties"))
targets.add(projectDir.resolve("src/test"))
targets.add(projectDir.resolve("HELP.md"))
targets.add(projectDir.resolve("settings.gradle.kts"))
}
// 执行删除操作并统计结果
var count = 0
targets.filter { it.exists() }.forEach {
logger.lifecycle("删除:${it.absolutePath}")
count++
it.deleteRecursively()
}
if (count > 0) {
logger.lifecycle("已删除 $count 个无用文件。")
} else {
logger.lifecycle("无无用文件。")
}
}
}

View File

@ -7,39 +7,114 @@
*/
object Modules {
object Context{
/**
* Context模块Spring Boot Starter上下文支持模块
* 提供基础的上下文功能包含API实现及自动配置模块
*/
object Context {
const val STARTER = ":forgeboot-context-spring-boot-starter"
const val API = ":forgeboot-context-spring-boot-starter:forgeboot-context-api"
const val IMPL = ":forgeboot-context-spring-boot-starter:forgeboot-context-impl"
const val AUTOCONFIGURE = ":forgeboot-context-spring-boot-starter:forgeboot-context-autoconfigure"
}
/**
* Webmvc模块Spring Boot Starter对WebMVC的支持模块
* 提供Web开发相关的功能包括数据传输对象DTO验证版本控制及日志功能
*/
object Webmvc {
const val STARTER = ":forgeboot-webmvc-spring-boot-starter"
const val DTO = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-dto"
const val EXCEPTION = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-exception-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{
/**
* Core模块项目的基础核心模块
* 包含项目的通用核心功能以及扩展支持
*/
object Core {
const val ROOT = ":forgeboot-core"
const val EXTENSION = ":forgeboot-core:forgeboot-core-extension"
const val SERIALIZATION = ":forgeboot-core:forgeboot-core-serialization"
}
/**
* Cache模块缓存支持模块
* 提供缓存功能包含API实现及自动配置模块
*/
object Cache {
const val STARTER = ":forgeboot-cache-spring-boot-starter"
const val API = ":forgeboot-cache-spring-boot-starter:forgeboot-cache-api"
const val IMPL = ":forgeboot-cache-spring-boot-starter:forgeboot-cache-impl"
const val AUTOCONFIGURE = ":forgeboot-cache-spring-boot-starter:forgeboot-cache-autoconfigure"
}
/**
* I18n模块国际化支持模块
* 提供多语言支持功能包含API实现及自动配置模块
*/
object I18n {
const val STARTER = ":forgeboot-i18n-spring-boot-starter"
const val API = ":forgeboot-i18n-spring-boot-starter:forgeboot-i18n-api"
const val IMPL = ":forgeboot-i18n-spring-boot-starter:forgeboot-i18n-impl"
const val AUTOCONFIGURE = ":forgeboot-i18n-spring-boot-starter:forgeboot-i18n-autoconfigure"
}
object TRACE{
/**
* TRACE模块分布式链路追踪支持模块
* 提供请求链路追踪能力包含API实现及自动配置模块
*/
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"
}
/**
* Banner模块启动横幅定制模块
* 负责自定义应用启动时显示的横幅信息
*/
object Banner {
const val STARTER = ":forgeboot-banner"
const val API = ":forgeboot-banner:forgeboot-banner-api"
const val IMPL = ":forgeboot-banner:forgeboot-banner-impl"
const val AUTOCONFIGURE = ":forgeboot-banner:forgeboot-banner-autoconfigure"
}
/**
* Security模块安全认证与授权模块集合
* 包含认证(Authenticate)和授权(Authorize)两个子模块
*/
object Security {
private const val SECURITY = ":forgeboot-security-spring-boot-starter"
private const val AUTHENTICATE = "${SECURITY}:forgeboot-security-authenticate-spring-boot-starter"
private const val AUTHORIZE = "${SECURITY}:forgeboot-security-authorize-spring-boot-starter"
const val CORE = "${SECURITY}:forgeboot-security-core"
/**
* Authenticate模块身份认证支持模块
* 提供用户身份认证相关功能包含API实现及自动配置模块
*/
object Authenticate {
const val STARTER = AUTHENTICATE
const val API = "${AUTHENTICATE}:forgeboot-security-authenticate-api"
const val IMPL = "${AUTHENTICATE}:forgeboot-security-authenticate-impl"
const val AUTOCONFIGURE =
"${AUTHENTICATE}:forgeboot-security-authenticate"
}
/**
* Authorize模块权限控制支持模块
* 提供基于角色或策略的权限控制功能包含API实现及自动配置模块
*/
object Authorize {
const val STARTER = AUTHORIZE
const val API = "${AUTHORIZE}:forgeboot-security-authorize-api"
const val IMPL = "${AUTHORIZE}:forgeboot-security-authorize-impl"
const val AUTOCONFIGURE =
"${AUTHORIZE}:forgeboot-security-authorize-autoconfigure"
}
}
}

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

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

39
forgeboot-cache/.gitignore vendored Normal file
View File

@ -0,0 +1,39 @@
.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,8 @@
extra {
// 标记为根项目
setProperty(ProjectFlags.IS_ROOT_MODULE, true)
}
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,5 @@
dependencies {
compileOnly(libs.springBoot.autoconfigure)
kapt(libs.springBoot.configuration.processor)
}

View File

@ -0,0 +1,20 @@
package com.gewuyou.forgeboot.cache.api.annotations
/**
* 缓存移除扩展注解用于标识需要清除缓存的方法
*
* 该注解应用于方法上表示在方法执行前后可以触发缓存清除操作
* 支持通过命名空间和SpEL表达式定义缓存键并指定缓存层级
*
* @property namespace 缓存的命名空间用于对缓存进行逻辑分组默认为空字符串
* @property keySpEL 缓存键的SpEL表达式用于动态生成缓存键默认为空字符串
*
* @since 2025-06-16 22:29:52
* @author gewuyou
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class CacheEvictEx(
val namespace: String,
val keySpEL: String
)

View File

@ -0,0 +1,21 @@
package com.gewuyou.forgeboot.cache.api.annotations
/**
* 缓存 put ex
*
* 用于声明式缓存更新操作的注解适用于方法级别
*
* @property namespace 命名空间用于缓存键的逻辑分组默认为空字符串
* @property keySpEL 缓存键的 SpEL 表达式默认为空字符串
* @property ttl 缓存键生存时间
*
* @since 2025-06-16 22:33:35
* @author gewuyou
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class CachePutEx(
val namespace: String,
val keySpEL: String,
val ttl: Long = 300L
)

View File

@ -0,0 +1,29 @@
package com.gewuyou.forgeboot.cache.api.annotations
import kotlin.reflect.KClass
/**
* 缓存注解扩展用于标记可缓存的方法
*
* 该注解应用于方法级别表示该方法的结果可以被缓存通过指定不同的参数
* 可以控制缓存的命名空间键生成表达式过期时间是否缓存空值以及缓存层级
*
* @property namespace 缓存的命名空间用于区分不同业务场景下的缓存数据默认为空字符串
* @property keySpEL 缓存键的 SpEL 表达式用于动态生成缓存键默认为空字符串
* @property ttl 缓存过期时间单位为秒默认值为 300 10 分钟
* @property cacheNull 是否缓存空值默认为 false表示不缓存空结果
* @property type 指定缓存值的类型用于确保类型安全默认使用 KClass<*> 表示任意类型
*
* @since 2025-06-16
* @author gewuyou
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class CacheableEx(
val namespace: String,
val keySpEL: String,
val ttl: Long = 300L,
val cacheNull: Boolean = false,
@JvmSuppressWildcards val type: KClass<*> = Any::class
)

View File

@ -0,0 +1,31 @@
package com.gewuyou.forgeboot.cache.api.config
import org.springframework.boot.context.properties.ConfigurationProperties
import java.time.Duration
/**
* 缓存属性配置类
*
* 该类用于定义缓存相关的配置属性通过@ConfigurationProperties绑定配置前缀"forgeboot.cache"
* 支持配置默认缓存过期时间TTL
*
* @since 2025-06-17 10:04:48
* @author gewuyou
*/
@ConfigurationProperties("forgeboot.cache")
class CacheProperties {
/**
* 默认缓存过期时间Time To Live
*
* 用于指定缓存项在未被访问后的最大存活时间
* 默认值为15分钟
*/
var theDefaultCacheTTL: Duration = Duration.ofMinutes(15)
/**
* 缓存项为null时使用的占位符
*
* 用于在缓存项为null时返回的占位符
* 默认值为"__NULL__"
*/
var nullValuePlaceholder: String = "__NULL__"
}

View File

@ -0,0 +1,69 @@
package com.gewuyou.forgeboot.cache.api.contract
import java.time.Duration
/**
* 缓存接口定义
*
* 提供统一的缓存操作方法包括获取存储删除和判断键是否存在
*
* @since 2025-06-16 22:39:16
* @author gewuyou
*/
interface Cache {
/**
* 获取指定键对应的缓存值
*
* @param key 缓存键
* @return 缓存值如果不存在则返回 null
*/
fun retrieve(key: String): String?
/**
* 将键值对存储到缓存中并设置过期时间
*
* @param key 缓存键
* @param value 缓存值可以为 null
* @param ttl 缓存存活时间Time To Live
*/
fun put(key: String, value: String?, ttl: Duration)
/**
* 删除指定键的缓存
*
* @param key 要删除的缓存键
* @return 如果删除成功返回 true否则返回 false
*/
fun remove(key: String): Boolean
/**
* 判断指定键是否存在于缓存中
*
* @param key 缓存键
* @return 如果存在返回 true否则返回 false
*/
fun exists(key: String): Boolean
/**
* 清空缓存中的所有数据
* @param namespace 命名空间
*/
fun clear(namespace: String)
/**
* 通过操作符重载实现缓存的获取操作
*
* @param key 缓存键
* @return 缓存值如果不存在则返回 null
*/
fun Cache.get(key: String): String? = this.retrieve(key)
/**
* 通过操作符重载实现缓存的存储操作
*
* @param key 缓存键
* @param value 缓存值可以为 null
* @param ttl 缓存存活时间Time To Live
*/
operator fun Cache.set(key: String, value: String?, ttl: Duration) = this.put(key, value, ttl)
}

View File

@ -0,0 +1,17 @@
package com.gewuyou.forgeboot.cache.api.entities
import com.gewuyou.forgeboot.cache.api.contract.Cache
/**
* 缓存层实体类用于定义缓存层级结构
*
* @property cache 关联的缓存实例用于实际执行缓存操作
* @property priority 缓存优先级用于决定多个缓存实现中的调用顺序
*
* @since 2025-06-16 22:43:15
* @author gewuyou
*/
data class CacheLayer(
val cache: Cache,
val priority: Int
)

View File

@ -0,0 +1,17 @@
package com.gewuyou.forgeboot.cache.api.exception
/**
* 缓存异常类用于封装与缓存操作相关的异常信息
*
* @param message 异常的详细描述信息默认为 null
* @param cause 导致此异常的底层异常默认为 null
*
* @since 2025-06-17 21:14:40
* @author gewuyou
*/
class CacheException(
message: String? = null,
cause: Throwable? = null,
) : RuntimeException(
message, cause
)

View File

@ -0,0 +1,32 @@
package com.gewuyou.forgeboot.cache.api.extension
import com.gewuyou.forgeboot.cache.api.contract.Cache
import java.time.Duration
/**
* 重载 set 操作符用于通过 [] 语法将指定键值对及存活时间存入缓存
*
* 此方法封装了缓存的存储操作并支持指定缓存项的存活时间TTL
*
* @param key 要存储的缓存键类型为 [String]
* @param value 要存储的缓存值类型为 [String?]允许为 null
* @param ttl 存活时间类型为 [Duration]表示缓存项的有效期
*/
operator fun Cache.set(key: String, value: String?, ttl: Duration) {
this.put(key, value, ttl)
}
/**
* 获取指定键对应的缓存值
*
* 从最高优先级的缓存层开始查找一旦找到有效值则将其回填到
* 所有优先级高于当前层的缓存中并返回该值
*
* 此方法封装了缓存的读取逻辑并负责维护缓存层级间的数据一致性
*
* @param key 要获取的缓存键类型为 [String]
* @return 如果存在缓存值则返回对应值类型为 [String?]否则返回 null
*/
operator fun Cache.get(key: String): String? {
return this.retrieve(key)
}

View File

@ -0,0 +1,77 @@
package com.gewuyou.forgeboot.cache.api.extension
import com.gewuyou.forgeboot.cache.api.loader.CacheLoader
import com.gewuyou.forgeboot.cache.api.service.CacheService
import java.time.Duration
/**
* 操作符扩展函数用于通过 set(key, value, ttl) 存储带有过期时间的泛型值
*
* 该函数扩展了 CacheService 接口允许使用操作符语法设置缓存键值对并指定生存时间
* 实际调用 put 方法完成缓存写入操作
*
* @param key 缓存键用于唯一标识存储的数据
* @param value 支持任意对象类型将被序列化后存储
* @param ttl 缓存生存时间指定该值后缓存将在设定的时间后自动失效
*/
operator fun CacheService.set(key: String, value: Any, ttl: Duration) {
this.put(key, value, ttl)
}
/**
* 获取指定键的缓存值
*
* @param key 缓存键
* @param type 值的类型 Class
* @return 缓存中的对象如果不存在则为 null
*/
operator fun <T : Any> CacheService.get(key: String, type: Class<T>): T? {
return this.retrieve(key, type)
}
/**
* 通过泛型方式获取指定键的缓存值
*
* @param key 缓存键
* @return 缓存中的对象如果不存在则为 null
*/
inline fun <reified T : Any> CacheService.get(key: String): T? {
return this.retrieve(key, T::class.java)
}
/**
* 获取指定键的缓存值若不存在则使用给定的加载器进行加载并将结果存入缓存
*
* @param key 缓存键
* @param type 值的类型 Class
* @param ttl 缓存生存时间指定该值后缓存将在设定的时间后自动失效
* @param loader 用于加载数据的 CacheLoader 实例
* @return 缓存中的对象如果不存在且 loader 也无法加载则为 null
*/
fun <T : Any> CacheService.getOrLoad(
key: String,
type: Class<T>,
ttl: Duration,
loader: CacheLoader<T> = CacheLoader { _, _ -> null }
): T? {
return this.retrieve(key, type) ?: loader.load(key, type)?.also {
this.put(key, it, ttl)
}
}
/**
* 通过泛型方式获取指定键的缓存值若不存在则使用给定的加载函数进行加载并将结果存入缓存
*
* @param key 缓存键
* @param ttl 缓存生存时间指定该值后缓存将在设定的时间后自动失效
* @param loader 一个函数接收键并返回加载的数据 null
* @return 缓存中的对象如果不存在且 loader 也无法加载则为 null
*/
inline fun <reified T : Any> CacheService.getOrLoad(
key: String,
ttl: Duration,
loader: CacheLoader<T> = CacheLoader { _, _ -> null }
): T? {
return retrieve(key, T::class.java) ?: loader.load(key, T::class.java)?.also {
put(key, it, ttl)
}
}

View File

@ -0,0 +1,32 @@
package com.gewuyou.forgeboot.cache.api.generator
/**
* 键生成器接口
*
* 该接口用于统一管理不同场景下的缓存键生成逻辑通过命名空间和基础键值组合生成完整键
*
* @since 2025-06-16 22:17:36
* @author gewuyou
*/
interface KeyGenerator {
/**
* 生成缓存键
*
* 根据传入的命名空间和基础键值组合生成一个完整的缓存键实现类应确保返回的键具有唯一性与可读性
*
* @param namespace 命名空间用于隔离不同的缓存区域例如模块名称或业务标识
* @param key 基础键值表示具体缓存项标识通常是动态参数或固定字符串
* @return 返回生成的完整缓存键通常格式为 "namespace:key"
*/
fun generateKey(namespace: String, key: String): String
/**
* 获取连接符
*
* 返回用于拼接命名空间和基础键值的连接符默认实现可能返回冒号:或其他特定字符
* 此方法允许子类自定义连接符以满足不同存储结构或命名规范的需求
*
* @return 返回连接符字符串
*/
fun getConnectors(): String
}

View File

@ -0,0 +1,18 @@
package com.gewuyou.forgeboot.cache.api.loader
/**
* 通用缓存加载器接口支持缓存未命中时从源头加载数据
*
* @since 2025-06-16
*/
fun interface CacheLoader<T : Any> {
/**
* 加载与指定缓存键关联的数据
*
* @param key 缓存键字符串形式
* @param type 缓存值类型用于反序列化
* @return 加载到的数据若无则返回 null
*/
fun load(key: String, type: Class<T>): T?
}

View File

@ -0,0 +1,37 @@
package com.gewuyou.forgeboot.cache.api.manager
import com.gewuyou.forgeboot.cache.api.service.CacheService
/**
* 缓存管理器
*
* 提供对不同缓存服务的统一访问接口通过指定的命名空间获取对应的缓存服务实例
* 该接口定义了核心方法用于获取泛型化的缓存服务对象
*
* @since 2025-06-16 22:11:06
* @author gewuyou
*/
interface CacheManager {
/**
* 获取指定命名空间的缓存服务实例
*
* @param namespace 缓存服务的命名空间标识
* @return 返回与命名空间关联的 CacheService 实例
*/
fun getCache(namespace: String): CacheService
/**
* 清除指定命名空间下的所有缓存数据
*
* @param namespace 要清除缓存数据的命名空间标识
*/
fun clear(namespace: String)
/**
* 清除所有命名空间下的缓存数据
*
* 通常用于全局缓存刷新或系统清理操作
*/
fun clearAll()
}

View File

@ -0,0 +1,28 @@
package com.gewuyou.forgeboot.cache.api.policy
/**
* 缓存策略接口定义了缓存数据应用的规则
*
* 实现该接口的类应提供一个 apply 方法用于根据给定的键和值来执行特定的缓存策略
* 例如可以在此方法中实现过期时间设置缓存淘汰算法等逻辑
*
* @since 2025-06-21 11:54:50
* @author gewuyou
*/
interface CachePolicy {
/**
* 根据给定的键和值来执行缓存策略
*
* @param key 缓存项的键用于唯一标识一个缓存条目
* @param value 可为空的字符串表示与键关联的值 null 时可能指示移除或跳过缓存操作
* @return 返回处理后的缓存值也可以是 null 表示不进行缓存
*/
fun apply(key: String, value: String?): String?
/**
* 获取该策略的优先级用于决定多个策略的执行顺序
*
* @return 优先级值越小表示优先级越高
*/
fun getOrder(): Int
}

View File

@ -0,0 +1,34 @@
package com.gewuyou.forgeboot.cache.api.policy
/**
* Null 值策略接口
*
* 用于定义缓存中 null 值的处理策略包括是否允许缓存 null 获取 null 占位符以及判断值是否为 null 占位符
*
* @since 2025-06-16 22:15:13
* @author gewuyou
*/
interface NullValuePolicy {
/**
* 判断给定的 key 是否允许缓存 null
*
* @param key 缓存项的键
* @return Boolean 返回 true 表示允许缓存 null 否则不允许
*/
fun allowCacheNull(key: String): Boolean
/**
* 获取用于表示 null 值的占位符字符串
*
* @return String 返回 null 值的占位符
*/
fun nullPlaceholder(): String
/**
* 判断给定的 value 是否为 null 占位符
*
* @param value 要判断的值
* @return Boolean 返回 true 表示是 null 占位符否则不是
*/
fun isNullPlaceholder(value: String): Boolean
}

View File

@ -0,0 +1,54 @@
package com.gewuyou.forgeboot.cache.api.service
import java.time.Duration
/**
* 缓存服务接口定义了缓存的基本操作
*
* @since 2025-06-16 21:58:56
* @author gewuyou
*/
interface CacheService {
/**
* 获取指定键的缓存值
*
* @param key 缓存键
* @param type 值的类型 Class
* @return 缓存中的对象如果不存在则为 null
*/
fun <T : Any> retrieve(key: String, type: Class<T>): T?
/**
* 设置缓存值
*
* @param key 缓存键
* @param value 支持任意对象
* @param ttl 缓存时长null 表示使用默认 TTL
*/
fun put(key: String, value: Any, ttl: Duration? = null)
/**
* 删除缓存
*
* @param key 缓存键
* @return 删除成功返回 true
*/
fun remove(key: String): Boolean
/**
* 判断缓存是否存在
*
* @param key 缓存键
* @return 如果存在返回 true否则返回 false
*/
fun exists(key: String): Boolean
/**
* 清空指定命名空间下的缓存
*
* @param namespace 命名空间
*/
fun clear(namespace: String)
}

View File

@ -0,0 +1,16 @@
package com.gewuyou.forgeboot.cache.api.service
/**
* 缓存预热服务
* 用于在系统启动或特定时机提前加载常用缓存数据提升后续请求的访问效率
*
* @since 2025-06-16 22:18:29
* @author gewuyou
*/
fun interface CacheWarmUpService {
/**
* 执行缓存预热操作
* 该方法应包含具体的缓存加载逻辑例如查询数据库或调用其他服务获取数据并写入缓存
*/
fun warmUp()
}

View File

@ -0,0 +1,43 @@
package com.gewuyou.forgeboot.cache.api.service
import java.time.Duration
/**
* 锁服务接口用于在分布式环境中执行需要加锁的操作
*
* @since 2025-06-16 22:11:57
* @author gewuyou
*/
interface LockService {
/**
* 在锁的保护下执行指定操作
*
* 使用给定的 key 获取锁并在指定的超时时间内执行 supplier 提供的操作
* 如果无法在超时时间内获取锁则可能抛出异常或返回默认值具体取决于实现方式
*
* @param <T> 返回值类型
* @param key 锁的唯一标识符用于区分不同的资源锁
* @param timeout 获取锁的最大等待时间单位为秒
* @param supplier 需要在锁保护下执行的操作提供返回值
* @return 执行 supplier 后返回的结果
*/
fun <T> executeWithLock(key: String, timeout: Duration, supplier: () -> T): T
/**
* 执行带读锁的操作
*
* @param key 锁键
* @param timeout 获取锁超时时间
* @param supplier 要执行的读操作
*/
fun <T> executeWithReadLock(key: String, timeout: Duration, supplier: () -> T): T
/**
* 执行带写锁的操作
*
* @param key 锁键
* @param timeout 获取锁超时时间
* @param supplier 要执行的写操作
*/
fun <T> executeWithWriteLock(key: String, timeout: Duration, supplier: () -> T): T
}

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,12 @@
plugins {
alias { libs.plugins.kotlin.plugin.spring }
}
dependencies {
compileOnly(project(Modules.Cache.API))
compileOnly(project(Modules.Cache.IMPL))
compileOnly(libs.springBoot.autoconfigure)
implementation(libs.springBootStarter.redis)
implementation(libs.jackson.databind)
implementation(libs.redisson.springBootStarter)
api(project(Modules.Core.SERIALIZATION))
}

View File

@ -0,0 +1,43 @@
package com.gewuyou.forgeboot.cache.autoconfigure
import com.gewuyou.forgeboot.cache.api.config.CacheProperties
import com.gewuyou.forgeboot.cache.autoconfigure.config.*
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Import
/**
* 缓存自动配置类
*
* 该类通过组合多个配置类实现完整的缓存功能装配负责集成并协调以下模块
* - Redis 基础设施连接工厂与操作模板[CacheBaseConfig]
* - 缓存策略定义该配置类用于定义缓存相关的策略 Bean[CachePolicyConfig]
* - 缓存实现层基于 Redis Caffeine 的两级缓存支持[CacheImplConfig]
* - 多级缓存架构支持本地缓存与分布式缓存的协同工作[CacheLayerConfig]
* - 组合缓存配置用于构建复合缓存操作逻辑[CacheCompositeConfig]
* - 序列化管理统一处理键值序列化与反序列化[CacheSerializerConfig]
* - 缓存管理器提供统一访问入口和运行时管理能力[CacheManagerConfig]
* - 缓存预热机制系统启动后自动加载热点数据[CacheWarmUpConfig]
* - 缓存切面支持为注解如 @CacheableEx, @CacheEvictEx 提供执行上下文[CacheAspectConfig]
* - 分布式锁服务提供跨节点资源同步机制[LockServiceConfig]
*
* 所有导入的配置类共同构成完整的缓存解决方案适用于高并发场景下的性能优化需求
*
* @property CacheProperties 提供外部可配置参数用于定制缓存行为
*
* @since 2025-06-17 20:49:17
* @author gewuyou
*/
@Import(
CacheBaseConfig::class,
CachePolicyConfig::class,
CacheImplConfig::class,
CacheLayerConfig::class,
CacheCompositeConfig::class,
CacheSerializerConfig::class,
CacheManagerConfig::class,
CacheWarmUpConfig::class,
CacheAspectConfig::class,
LockServiceConfig::class
)
@EnableConfigurationProperties(CacheProperties::class)
class CacheAutoConfiguration

View File

@ -0,0 +1,76 @@
package com.gewuyou.forgeboot.cache.autoconfigure.config
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
import com.gewuyou.forgeboot.cache.api.manager.CacheManager
import com.gewuyou.forgeboot.cache.impl.aspect.CacheEvictExAspect
import com.gewuyou.forgeboot.cache.impl.aspect.CachePutExAspect
import com.gewuyou.forgeboot.cache.impl.aspect.CacheableExAspect
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* 缓存切面配置类
*
* 用于定义缓存相关的切面 Bean包括 CacheableExAspectCacheEvictExAspect CachePutExAspect
* 这些切面处理基于 Spring AOP 的缓存操作逻辑
*
* @since 2025-06-21 11:41:51
* @author gewuyou
*/
@Configuration
class CacheAspectConfig {
/**
* 创建 CacheableExAspect 切面 Bean
*
* 该方法将 CacheManager KeyGenerator 注入到 CacheableExAspect 实例中
* 用于构建支持扩展的缓存获取切面逻辑
*
* @param cacheManager 缓存管理器用于管理具体的缓存实现
* @param keyGenerator 键生成器用于根据方法参数生成缓存键
* @return 初始化完成的 CacheableExAspect 实例
*/
@Bean
fun cacheableExAspect(
cacheManager: CacheManager,
keyGenerator: KeyGenerator
): CacheableExAspect {
return CacheableExAspect(cacheManager, keyGenerator)
}
/**
* 创建 CacheEvictExAspect 切面 Bean
*
* 该方法将 CacheManager KeyGenerator 注入到 CacheEvictExAspect 实例中
* 用于构建支持扩展的缓存清除切面逻辑
*
* @param cacheManager 缓存管理器用于管理具体的缓存实现
* @param keyGenerator 键生成器用于根据方法参数生成缓存键
* @return 初始化完成的 CacheEvictExAspect 实例
*/
@Bean
fun cacheEvictExAspect(
cacheManager: CacheManager,
keyGenerator: KeyGenerator
): CacheEvictExAspect {
return CacheEvictExAspect(cacheManager, keyGenerator)
}
/**
* 创建 CachePutExAspect 切面 Bean
*
* 该方法将 CacheManager KeyGenerator 注入到 CachePutExAspect 实例中
* 用于构建支持扩展的缓存更新切面逻辑
*
* @param cacheManager 缓存管理器用于管理具体的缓存实现
* @param keyGenerator 键生成器用于根据方法参数生成缓存键
* @return 初始化完成的 CachePutExAspect 实例
*/
@Bean
fun cachePutExAspect(
cacheManager: CacheManager,
keyGenerator: KeyGenerator
): CachePutExAspect {
return CachePutExAspect(cacheManager, keyGenerator)
}
}

View File

@ -0,0 +1,73 @@
package com.gewuyou.forgeboot.cache.autoconfigure.config
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
import com.gewuyou.forgeboot.cache.impl.generator.DefaultKeyGenerator
import com.gewuyou.forgeboot.cache.impl.utils.RedisKeyScanner
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
import org.springframework.data.redis.core.StringRedisTemplate
/**
* 缓存基础配置类用于定义缓存相关的基础 Bean
*
* @since 2025-06-20 23:31:09
* @author gewuyou
*/
@Configuration(proxyBeanMethods = false)
class CacheBaseConfig {
/**
* 创建 StringRedisTemplate Bean
*
* StringRedisTemplate Spring Data Redis 提供的模板类
* 专门用于操作 Redis 中的字符串类型数据
*
* @return 配置好的 StringRedisTemplate 实例
*/
@Bean
fun redisTemplate(): StringRedisTemplate {
return StringRedisTemplate()
}
/**
* 创建 RedisConnectionFactory Bean
*
* RedisConnectionFactory 是连接 Redis 数据库的基础组件
* 使用 Lettuce 客户端实现连接
*
* @return 配置好的 LettuceConnectionFactory 实例
*/
@Bean
fun redisConnectionFactory(): RedisConnectionFactory {
return LettuceConnectionFactory()
}
/**
* 创建 RedisKeyScanner Bean
*
* RedisKeyScanner 用于扫描 Redis 中的键Key便于管理与维护缓存数据
*
* @param redisConnectionFactory 已注入的 Redis 连接工厂实例
* @return 配置好的 RedisKeyScanner 实例
*/
@Bean
fun redisKeyScanner(redisConnectionFactory: RedisConnectionFactory): RedisKeyScanner {
return RedisKeyScanner(redisConnectionFactory)
}
/**
* 创建默认的 KeyGenerator Bean
*
* DefaultKeyGenerator 用于生成统一格式的缓存键确保缓存键的命名一致性
* 若上下文中不存在 KeyGenerator 类型的 Bean则自动创建此默认实例
*
* @return 默认的 KeyGenerator 实现实例
*/
@Bean
@ConditionalOnMissingBean
fun keyGenerator(): KeyGenerator {
return DefaultKeyGenerator()
}
}

View File

@ -0,0 +1,41 @@
package com.gewuyou.forgeboot.cache.autoconfigure.config
import com.gewuyou.forgeboot.cache.api.config.CacheProperties
import com.gewuyou.forgeboot.cache.api.contract.Cache
import com.gewuyou.forgeboot.cache.api.entities.CacheLayer
import com.gewuyou.forgeboot.cache.impl.contract.CompositeCache
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
/**
* 缓存复合配置类
*
* 该配置类用于定义与缓存相关的主缓存 Bean通过组合多层缓存实现灵活的缓存策略
* 主要职责是创建并初始化 CompositeCache 实例作为 Spring 容器中的核心缓存组件
*
* @since 2025-06-21 11:40:10
* @author gewuyou
*/
@Configuration
class CacheCompositeConfig {
/**
* 创建主缓存 Bean使用 CompositeCache 组合多层缓存
*
* 该方法将 cacheLayers cacheProperties 注入到 CompositeCache 实例中
* 构建出一个支持多层缓存结构的主缓存组件该组件会被 Spring 标记为首选 Bean
*
* @param cacheLayers 缓存层列表包含多个 CacheLayer 实例用于构建缓存层级结构
* @param cacheProperties 缓存配置对象包含全局缓存相关属性设置
* @return 返回构建完成的 Cache 实例实际类型为 CompositeCache
*/
@Bean("compositeCache")
@Primary
fun compositeCache(
cacheLayers: List<CacheLayer>,
cacheProperties: CacheProperties,
): Cache {
return CompositeCache(cacheLayers, cacheProperties)
}
}

View File

@ -0,0 +1,65 @@
package com.gewuyou.forgeboot.cache.autoconfigure.config
import com.gewuyou.forgeboot.cache.api.contract.Cache
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
import com.gewuyou.forgeboot.cache.api.policy.CachePolicy
import com.gewuyou.forgeboot.cache.api.policy.NullValuePolicy
import com.gewuyou.forgeboot.cache.impl.contract.MultiPolicyCache
import com.gewuyou.forgeboot.cache.impl.contract.PerEntryTtlCaffeineCache
import com.gewuyou.forgeboot.cache.impl.contract.RedisCache
import com.gewuyou.forgeboot.cache.impl.utils.RedisKeyScanner
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.core.StringRedisTemplate
/**
* 缓存实现配置类
*
* 该配置类定义了缓存相关的 Bean包括基于 Redis Caffeine NullAwareCache 实例
*
* @since 2025-06-21 11:38:18
* @author gewuyou
*/
@Configuration
class CacheImplConfig {
/**
* 构建基于 Redis NullAwareCache 缓存实例
*
* 该方法创建了一个包装 RedisCache NullAwareCache 实例并结合 NullValuePolicy 控制 null 值的处理策略
*
* @param redisTemplate Redis 模板用于操作 Redis 数据库
* @param redisKeyScanner 用于扫描 Redis 中的键
* @param keyGenerator 用于生成缓存键
* @param nullCachePolicy NullValuePolicy 实例控制 null 值的缓存行为
* @return 返回一个基于 Redis Cache 实例
*/
@Bean("redisNullAwareCache")
fun redisNullAwareCache(
redisTemplate: StringRedisTemplate,
redisKeyScanner: RedisKeyScanner,
keyGenerator: KeyGenerator,
nullCachePolicy: NullValuePolicy,
policies: List<CachePolicy>
): Cache {
return MultiPolicyCache(RedisCache(redisTemplate, redisKeyScanner, keyGenerator), nullCachePolicy,policies)
}
/**
* 构建基于 Caffeine NullAwareCache 缓存实例
*
* 该方法创建了一个使用 PerEntryTtlCaffeineCache NullAwareCache 实例支持条目级别的 TTL生存时间设置
*
* @param keyGenerator 用于生成缓存键
* @param nullCachePolicy NullValuePolicy 实例控制 null 值的缓存行为
* @return 返回一个基于 Caffeine Cache 实例
*/
@Bean("perEntryTtlCaffeineNullAwareCache")
fun perEntryTtlCaffeineNullAwareCache(
keyGenerator: KeyGenerator,
nullCachePolicy: NullValuePolicy,
policies: List<CachePolicy>
): Cache {
return MultiPolicyCache(PerEntryTtlCaffeineCache(keyGenerator), nullCachePolicy,policies)
}
}

View File

@ -0,0 +1,43 @@
package com.gewuyou.forgeboot.cache.autoconfigure.config
import com.gewuyou.forgeboot.cache.api.contract.Cache
import com.gewuyou.forgeboot.cache.api.entities.CacheLayer
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* 缓存层配置
*
* 该配置类用于定义多级缓存的层级结构当前实现中包含两级缓存
* 第一层为基于 Caffeine 的本地缓存具有每个条目独立的生存时间TTL特性
* 第二层为基于 Redis 的分布式缓存用于跨服务共享缓存数据
*
* @since 2025-06-21 11:39:35
* @author gewuyou
*/
@Configuration
class CacheLayerConfig {
/**
* 定义缓存层级结构Caffeine 缓存在第一层Redis 缓存在第二层
*
* 该方法创建并返回一个包含两个缓存层的列表每层缓存都与一个具体的缓存实现绑定
* 并指定其在整体架构中的优先级顺序第一层使用本地的 Caffeine 缓存以提高访问速度
* 第二层使用 Redis 缓存来保证数据的共享和持久性
*
* @param redisNullAwareCache 第二层缓存基于 Redis Null-aware 缓存实现
* @param perEntryTtlCaffeineNullAwareCache 第一层缓存基于 Caffeine Null-aware 缓存实现支持每个缓存条目独立的 TTL 设置
* @return 包含两个缓存层的列表按优先级排序
*/
@Bean
fun cacheLayers(
@Qualifier("redisNullAwareCache") redisNullAwareCache: Cache,
@Qualifier("perEntryTtlCaffeineNullAwareCache") perEntryTtlCaffeineNullAwareCache: Cache,
): List<CacheLayer> {
return listOf(
CacheLayer(perEntryTtlCaffeineNullAwareCache, 1), // 第一层Caffeine 缓存,速度快,适合高频访问数据
CacheLayer(redisNullAwareCache, 2) // 第二层Redis 缓存,用于持久化存储和跨节点共享数据
)
}
}

View File

@ -0,0 +1,57 @@
package com.gewuyou.forgeboot.cache.autoconfigure.config
import com.gewuyou.forgeboot.cache.api.config.CacheProperties
import com.gewuyou.forgeboot.cache.api.contract.Cache
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
import com.gewuyou.forgeboot.cache.api.manager.CacheManager
import com.gewuyou.forgeboot.cache.impl.manager.DefaultCacheManager
import com.gewuyou.forgeboot.core.serialization.serializer.ValueSerializer
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* 缓存管理器配置
*
* 配置类用于定义缓存管理相关的 Bean主要职责是创建和初始化 CacheManager 实例
* 该配置类基于 Spring @Configuration 注解标识为配置类并通过 @Bean 定义核心 Bean
*
* @since 2025-06-21 11:41:05
* @author gewuyou
*/
@Configuration
class CacheManagerConfig {
/**
* 创建并配置 CacheManager 缓存管理器 Bean
*
* 此方法负责实例化一个 DefaultCacheManager它依赖以下组件
* - cacheProperties提供缓存的全局配置参数
* - cache实现缓存数据存储与访问的核心接口
* - serializer用于序列化和反序列化缓存值
* - keyGenerator生成缓存键的策略接口
*
* 返回的 CacheManager 是一个具体实现用于管理整个应用中的缓存操作
*
* @param cacheProperties 缓存配置信息
* @param cache 缓存存储与访问的具体实现
* @param serializer 序列化/反序列化工具
* @param keyGenerator 缓存键生成策略
* @return 初始化完成的缓存管理器实例
*/
@Bean
@ConditionalOnMissingBean
fun cacheManager(
cacheProperties: CacheProperties,
cache: Cache,
serializer: ValueSerializer,
keyGenerator: KeyGenerator
): CacheManager {
return DefaultCacheManager(
cacheProperties,
cache,
serializer,
keyGenerator
)
}
}

View File

@ -0,0 +1,53 @@
package com.gewuyou.forgeboot.cache.autoconfigure.config
import com.gewuyou.forgeboot.cache.api.config.CacheProperties
import com.gewuyou.forgeboot.cache.api.policy.CachePolicy
import com.gewuyou.forgeboot.cache.api.policy.NullValuePolicy
import com.gewuyou.forgeboot.cache.impl.policy.AllowNullCachePolicy
import com.gewuyou.forgeboot.cache.impl.policy.NullValueCachePolicy
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* 缓存策略配置类
*
* 该配置类用于定义缓存相关的策略 Bean当前主要包含 NullValuePolicy 的默认实现
* 所有需要注册为 Spring Bean 的缓存策略应在本类中进行声明和配置
*
* @since 2025-06-21 11:37:50
* @author gewuyou
*/
@Configuration
class CachePolicyConfig {
/**
* 创建 NullValuePolicy 类型的 Bean用于控制是否允许缓存 null
*
* 当前使用 AllowNullCachePolicy 作为默认实现表示允许缓存 null
* 如果应用上下文中已存在同类型的 Bean则跳过此 Bean 的创建
*
* @return 返回一个 NullValuePolicy 接口的实现对象
*/
@Bean
@ConditionalOnMissingBean
fun nullCachePolicy(cacheProperties: CacheProperties): NullValuePolicy {
return AllowNullCachePolicy(cacheProperties)
}
/**
* 创建 CachePolicy 类型的 Bean用于包装基础的 NullValuePolicy 实现
*
* 该方法将 NullValuePolicy 实例封装为一个 CachePolicy 接口的实现对象
* 允许在缓存操作中应用 null 值策略如果应用上下文中已存在同类型的 Bean
* 则跳过此 Bean 的创建
*
* @param nullValuePolicy 注入的 NullValuePolicy 实例用于决定是否允许缓存 null
* @return 返回封装后的 CachePolicy 对象
*/
@Bean("nullValueCachePolicy")
@ConditionalOnMissingBean
fun nullValueCachePolicy(nullValuePolicy: NullValuePolicy): CachePolicy {
return NullValueCachePolicy(nullValuePolicy)
}
}

View File

@ -0,0 +1,36 @@
package com.gewuyou.forgeboot.cache.autoconfigure.config
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.core.serialization.serializer.ValueSerializer
import com.gewuyou.forgeboot.core.serialization.serializer.impl.serializer.JacksonValueSerializer
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* 缓存序列化程序配置
*
* 该配置类用于定义缓存所需的序列化器 Bean
*
* @since 2025-06-21 11:40:45
* @author gewuyou
*/
@Configuration
class CacheSerializerConfig {
/**
* 创建 JacksonValueSerializer 序列化器 Bean
*
* 该方法定义了一个 JacksonValueSerializer Bean用于将值序列化和反序列化
* 适用于与 Jackson 库集成进行 JSON 数据格式的处理
*
* @param objectMapper Jackson 提供的对象映射器用于实际的序列化/反序列化操作
* @return 返回一个 JacksonValueSerializer 实例作为 ValueSerializer 接口的实现
*/
@Bean
@ConditionalOnMissingBean
fun serializer(objectMapper: ObjectMapper): ValueSerializer {
return JacksonValueSerializer(objectMapper)
}
}

View File

@ -0,0 +1,34 @@
package com.gewuyou.forgeboot.cache.autoconfigure.config
import com.gewuyou.forgeboot.cache.api.service.CacheWarmUpService
import com.gewuyou.forgeboot.cache.impl.runner.CacheWarmUpRunner
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* 缓存预热配置类
*
* 该配置类用于定义与缓存预热相关的 Bean确保应用启动时能够自动执行缓存预热逻辑
*
* @since 2025-06-21 11:41:34
* @author gewuyou
*/
@Configuration
class CacheWarmUpConfig {
/**
* 创建 CacheWarmUpRunner Bean
*
* 该方法将一组 CacheWarmUpService 实例注入到 CacheWarmUpRunner
* 用于在应用启动时运行缓存预热任务
*
* @param services 缓存预热服务的列表每个服务实现具体的预热逻辑
* @return 返回一个 CacheWarmUpRunner 实例用于启动缓存预热流程
*/
@Bean
fun cacheWarmUpRunner(
services: List<CacheWarmUpService>
): CacheWarmUpRunner {
return CacheWarmUpRunner(services)
}
}

View File

@ -0,0 +1,35 @@
package com.gewuyou.forgeboot.cache.autoconfigure.config
import com.gewuyou.forgeboot.cache.api.service.LockService
import com.gewuyou.forgeboot.cache.impl.service.RedisLockService
import org.redisson.api.RedissonClient
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* 锁服务配置类
*
* 该配置类用于定义与锁服务相关的 Bean确保应用中可以正常使用分布式锁功能
*
* @since 2025-06-21 11:42:20
* @author gewuyou
*/
@Configuration
class LockServiceConfig {
/**
* 创建 LockService 锁服务 Bean
*
* 该方法定义了一个 LockService 类型的 Bean用于提供基于 Redisson 的分布式锁实现
* 如果 Spring 容器中尚未存在 LockService 实例则会通过此方法创建一个
*
* @param redissonClient Redisson 客户端实例用于与 Redis 进行交互并创建锁
* @return 返回一个 LockService 实例具体实现为 RedisLockService
*/
@Bean
@ConditionalOnMissingBean
fun lockService(redissonClient: RedissonClient): LockService {
return RedisLockService(redissonClient)
}
}

View File

@ -0,0 +1 @@
com.gewuyou.forgeboot.cache.autoconfigure.CacheAutoConfiguration

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,13 @@
plugins{
alias(libs.plugins.kotlin.plugin.spring)
}
dependencies {
compileOnly(project(Modules.Cache.API))
compileOnly(project(Modules.Core.SERIALIZATION))
implementation(libs.springBootStarter.aop)
implementation(libs.springBootStarter.redis)
implementation(libs.com.github.benManes.caffeine)
implementation(libs.kotlinxCoroutines.core)
implementation(libs.jackson.databind)
implementation(libs.redisson.springBootStarter)
}

View File

@ -0,0 +1,50 @@
package com.gewuyou.forgeboot.cache.impl.aspect
import com.gewuyou.forgeboot.cache.api.annotations.CacheEvictEx
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
import com.gewuyou.forgeboot.cache.api.manager.CacheManager
import com.gewuyou.forgeboot.cache.impl.support.CacheSpELHelper
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.After
import org.aspectj.lang.annotation.Aspect
/**
* 缓存清理扩展切面
*
* 该切面用于处理带有 [CacheEvictEx] 注解的方法在方法执行后清除指定的缓存项
*
* @property cacheManager 用于管理缓存的组件提供缓存操作的接口
* @property keyGenerator 用于生成缓存键的辅助组件支持动态键值解析
*
* @since 2025-06-18 20:59:18
* @author gewuyou
*/
@Aspect
class CacheEvictExAspect(
private val cacheManager: CacheManager,
private val keyGenerator: KeyGenerator
) {
/**
* 处理被 [CacheEvictEx] 注解标记的方法调用后的缓存清理逻辑
*
* 该方法会在目标方法执行完成后触发根据注解配置的命名空间和 SpEL 表达式
* 构建完整的缓存键并通过 [cacheManager] 移除对应的缓存项
*
* @param joinPoint 封装了目标方法调用上下文的信息包括方法参数返回值等
* @param cacheEvictEx 注解实例包含缓存清理所需的配置信息
*/
@After("@annotation(cacheEvictEx)")
fun handle(joinPoint: JoinPoint, cacheEvictEx: CacheEvictEx) {
val namespace = cacheEvictEx.namespace
// 解析并构建完整的缓存键
val fullKey = CacheSpELHelper.parseKey(
namespace,
cacheEvictEx.keySpEL,
joinPoint,
keyGenerator
)
// 执行缓存项移除操作
cacheManager.getCache(namespace).remove(fullKey)
}
}

View File

@ -0,0 +1,55 @@
package com.gewuyou.forgeboot.cache.impl.aspect
import com.gewuyou.forgeboot.cache.api.annotations.CachePutEx
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
import com.gewuyou.forgeboot.cache.api.manager.CacheManager
import com.gewuyou.forgeboot.cache.impl.support.CacheSpELHelper
import com.gewuyou.forgeboot.cache.impl.utils.SpELResolver
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.AfterReturning
import org.aspectj.lang.annotation.Aspect
import java.time.Duration
/**
* 缓存插入扩展切面
*
* 该切面用于处理带有 [CachePutEx] 注解的方法将方法执行结果根据指定的命名空间和键存储到缓存中并设置过期时间
*
* @property cacheManager 缓存管理器用于获取缓存服务实例
* @property keyGenerator 缓存键生成器用于生成完整的缓存键
*
* @since 2025-06-18 20:57:27
* @author gewuyou
*/
@Aspect
class CachePutExAspect(
private val cacheManager: CacheManager,
private val keyGenerator: KeyGenerator
) {
/**
* 处理带有 [CachePutEx] 注解的方法返回后逻辑
*
* 通过 AOP 获取方法参数注解配置的命名空间键表达式和过期时间
* 使用 [SpELResolver] 解析出最终的缓存键并调用 [cacheManager] 将结果写入缓存
*
* @param joinPoint 切点信息包含目标方法及其参数
* @param cachePutEx 方法上的 [CachePutEx] 注解实例定义缓存行为配置
* @param result 方法执行后的返回值
*/
@AfterReturning("@annotation(cachePutEx)", returning = "result")
fun handle(joinPoint: JoinPoint, cachePutEx: CachePutEx, result: Any?) {
if (result == null) return
val namespace = cachePutEx.namespace
// 解析缓存键
val fullKey = CacheSpELHelper.parseKey(
namespace,
cachePutEx.keySpEL,
joinPoint,
keyGenerator
)
// 获取缓存过期时间(单位:秒)
val ttl = cachePutEx.ttl
cacheManager.getCache(namespace).put(fullKey, result, Duration.ofSeconds(ttl))
}
}

View File

@ -0,0 +1,76 @@
package com.gewuyou.forgeboot.cache.impl.aspect
import com.gewuyou.forgeboot.cache.api.annotations.CacheableEx
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
import com.gewuyou.forgeboot.cache.api.manager.CacheManager
import com.gewuyou.forgeboot.cache.impl.utils.SpELResolver
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.reflect.MethodSignature
import java.time.Duration
/**
* 可缓存的扩展切面类
*
* 用于处理带有 @CacheableEx 注解的方法实现方法结果的缓存逻辑
* 通过 AOP 拦截注解方法根据配置生成缓存键并操作缓存服务
*
* @property cacheManager 缓存管理器用于操作缓存服务
* @property keyGenerator 缓存键生成器用于生成完整的缓存键
*/
@Aspect
class CacheableExAspect(
private val cacheManager: CacheManager,
private val keyGenerator: KeyGenerator
) {
/**
* 环绕通知方法处理缓存逻辑
*
* 执行流程
* 1. 解析方法参数并生成参数映射表
* 2. 使用 SpEL 表达式解析器生成缓存键
* 3. 从缓存中尝试获取已存在的值
* 4. 如果缓存命中且值不为 null或允许缓存 null 则直接返回缓存结果
* 5. 否则执行目标方法并将结果写入缓存
*
* @param joinPoint 切点信息包含目标方法及参数等
* @param cacheableEx 方法上的 @CacheableEx 注解实例提供缓存配置
* @return 目标方法的执行结果可能是缓存值也可能是实际调用结果
*/
@Around("@annotation(cacheableEx)")
fun handle(joinPoint: ProceedingJoinPoint, cacheableEx: CacheableEx): Any? {
// 提取目标方法对象
val method = (joinPoint.signature as MethodSignature).method
// 构建参数名称与值的映射关系
val argsMap = method.parameters.mapIndexed { i, param -> param.name to joinPoint.args[i] }.toMap()
// 使用 SpEL 解析表达式生成缓存键
val key = SpELResolver.parse(cacheableEx.keySpEL, argsMap)
// 获取缓存值类型、命名空间和过期时间
val type = cacheableEx.type.java
val namespace = cacheableEx.namespace
val ttl = cacheableEx.ttl
// 生成完整缓存键
val fullKey = keyGenerator.generateKey(namespace, key)
val cacheService = cacheManager.getCache(namespace)
// 尝试从缓存获取数据
val cached = cacheService.retrieve(fullKey, type)
if (cached != null) return cached
// 执行原始方法逻辑
val result = joinPoint.proceed()
// 根据是否允许缓存 null 值进行判断
if (result == null && !cacheableEx.cacheNull) return null
// 将非 null 结果写入缓存,空值使用占位符 ""
cacheService.put(fullKey, result ?: "", Duration.ofSeconds(ttl))
return result
}
}

View File

@ -0,0 +1,91 @@
package com.gewuyou.forgeboot.cache.impl.contract
import com.gewuyou.forgeboot.cache.api.config.CacheProperties
import com.gewuyou.forgeboot.cache.api.contract.Cache
import com.gewuyou.forgeboot.cache.api.entities.CacheLayer
import java.time.Duration
/**
* 复合缓存实现类通过组合多个缓存层提供统一的缓存访问接口
*
* 缓存按照优先级排序高优先级的缓存层在前读取时会从高到低依次查找
* 一旦在某一层找到数据就会将该数据回填到优先级更高的缓存层中
*
* @property layers 缓存层列表每个缓存层包含一个具体的缓存实例和优先级
* @property cacheProperties 配置属性对象用于获取默认缓存过期时间等配置参数
* @constructor 创建一个复合缓存实例并按优先级对缓存层进行排序
*/
class CompositeCache(
layers: List<CacheLayer>,
private val cacheProperties: CacheProperties
) : Cache {
/**
* 按照优先级排序后的缓存层列表
* 高优先级的缓存层排在前面用于后续的缓存查找与回填逻辑
*/
private val sortedLayers = layers.sortedBy { it.priority }
/**
* 获取指定键对应的缓存值
*
* 从最高优先级的缓存层开始查找一旦找到有效值则将其回填到
* 所有优先级高于当前层的缓存中并返回该值
*
* @param key 要获取的缓存键
* @return 如果存在缓存值则返回对应值否则返回 null
*/
override fun retrieve(key: String): String? {
for ((index, layer) in sortedLayers.withIndex()) {
val value = layer.cache.retrieve(key)
if (value != null) {
// 将找到的值回填到所有更高优先级的缓存层中
for (i in 0 until index) {
sortedLayers[i].cache[key, value] = cacheProperties.theDefaultCacheTTL
}
return value
}
}
return null
}
/**
* 将指定键值对放入所有缓存层中
*
* @param key 要存储的缓存键
* @param value 要存储的缓存值
* @param ttl 存活时间
*/
override fun put(key: String, value: String?, ttl: Duration) {
sortedLayers.forEach { it.cache[key, value] = ttl }
}
/**
* 从所有缓存层中移除指定键的缓存项
*
* @param key 要移除的缓存键
* @return 如果所有缓存层都成功移除了该键则返回 true否则返回 false
*/
override fun remove(key: String): Boolean {
return sortedLayers.all { it.cache.remove(key) }
}
/**
* 判断指定键是否存在于任意一个缓存层中
*
* @param key 要检查的缓存键
* @return 如果存在任意一层缓存包含该键则返回 true否则返回 false
*/
override fun exists(key: String): Boolean {
return sortedLayers.any { it.cache.exists(key) }
}
/**
* 清空缓存中的所有数据
*
* @param namespace 命名空间用于限定清空操作的作用域
*/
override fun clear(namespace: String) {
sortedLayers.forEach { it.cache.clear(namespace) }
}
}

View File

@ -0,0 +1,88 @@
package com.gewuyou.forgeboot.cache.impl.contract
import com.gewuyou.forgeboot.cache.api.config.CacheProperties
import com.gewuyou.forgeboot.cache.api.contract.Cache
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
import com.github.benmanes.caffeine.cache.Caffeine
import java.time.Duration
import java.util.concurrent.TimeUnit
/**
* 全局 TTL 咖啡因缓存
*
* 该实现基于 Caffeine 缓存库所有缓存项使用统一的默认过期时间TTL
* 不支持针对单个缓存项设置不同的 TTL
*
* @property cacheProperties 缓存配置属性用于获取默认 TTL 设置
* @property keyGenerator 键生成器用于构建带命名空间连接符的键前缀
* @since 2025-06-17 22:32:36
* @author gewuyou
*/
class GlobalTtlCaffeineCache(
cacheProperties: CacheProperties,
private val keyGenerator: KeyGenerator
) : Cache {
/**
* 初始化 Caffeine 缓存实例
*
* 使用 expireAfterWrite 设置写入后过期时间时间长度基于 cacheProperties.theDefaultCacheTTL 配置
* 最大缓存条目数量限制为 10,000
*/
private val cache = Caffeine.newBuilder()
.expireAfterWrite(cacheProperties.theDefaultCacheTTL.toMillis(), TimeUnit.MILLISECONDS)
.maximumSize(10_000)
.build<String, String>()
/**
* 获取指定 key 对应的缓存值
*
* @param key 缓存键
* @return 缓存中对应的值如果不存在则返回 null
*/
override fun retrieve(key: String): String? = cache.getIfPresent(key)
/**
* 将键值对放入缓存中
*
* 注意此处忽略传入的 ttl 参数统一使用 defaultTtl 控制过期时间
*
* @param key 缓存键
* @param value 缓存值若为 null 则不存储
* @param ttl 忽略此参数使用全局默认的过期时间
*/
override fun put(key: String, value: String?, ttl: Duration) {
if (value != null) cache.put(key, value)
}
/**
* 移除指定 key 的缓存项
*
* @param key 要移除的缓存键
* @return 总是返回 true表示操作成功
*/
override fun remove(key: String): Boolean {
cache.invalidate(key)
return true
}
/**
* 检查指定 key 是否存在于缓存中
*
* @param key 缓存键
* @return 如果存在则返回 true否则返回 false
*/
override fun exists(key: String): Boolean = cache.getIfPresent(key) != null
/**
* 清除指定命名空间下的所有缓存项
*
* 根据命名空间和连接符拼接出前缀并清除所有以前缀开头的 key
*
* @param namespace 命名空间前缀
*/
override fun clear(namespace: String) {
val prefix = "$namespace${keyGenerator.getConnectors()}"
cache.asMap().keys.filter { it.startsWith(prefix) }.forEach { cache.invalidate(it) }
}
}

View File

@ -0,0 +1,103 @@
package com.gewuyou.forgeboot.cache.impl.contract
import com.gewuyou.forgeboot.cache.api.contract.Cache
import com.gewuyou.forgeboot.cache.api.policy.CachePolicy
import com.gewuyou.forgeboot.cache.api.policy.NullValuePolicy
import java.time.Duration
/**
* 多策略缓存装饰器
*
* 支持多个缓存策略动态应用各种缓存策略 Null 值策略失效策略等
* 该类通过组合多个 CachePolicy 实现对基础缓存操作的增强处理
*
* @property delegate 被包装的真实缓存实现用于执行底层缓存操作
* @property policies 缓存策略列表按顺序依次应用于缓存值
* @since 2025-06-17 21:16:58
*/
class MultiPolicyCache(
private val delegate: Cache,
private val nullValuePolicy: NullValuePolicy,
private val policies: List<CachePolicy>,
) : Cache {
init {
// 排序
policies.sortedBy { it.getOrder() }
}
/**
* 获取缓存值并应用所有缓存策略
*
* 按照策略列表顺序依次处理获取到的缓存值
* - 如果原始值为 null null 占位符则返回 null
* - 否则返回经过策略处理后的最终值
*
* @param key 缓存键用于定位缓存项
* @return 经过策略处理后的缓存值如果为 null null 占位符则返回 null
*/
override fun retrieve(key: String): String? {
var value = delegate.retrieve(key)
// 按策略顺序依次处理缓存值
policies.forEach { value = it.apply(key, value) }
return value
}
/**
* 存储缓存值并应用所有缓存策略
*
* 按照策略列表顺序依次处理要存储的值
* - 如果值为 null可能被转换为 null 占位符
* - 最终将处理后的值委托给底层缓存实现进行存储
*
* @param key 缓存键用于标识要存储的缓存项
* @param value 要存储的值可能为 null
* @param ttl 缓存过期时间控制该项在缓存中的存活时长
*/
override fun put(key: String, value: String?, ttl: Duration) {
var processedValue = value
// 按策略顺序依次处理待存储的值
policies.forEach { processedValue = it.apply(key, processedValue) }
// 如果最终值是 null 且不允许缓存 null跳过缓存存储
if (processedValue == null && !nullValuePolicy.allowCacheNull(key)) {
// 不缓存 null 值
return
}
delegate.put(key, processedValue, ttl)
}
/**
* 移除指定缓存项
*
* 将移除操作直接委托给底层缓存实现
*
* @param key 要移除的缓存键
* @return 移除成功返回 true否则返回 false
*/
override fun remove(key: String): Boolean {
return delegate.remove(key)
}
/**
* 检查指定缓存项是否存在
*
* 将存在性检查操作直接委托给底层缓存实现
*
* @param key 要检查的缓存键
* @return 存在返回 true否则返回 false
*/
override fun exists(key: String): Boolean {
return delegate.exists(key)
}
/**
* 清除指定命名空间下的所有缓存
*
* 将清除操作直接委托给底层缓存实现
*
* @param namespace 要清除的命名空间
*/
override fun clear(namespace: String) {
delegate.clear(namespace)
}
}

View File

@ -0,0 +1,114 @@
package com.gewuyou.forgeboot.cache.impl.contract
import com.gewuyou.forgeboot.cache.api.contract.Cache
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
import kotlinx.coroutines.*
import java.time.Duration
import java.util.concurrent.ConcurrentHashMap
import kotlin.time.Duration.Companion.seconds
/**
* 每个条目 TTL 咖啡因缓存实现类
*
* 该缓存为每个缓存条目设置独立的生存时间TTL基于 Caffeine 缓存策略设计
*
* @since 2025-06-17 22:34:50
* @author gewuyou
*/
class PerEntryTtlCaffeineCache(
private val keyGenerator: KeyGenerator
) : Cache {
/**
* 缓存条目数据类用于存储值和过期时间
*
* @property value 缓存的字符串值
* @property expireAt 条目的过期时间戳毫秒
*/
private data class CacheEntry(val value: String, val expireAt: Long)
/**
* 使用线程安全的 ConcurrentHashMap 存储缓存条目
*/
private val map = ConcurrentHashMap<String, CacheEntry>(16)
/**
* 协程作用域用于执行后台清理任务
*/
private val cleanupScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
/**
* 初始化块启动定期清理任务
*
* 清理任务每 5 秒运行一次移除所有已过期的缓存条目
*/
init {
cleanupScope.launch {
while (this.isActive) {
val now = System.currentTimeMillis()
map.entries.removeIf { it.value.expireAt <= now }
delay(5.seconds)
}
}
}
/**
* 获取指定键的缓存值
*
* 如果缓存不存在或已过期则返回 null 并从缓存中移除该条目
*
* @param key 要获取的缓存键
* @return 缓存值如果存在且未过期否则 null
*/
override fun retrieve(key: String): String? {
val entry = map[key] ?: return null
return if (System.currentTimeMillis() > entry.expireAt) {
map.remove(key)
null
} else entry.value
}
/**
* 将键值对放入缓存并设置其生存时间
*
* 如果值为 null则不会插入任何内容
*
* @param key
* @param value
* @param ttl 生存时间Duration 类型
*/
override fun put(key: String, value: String?, ttl: Duration) {
if (value != null) {
val expireAt = System.currentTimeMillis() + ttl.toMillis()
map[key] = CacheEntry(value, expireAt)
}
}
/**
* 从缓存中移除指定键
*
* @param key 要移除的键
* @return 如果成功移除则返回 true否则 false
*/
override fun remove(key: String): Boolean = map.remove(key) != null
/**
* 检查指定键是否存在有效的缓存条目
*
* @param key 要检查的键
* @return 如果存在有效条目则返回 true否则 false
*/
override fun exists(key: String): Boolean = retrieve(key) != null
/**
* 清除指定命名空间下的所有缓存条目
*
* 根据命名空间和连接符生成前缀移除所有以该前缀开头的键
*
* @param namespace 命名空间
*/
override fun clear(namespace: String) {
val prefix = "$namespace${keyGenerator.getConnectors()}"
map.keys.removeIf { it.startsWith(prefix) }
}
}

View File

@ -0,0 +1,80 @@
package com.gewuyou.forgeboot.cache.impl.contract
import com.gewuyou.forgeboot.cache.api.contract.Cache
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
import com.gewuyou.forgeboot.cache.impl.utils.RedisKeyScanner
import org.springframework.data.redis.core.StringRedisTemplate
import java.time.Duration
/**
* Redis 缓存实现类用于通过 Redis 存储获取和管理缓存数据
*
* @property redisTemplate Spring 提供的 StringRedisTemplate 实例用于与 Redis 进行交互
* @property redisKeyScanner 用于扫描匹配的 Redis 键集合
* @author gewuyou
* @since 2025-06-17 21:08:36
*/
class RedisCache(
private val redisTemplate: StringRedisTemplate,
private val redisKeyScanner: RedisKeyScanner,
private val keyGenerator: KeyGenerator
) : Cache {
/**
* 获取指定键对应的缓存值
*
* @param key 缓存键非空字符串
* @return 缓存值如果不存在则返回 null
*/
override fun retrieve(key: String): String? {
return redisTemplate.opsForValue()[key]
}
/**
* 将键值对存储到缓存中并设置过期时间
*
* @param key 缓存键非空字符串
* @param value 缓存值可以为 null
* @param ttl 缓存存活时间Time To Live单位为秒或毫秒等
*/
override fun put(key: String, value: String?, ttl: Duration) {
redisTemplate.opsForValue()[key, value!!] = ttl
}
/**
* 删除指定键的缓存
*
* @param key 要删除的缓存键非空字符串
* @return 如果删除成功返回 true否则返回 false
*/
override fun remove(key: String): Boolean {
return redisTemplate.delete(key)
}
/**
* 判断指定键是否存在于缓存中
*
* @param key 缓存键非空字符串
* @return 如果存在返回 true否则返回 false
*/
override fun exists(key: String): Boolean {
return redisTemplate.hasKey(key)
}
/**
* 清空缓存中的所有数据基于给定的命名空间和连接符进行模式匹配
*
* @param namespace 命名空间用于限定要清除的键范围
*/
override fun clear(namespace: String) {
require(namespace.isNotBlank()) { "namespace must not be blank" }
// 构造匹配模式
val pattern = "$namespace${keyGenerator.getConnectors()}*"
while (true) {
// 扫描当前模式下的所有键
val keys = redisKeyScanner.scan(pattern)
if (keys.isEmpty()) break
// 批量删除扫描到的键
redisTemplate.delete(keys)
}
}
}

View File

@ -0,0 +1,39 @@
package com.gewuyou.forgeboot.cache.impl.generator
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
/**
* 默认缓存键生成器
*
* 该类负责生成带有命名空间的缓存键通过将命名空间和键值拼接为 `namespace:key` 的形式
*
* @since 2025-06-18 12:40:04
* @author gewuyou
*/
class DefaultKeyGenerator : KeyGenerator {
/**
* 生成缓存键
*
* 将给定的命名空间和键拼接成一个完整的缓存键格式为 `namespace:key`
*
* @param namespace 命名空间用于隔离不同的缓存区域
* @param key 缓存项的具体键值
* @return 拼接后的完整缓存键
*/
override fun generateKey(namespace: String, key: String): String {
return "$namespace${this.getConnectors()}$key"
}
/**
* 获取连接符
*
* 返回用于拼接命名空间和基础键值的连接符默认实现可能返回冒号:或其他特定字符
* 此方法允许子类自定义连接符以满足不同存储结构或命名规范的需求
*
* @return 返回连接符字符串
*/
override fun getConnectors(): String {
return ":"
}
}

View File

@ -0,0 +1,74 @@
package com.gewuyou.forgeboot.cache.impl.manager
import com.gewuyou.forgeboot.cache.api.config.CacheProperties
import com.gewuyou.forgeboot.cache.api.contract.Cache
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
import com.gewuyou.forgeboot.cache.api.manager.CacheManager
import com.gewuyou.forgeboot.cache.api.service.CacheService
import com.gewuyou.forgeboot.cache.impl.service.DefaultCacheService
import com.gewuyou.forgeboot.core.serialization.serializer.ValueSerializer
import java.util.concurrent.ConcurrentHashMap
/**
* 默认缓存管理器
*
* 该类负责管理不同命名空间下的缓存服务实例通过懒加载方式创建 CacheService
* 并提供统一的清除缓存接口
*
* @since 2025-06-18 13:39:07
* @author gewuyou
*/
class DefaultCacheManager(
private val cacheProperties: CacheProperties,
private val cache: Cache,
private val serializer: ValueSerializer,
private val keyGenerator: KeyGenerator
) : CacheManager {
/**
* 缓存服务实例的线程安全映射表键为命名空间值为对应的 CacheService 实例
* 使用 ConcurrentHashMap 确保多线程环境下的安全访问
*/
private val serviceMap = ConcurrentHashMap<String, CacheService>()
/**
* 获取指定命名空间的缓存服务
*
* 如果该命名空间尚未存在则使用提供的参数创建一个新的 DefaultCacheService 实例
* 否则返回已存在的实例
*
* @param namespace 缓存命名空间标识符
* @return 对应命名空间的缓存服务实例
*/
override fun getCache(namespace: String): CacheService {
return serviceMap.computeIfAbsent(namespace) {
DefaultCacheService(
serializer = serializer,
cache = cache,
namespace = namespace,
cacheProperties = cacheProperties,
keyGenerator = keyGenerator
)
}
}
/**
* 清除指定命名空间的缓存内容
*
* 如果该命名空间存在对应的缓存服务则调用其 clear 方法进行清除
*
* @param namespace 要清除缓存的命名空间标识符
*/
override fun clear(namespace: String) {
serviceMap[namespace]?.clear(namespace)
}
/**
* 清除所有命名空间的缓存内容
*
* 遍历 serviceMap 中的所有缓存服务并分别调用它们的 clear 方法
*/
override fun clearAll() {
serviceMap.forEach { (ns, svc) -> svc.clear(ns) }
}
}

View File

@ -0,0 +1,49 @@
package com.gewuyou.forgeboot.cache.impl.policy
import com.gewuyou.forgeboot.cache.api.config.CacheProperties
import com.gewuyou.forgeboot.cache.api.policy.NullValuePolicy
/**
* 允许 Null 缓存策略
*
* 该策略允许缓存 null 并提供相应的占位符机制
*
* @since 2025-06-20 21:42:24
* @author gewuyou
*/
class AllowNullCachePolicy(
private val cacheProperties: CacheProperties
) : NullValuePolicy {
/**
* 判断是否允许缓存 null
*
* @param key 缓存项的键
* @return Boolean 表示是否允许缓存 null 始终返回 true
*/
override fun allowCacheNull(key: String): Boolean {
// 始终允许缓存 null 值
return true
}
/**
* 获取表示 null 值的占位符
*
* @return String 表示 null 值的占位符
*/
override fun nullPlaceholder(): String {
// 定义一个占位符,表示 null 值
return cacheProperties.nullValuePlaceholder
}
/**
* 判断给定值是否为 null 占位符
*
* @param value 要检查的值
* @return Boolean 表示给定值是否为 null 占位符
*/
override fun isNullPlaceholder(value: String): Boolean {
// 判断值是否为 null 占位符
return value == cacheProperties.nullValuePlaceholder
}
}

View File

@ -0,0 +1,52 @@
package com.gewuyou.forgeboot.cache.impl.policy
import com.gewuyou.forgeboot.cache.api.policy.NullValuePolicy
/**
* 禁止 Null 缓存策略
*
* 该策略实现不允许缓存 null 值的行为所有与 null 相关的操作都会被禁止并抛出异常
*
* @since 2025-06-20 21:43:00
* @author gewuyou
*/
class DisallowNullCachePolicy : NullValuePolicy {
/**
* 判断是否允许缓存 null
*
* @param key 缓存键用于标识缓存项
* @return Boolean 返回 false 表示始终不允许缓存 null
*/
override fun allowCacheNull(key: String): Boolean {
// 始终不允许缓存 null 值
return false
}
/**
* 获取 null 值的占位符
*
* 由于不允许缓存 null 调用此方法会抛出 UnsupportedOperationException
*
* @return String 不会返回任何值总是抛出异常
* @throws UnsupportedOperationException 因为不允许缓存 null
*/
override fun nullPlaceholder(): String {
// 该策略不会使用占位符,因为不允许缓存 null
throw UnsupportedOperationException("Cannot use null placeholder when null is not allowed in cache.")
}
/**
* 判断指定值是否是 null 占位符
*
* 由于不允许缓存 null 此方法无法执行有效判断并会抛出异常
*
* @param value 要检查的值
* @return Boolean 不会返回任何值总是抛出异常
* @throws UnsupportedOperationException 因为不允许缓存 null
*/
override fun isNullPlaceholder(value: String): Boolean {
// 无法判断 null 占位符,因为 null 值不被缓存
throw UnsupportedOperationException("Cannot check null placeholder when null is not allowed in cache.")
}
}

View File

@ -0,0 +1,49 @@
package com.gewuyou.forgeboot.cache.impl.policy
import com.gewuyou.forgeboot.cache.api.policy.CachePolicy
import com.gewuyou.forgeboot.cache.api.policy.NullValuePolicy
/**
* 空值缓存策略
* 用于处理缓存中空值null的存储和占位逻辑
*
* @property nullValuePolicy 提供空值处理策略的接口实现
* @constructor 创建一个 NullValueCachePolicy 实例
* @param nullValuePolicy 指定的空值处理策略对象不可为 null
* @since 2025-06-21 11:56:54
* @author gewuyou
*/
class NullValueCachePolicy(
private val nullValuePolicy: NullValuePolicy
) : CachePolicy {
/**
* 应用当前策略对指定的缓存值进行处理
*
* @param key 缓存键用于判断是否允许缓存空值
* @param value 需要处理的缓存值可能为 null
* @return 处理后的缓存值
* - 如果值为 null 且允许缓存则返回 null 占位符
* - 如果值为 null 但不允许缓存则返回 null
* - 否则返回原始非 null
*/
override fun apply(key: String, value: String?): String? {
return when {
// 当值为空且策略允许缓存空值时,返回占位符
value == null && nullValuePolicy.allowCacheNull(key) -> nullValuePolicy.nullPlaceholder()
// 当值为空但策略不允许缓存时,直接返回 null
value == null -> null
// 非空值直接返回
else -> value
}
}
/**
* 获取该策略的优先级用于决定多个策略的执行顺序
*
* @return 优先级值越小表示优先级越高
*/
override fun getOrder(): Int {
return 1
}
}

View File

@ -0,0 +1,32 @@
package com.gewuyou.forgeboot.cache.impl.runner
import com.gewuyou.forgeboot.cache.api.service.CacheWarmUpService
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
/**
* 缓存预热运行程序
*
* 该类实现 ApplicationRunner 接口用于在应用启动完成后执行缓存预热操作
* 构造函数注入了多个 CacheWarmUpService 实现每个服务对应一个需要预热的缓存资源
*
* @property services 需要执行预热操作的所有缓存服务集合
*
* @since 2025-06-18 12:36:40
* @author gewuyou
*/
class CacheWarmUpRunner(
private val services: List<CacheWarmUpService>
) : ApplicationRunner {
/**
* 应用启动完成后执行的方法
*
* 遍历所有注入的 CacheWarmUpService 并调用 warmUp 方法执行缓存预热逻辑
* 该方法会在 Spring Boot 应用完成启动后自动触发
*
* @param args 应用启动参数可为空
*/
override fun run(args: ApplicationArguments) {
services.forEach { it.warmUp() }
}
}

View File

@ -0,0 +1,93 @@
package com.gewuyou.forgeboot.cache.impl.service
import com.gewuyou.forgeboot.cache.api.config.CacheProperties
import com.gewuyou.forgeboot.cache.api.contract.Cache
import com.gewuyou.forgeboot.cache.api.extension.get
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
import com.gewuyou.forgeboot.cache.api.service.CacheService
import com.gewuyou.forgeboot.core.serialization.serializer.ValueSerializer
import java.time.Duration
/**
* 默认缓存服务实现类提供基于前缀的键值对缓存操作
*
* 该类实现了通用的缓存功能包括获取设置删除判断存在性和清空缓存的方法
* 所有方法均通过命名空间隔离不同业务的数据确保缓存管理的安全性和灵活性
*
* @property serializer 序列化/反序列化缓存值的工具
* @property cache 实际存储数据的复合缓存实例
* @property namespace 命名空间
* @property cacheProperties 缓存配置属性包含默认过期时间等
* @since 2025-06-16 22:56:49
* @author gewuyou
*/
class DefaultCacheService(
private val serializer: ValueSerializer,
private val cache: Cache,
private val namespace: String,
private val cacheProperties: CacheProperties,
private val keyGenerator: KeyGenerator,
) : CacheService {
/**
* 获取指定键的缓存值
*
* 在命名空间下拼接 key并从缓存中获取对应的值若值存在则使用序列化器将其转换为指定类型否则返回 null
*
* @param key 缓存键
* @param type 值的类型 Class
* @return 缓存中的对象如果不存在则为 null
*/
override fun <T : Any> retrieve(key: String, type: Class<T>): T? {
return cache[keyGenerator.generateKey(namespace,key)]?.let { serializer.deserialize(it, type) }
}
/**
* 设置缓存值
*
* 将值序列化后在命名空间下拼接 key 并写入缓存支持自定义 TTL生存时间若未指定则使用默认 TTL
*
* @param key 缓存键
* @param value 支持任意对象
* @param ttl 缓存时长null 表示使用默认 TTL
*/
override fun put(key: String, value: Any, ttl: Duration?) {
cache.put(keyGenerator.generateKey(namespace,key), serializer.serialize(value), (ttl ?: cacheProperties.theDefaultCacheTTL))
}
/**
* 删除缓存
*
* 在命名空间下拼接 key并尝试从缓存中移除对应条目
*
* @param key 缓存键
* @return 删除成功返回 true
*/
override fun remove(key: String): Boolean {
return cache.remove(keyGenerator.generateKey(namespace,key))
}
/**
* 判断缓存是否存在
*
* 检查在命名空间下拼接的 key 是否存在于缓存中
*
* @param key 缓存键
* @return 如果存在返回 true否则返回 false
*/
override fun exists(key: String): Boolean {
return cache.exists(keyGenerator.generateKey(namespace,key))
}
/**
* 清空指定命名空间下的所有缓存数据
*
* 直接调用底层缓存实现的 clear 方法传入命名空间参数
*
* @param namespace 命名空间
*/
override fun clear(namespace: String) {
cache.clear(namespace)
}
}

View File

@ -0,0 +1,106 @@
package com.gewuyou.forgeboot.cache.impl.service
import com.gewuyou.forgeboot.cache.api.exception.CacheException
import com.gewuyou.forgeboot.cache.api.service.LockService
import org.redisson.api.RLock
import org.redisson.api.RedissonClient
import java.time.Duration
import java.util.concurrent.TimeUnit
/**
* Redis 锁服务实现类
*
* 该类通过 Redisson 提供分布式锁功能确保在分布式环境下关键代码块的线程安全执行
*
* @property redissonClient Redisson 客户端实例用于获取和操作分布式锁
* @since 2025-06-18 21:17:21
* @author gewuyou
*/
class RedisLockService(
private val redissonClient: RedissonClient
) : LockService {
/**
* 在分布式锁保护下执行指定的操作
*
* 尝试获取指定 key 的分布式锁若成功则执行 supplier 函数并确保最终释放锁
* 若未能在指定时间内获取锁则抛出 CacheException 异常
*
* @param key 分布式锁的唯一标识
* @param timeout 获取锁的最大等待时间
* @param supplier 需要在锁保护下执行的业务逻辑
* @return supplier 执行后的返回结果
* @throws CacheException 如果无法获取到分布式锁
*/
override fun <T> executeWithLock(key: String, timeout: Duration, supplier: () -> T): T {
// 获取 Redisson 分布式锁实例
val lock: RLock = redissonClient.getLock(key)
// 尝试在指定时间内获取锁
val acquired: Boolean = lock.tryLock(timeout.seconds, TimeUnit.SECONDS)
if (!acquired) {
throw CacheException("failed to acquire distributed lockkey=$key")
}
return try {
// 执行受锁保护的业务逻辑
supplier()
} finally {
// 确保当前线程持有锁的情况下释放锁
if (lock.isHeldByCurrentThread) {
lock.unlock()
}
}
}
/**
* 使用读锁在受保护的上下文中执行指定操作
*
* 获取指定 key 的读锁并在限定时间内执行 supplier如果无法获得锁则抛出异常
*
* @param key 读锁的唯一标识
* @param timeout 获取锁的最大等待时间
* @param supplier 需要在读锁保护下执行的业务逻辑
* @return supplier 执行后的返回结果
* @throws CacheException 如果无法获取到读锁
*/
override fun <T> executeWithReadLock(key: String, timeout: Duration, supplier: () -> T): T {
val rwLock = redissonClient.getReadWriteLock(key).readLock()
val acquired = rwLock.tryLock(timeout.seconds, TimeUnit.SECONDS)
if (!acquired) {
throw CacheException("failed to obtain a read lock$key")
}
return try {
supplier()
} finally {
if (rwLock.isHeldByCurrentThread) {
rwLock.unlock()
}
}
}
/**
* 使用写锁在受保护的上下文中执行指定操作
*
* 获取指定 key 的写锁并在限定时间内执行 supplier如果无法获得锁则抛出异常
*
* @param key 写锁的唯一标识
* @param timeout 获取锁的最大等待时间
* @param supplier 需要在写锁保护下执行的业务逻辑
* @return supplier 执行后的返回结果
* @throws CacheException 如果无法获取到写锁
*/
override fun <T> executeWithWriteLock(key: String, timeout: Duration, supplier: () -> T): T {
val rwLock = redissonClient.getReadWriteLock(key).writeLock()
val acquired = rwLock.tryLock(timeout.seconds, TimeUnit.SECONDS)
if (!acquired) {
throw CacheException("failed to obtain a write lock$key")
}
return try {
supplier()
} finally {
if (rwLock.isHeldByCurrentThread) {
rwLock.unlock()
}
}
}
}

View File

@ -0,0 +1,51 @@
package com.gewuyou.forgeboot.cache.impl.support
import com.gewuyou.forgeboot.cache.api.generator.KeyGenerator
import com.gewuyou.forgeboot.cache.impl.utils.SpELResolver
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.reflect.MethodSignature
/**
* 缓存 SpEL 表达式帮助类
* 提供基于方法参数的 SpEL 解析和缓存键生成能力
*
* @since 2025-06-18 21:04:03
* @author gewuyou
*/
object CacheSpELHelper {
/**
* 构建方法参数名称到参数值的映射关系
* 用于支持 SpEL 表达式解析时的参数上下文
*
* @param joinPoint AOP 方法连接点包含方法签名和参数信息
* @return 参数名到参数值的映射表
*/
fun buildArgsMap(joinPoint: JoinPoint): Map<String, Any?> {
val method = (joinPoint.signature as MethodSignature).method
// 将方法参数列表转换为(参数名 -> 实际参数值)的键值对集合
return method.parameters.mapIndexed { i, param -> param.name to joinPoint.args[i] }.toMap()
}
/**
* 解析 SpEL 表达式并生成最终缓存键
*
* @param namespace 缓存命名空间用于隔离不同业务的缓存键
* @param keySpEL SpEL 表达式字符串定义缓存键的动态生成逻辑
* @param joinPoint AOP 方法连接点提供运行时参数上下文
* @param keyGenerator 用户定义的缓存键生成策略
* @return 最终生成的缓存键字符串
*/
fun parseKey(
namespace: String,
keySpEL: String,
joinPoint: JoinPoint,
keyGenerator: KeyGenerator
): String {
val argsMap = buildArgsMap(joinPoint)
// 使用 SpEL 解析器根据参数上下文解析表达式得到原始键值
val key = SpELResolver.parse(keySpEL, argsMap)
// 委托给键生成器结合命名空间生成最终缓存键
return keyGenerator.generateKey(namespace, key)
}
}

View File

@ -0,0 +1,38 @@
package com.gewuyou.forgeboot.cache.impl.utils
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.core.ScanOptions
import java.nio.charset.StandardCharsets
/**
*Redis 键扫描器
*
* @since 2025-06-17 21:32:15
* @author gewuyou
*/
class RedisKeyScanner(
private val redisConnectionFactory: RedisConnectionFactory
) {
/**
* 扫描指定 pattern 下的所有 key使用非阻塞 SCAN 命令
*
* @param pattern 通配符 "user:*"
* @param count 每次扫描的数量 默认1000
* @return 匹配到的所有 key 列表
*/
fun scan(pattern: String, count: Long = 1000): Set<String> {
require(pattern.isNotBlank()) { "Scan pattern must not be blank" }
val keys = mutableSetOf<String>()
redisConnectionFactory.connection.use { connection ->
val options = ScanOptions.scanOptions().match(pattern).count(count).build()
connection.keyCommands().scan(options).use { cursor ->
while (cursor.hasNext()) {
val rawKey = cursor.next()
keys.add(String(rawKey, StandardCharsets.UTF_8))
}
}
}
return keys
}
}

View File

@ -0,0 +1,35 @@
package com.gewuyou.forgeboot.cache.impl.utils
import org.springframework.expression.ExpressionParser
import org.springframework.expression.spel.standard.SpelExpressionParser
import org.springframework.expression.spel.support.StandardEvaluationContext
/**
* SpEL 解析器用于解析和执行 Spring Expression Language (SpEL) 表达式
*
* @since 2025-06-18 20:44:10
* @author gewuyou
*/
object SpELResolver {
/**
* 表达式解析器实例用于解析 SpEL 表达式
*/
private val parser: ExpressionParser = SpelExpressionParser()
/**
* 解析给定的 SpEL 表达式并使用提供的参数进行求值
*
* @param expression SpEL 表达式字符串
* @param argsMap 包含变量及其对应值的映射表用于表达式求值
* @return 解析后的字符串结果如果表达式返回 null则返回空字符串
*/
fun parse(expression: String, argsMap: Map<String, Any?>): String {
// 创建评估上下文并设置变量
val context = StandardEvaluationContext().apply {
argsMap.forEach { (name, value) -> setVariable(name, value) }
}
// 解析并获取表达式的字符串值
return parser.parseExpression(expression).getValue(context, String::class.java) ?: ""
}
}

View File

@ -36,11 +36,30 @@ import org.springframework.web.reactive.function.client.ClientRequest
@Configuration
class ForgeContextAutoConfiguration {
/**
* 创建 ValueSerializer Bean用于在缺少其他实现时提供默认的值序列化器
*
* 该方法定义了一个 JacksonValueSerializer 实例作为默认的 ValueSerializer 实现
* 负责使用 Jackson 库对上下文字段值进行序列化和反序列化操作
*
* @param objectMapper 提供 JSON 序列化支持的 ObjectMapper 实例
* @return 构建完成的 ValueSerializer 实例
*/
@Bean
@ConditionalOnMissingBean
fun valueSerializer(objectMapper: ObjectMapper): ValueSerializer{
fun valueSerializer(objectMapper: ObjectMapper): ValueSerializer {
return JacksonValueSerializer(objectMapper)
}
/**
* 创建 ContextHolder Bean用于在缺少其他实现时提供默认的上下文持有者
*
* 该方法定义了一个 ContextHolder 实例作为默认的上下文管理器
* 负责存储传递和清理当前线程的上下文字段数据
*
* @param valueSerializer 提供值序列化支持的 ValueSerializer 实例
* @return 构建完成的 ContextHolder 实例
*/
@Bean
@ConditionalOnMissingBean
fun contextHolder(valueSerializer: ValueSerializer): ContextHolder {

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

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

40
forgeboot-security/.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,5 @@
extra{
setProperty(ProjectFlags.IS_ROOT_MODULE,true)
}
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,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,8 @@
dependencies {
api(project(Modules.Webmvc.DTO))
compileOnly(project(Modules.Security.CORE))
compileOnly(libs.springBootStarter.web)
compileOnly(libs.springBootStarter.security)
kapt(libs.springBoot.configuration.processor)
}

View File

@ -0,0 +1,45 @@
package com.gewuyou.forgeboot.security.authenticate.api.config
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.http.HttpMethod
/**
* 安全身份验证属性配置类
*
* 该类用于定义安全认证相关的配置属性包含路径权限设置异常响应信息和基础URL配置
* 属性值通过@ConfigurationProperties绑定前缀"forgeboot.security.authenticate"的配置项
*
* @since 2025-06-11 15:20:27
* @author gewuyou
*/
@ConfigurationProperties(prefix = "forgeboot.security.authenticate")
class SecurityAuthenticateProperties {
/**
* 登录请求的HTTP方法默认为POST
*/
var loginHttpMethod: String = HttpMethod.POST.name()
/**
* 默认认证异常响应内容当请求缺少认证时返回此字符串
*/
var defaultExceptionResponse = "Full authentication is required to access this resource"
/**
* 默认认证失败响应内容当认证失败时返回此字符串
*/
var defaultAuthenticationFailureResponse = "If the authentication fails, please report the request ID for more information!"
/**
* 认证模块的基础URL前缀
*/
var baseUrl = "/api/auth"
/**
* 登录接口的URL路径
*/
var loginUrl = "/login"
/**
* 登出接口的URL路径
*/
var logoutUrl = "/logout"
}

View File

@ -0,0 +1,24 @@
package com.gewuyou.forgeboot.security.authenticate.api.customizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
/**
* 登录过滤器定制器接口
*
* 该函数式接口用于自定义Spring Security的登录过滤器配置
* 实现此接口的类或lambda表达式可用于调整安全配置中的HttpSecurity设置
* 特别是在登录认证流程中对请求的安全策略进行定制化处理
*
* @since 2025-06-11 18:53:25
* @author gewuyou
*/
fun interface LoginFilterCustomizer {
/**
* 自定义方法用于配置或修改HttpSecurity对象
*
* 此方法将被调用以应用特定的安全性规则到登录过滤器上
*
* @param http HttpSecurity对象提供了配置Web安全性的API
*/
fun customize(http: HttpSecurity)
}

View File

@ -0,0 +1,28 @@
package com.gewuyou.forgeboot.security.authenticate.api.enums
/**
*登录类型
*
* @since 2025-06-12 21:11:02
* @author gewuyou
*/
object LoginTypes {
/**
* 默认登录类型标识
*/
const val DEFAULT = "default"
/**
* 用户名密码登录类型标识
*/
const val USERNAME_PASSWORD = "username_password"
/**
* OAuth2 认证登录类型标识
*/
const val OAUTH2 = "oauth2"
/**
* 短信验证码登录类型标识
*/
const val SMS = "sms"
}

View File

@ -0,0 +1,18 @@
package com.gewuyou.forgeboot.security.authenticate.api.exception
import org.springframework.security.core.AuthenticationException
/**
*Forge Boot身份验证异常
*
* @since 2025-06-11 15:49:33
* @author gewuyou
*/
open class ForgeBootAuthenticationException(
msg: String,
cause: Throwable? = null
): AuthenticationException(
msg,
cause
)

View File

@ -0,0 +1,27 @@
package com.gewuyou.forgeboot.security.authenticate.api.handler
import jakarta.servlet.http.HttpServletRequest
/**
* 委派处理程序接口用于定义委派处理器的行为规范
*
* 该接口设计为泛型函数式接口要求实现类提供一个方法
* 将给定的 HTTP 请求解析为指定类型的委派对象
*
* @param <T> 委派对象的类型必须是非空类型
*
* @since 2025-06-11 14:51:48
* @author gewuyou
*/
fun interface DelegatingHandler<T: Any> {
/**
* 根据提供的 HTTP 请求解析出对应的委派对象
*
* 此方法通常用于从请求中提取认证信息或上下文数据
* 并将其转换为具体的业务对象以供后续处理使用
*
* @param request 要解析的 HTTP 请求对象不能为 null
* @return 返回解析得到的委派对象具体类型由接口泛型 T 决定
*/
fun resolveDelegate(request: HttpServletRequest): T
}

View File

@ -0,0 +1,47 @@
package com.gewuyou.forgeboot.security.authenticate.api.registry
import com.gewuyou.forgeboot.security.core.authenticate.entities.request.LoginRequest
/**
* 登录请求类型注册表
*
* 用于管理不同登录类型的请求类映射关系支持动态注册和获取登录请求类型
*
* ```kt
* @Bean
* fun loginRequestTypeRegistry(): LoginRequestTypeRegistry {
* return LoginRequestTypeRegistry().apply {
* register("default", DefaultLoginRequest::class.java)
* register("sms", SmsLoginRequest::class.java)
* }
* }
* ```
* @since 2025-06-10 16:33:43
* @author gewuyou
*/
class LoginRequestTypeRegistry {
/**
* 内部使用可变Map保存登录类型与对应LoginRequest子类的映射关系
*/
private val mapping = mutableMapOf<String, Class<out LoginRequest>>()
/**
* 注册指定登录类型对应的请求类
*
* @param loginType 登录类型的标识字符串
* @param clazz 继承自LoginRequest的具体请求类
*/
fun register(loginType: String, clazz: Class<out LoginRequest>) {
mapping[loginType] = clazz
}
/**
* 根据登录类型获取对应的请求类
*
* @param loginType 登录类型的标识字符串
* @return 返回对应的LoginRequest子类若未找到则返回null
*/
fun getTypeForLoginType(loginType: String): Class<out LoginRequest>? {
return mapping[loginType]
}
}

View File

@ -0,0 +1,31 @@
package com.gewuyou.forgeboot.security.authenticate.api.resolver
import com.gewuyou.forgeboot.security.core.authenticate.entities.request.LoginRequest
import jakarta.servlet.http.HttpServletRequest
/**
* 登录请求解析器接口
* 用于定义处理登录请求的通用规范包括内容类型支持判断和请求参数解析功能
*
* @since 2025-06-10 16:32:32
* @author gewuyou
*/
interface LoginRequestResolver {
/**
* 判断当前解析器是否支持指定的内容类型
*
* @param contentType 请求内容类型
* @return Boolean 返回true表示支持该内容类型否则不支持
*/
fun supports(contentType: String): Boolean
/**
* 解析HTTP请求为具体的登录请求对象
* 该方法应从HttpServletRequest中提取并封装登录所需参数
*
* @param request HTTP请求对象
* @return LoginRequest 解析后的登录请求实体对象
*/
fun resolve(request: HttpServletRequest): LoginRequest
}

View File

@ -0,0 +1,31 @@
package com.gewuyou.forgeboot.security.authenticate.api.service
import org.springframework.security.core.userdetails.UserDetails
/**
*用户缓存服务
*
* @since 2025-02-15 17:15:17
* @author gewuyou
*/
interface UserCacheService {
/**
* 从缓存中获取用户信息
* @param principal 用户标识
* @return 用户信息 返回null表示缓存中没有该用户信息或信息已过期
*/
fun getUserFromCache(principal: String): UserDetails?
/**
* 将用户信息缓存到缓存中 注意请将过期时间设置得尽可能短以防止缓存与数据库出现数据不一致
* @param userDetails 用户信息
*/
fun putUserToCache(userDetails: UserDetails)
/**
* 从缓存中移除用户信息
* @param principal 用户标识
*/
fun removeUserFromCache(principal: String)
}

View File

@ -0,0 +1,19 @@
package com.gewuyou.forgeboot.security.authenticate.api.service
import org.springframework.security.core.userdetails.UserDetails
/**
* 用户详情服务
*
* @author gewuyou
* @since 2024-11-05 19:34:50
*/
fun interface UserDetailsService {
/**
* 根据用户身份标识获取用户
* @param principal 用户身份标识 通常为用户名 手机号 邮箱等
* @return 用户详情 不能为空
*/
fun loadUserByPrincipal(principal: Any): UserDetails
}

View File

@ -0,0 +1,11 @@
package com.gewuyou.forgeboot.security.authenticate.api.strategy
import org.springframework.security.web.authentication.AuthenticationFailureHandler
/**
* 身份验证失败策略接口用于定义不同登录类型的身份验证失败处理机制
*
* @since 2025-06-10 23:43:43
* @author gewuyou
*/
interface AuthenticationFailureStrategy : AuthenticationHandlerSupportStrategy, AuthenticationFailureHandler

View File

@ -0,0 +1,16 @@
package com.gewuyou.forgeboot.security.authenticate.api.strategy
/**
*身份验证处理程序支持策略
*
* @since 2025-06-11 00:03:28
* @author gewuyou
*/
fun interface AuthenticationHandlerSupportStrategy{
/**
* 获取当前策略支持的登录类型标识符
*
* @return 返回当前策略支持的登录类型字符串
*/
fun supportedLoginType(): String
}

View File

@ -0,0 +1,11 @@
package com.gewuyou.forgeboot.security.authenticate.api.strategy
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
/**
* 身份验证成功策略接口用于定义不同登录类型的成功处理策略
*
* @since 2025-06-10 23:42:54
* @author gewuyou
*/
interface AuthenticationSuccessStrategy : AuthenticationHandlerSupportStrategy, AuthenticationSuccessHandler

View File

@ -0,0 +1,26 @@
package com.gewuyou.forgeboot.security.authenticate.api.strategy
import com.gewuyou.forgeboot.security.core.authenticate.entities.request.LoginRequest
import org.springframework.security.core.Authentication
/**
*登录请求转换器策略
* 请注意该接口仅用于定义转换器的接口只负责转换登录请求为认证对象不负责提供认证逻辑
* 具体的认证逻辑由具体的认证提供类实现因此当需要自定义认证逻辑时请实现具体的认证提供类
* @since 2025-02-15 03:23:34
* @author gewuyou
*/
interface LoginRequestConverterStrategy {
/**
* 转换登录请求为认证对象
* @param loginRequest 登录请求
* @return 认证对象
*/
fun convert(loginRequest: LoginRequest): Authentication
/**
* 获取支持的登录类型
* @return 请求的登录类型
*/
fun getSupportedLoginType(): String
}

View File

@ -0,0 +1,13 @@
package com.gewuyou.forgeboot.security.authenticate.api.strategy
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler
/**
* 注销成功策略接口用于定义在用户注销成功后的处理逻辑
*
* 实现此类的策略应当提供具体地登出后操作例如清理会话记录日志等
*
* @since 2025-06-10 23:49:35
* @author gewuyou
*/
interface LogoutSuccessStrategy : AuthenticationHandlerSupportStrategy, LogoutSuccessHandler

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 @@
plugins {
alias(libs.plugins.kotlin.plugin.spring)
}
dependencies {
compileOnly(libs.springBootStarter.security)
compileOnly(libs.springBootStarter.web)
compileOnly(project(Modules.Security.Authenticate.IMPL))
implementation(project(Modules.Security.CORE))
}

View File

@ -0,0 +1,309 @@
package com.gewuyou.forgeboot.security.authenticate.autoconfigure
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.security.authenticate.api.config.SecurityAuthenticateProperties
import com.gewuyou.forgeboot.security.authenticate.api.customizer.LoginFilterCustomizer
import com.gewuyou.forgeboot.security.authenticate.api.resolver.LoginRequestResolver
import com.gewuyou.forgeboot.security.authenticate.api.strategy.AuthenticationFailureStrategy
import com.gewuyou.forgeboot.security.authenticate.api.strategy.AuthenticationSuccessStrategy
import com.gewuyou.forgeboot.security.authenticate.api.strategy.LogoutSuccessStrategy
import com.gewuyou.forgeboot.security.core.common.extension.cleanUnNeedConfig
import com.gewuyou.forgeboot.security.authenticate.impl.filter.UnifiedAuthenticationFilter
import com.gewuyou.forgeboot.security.authenticate.impl.handler.AuthenticationExceptionHandler
import com.gewuyou.forgeboot.security.authenticate.impl.handler.CompositeAuthenticationFailureHandler
import com.gewuyou.forgeboot.security.authenticate.impl.handler.CompositeAuthenticationSuccessHandler
import com.gewuyou.forgeboot.security.authenticate.impl.handler.CompositeLogoutSuccessHandler
import com.gewuyou.forgeboot.security.authenticate.impl.resolver.CompositeLoginRequestResolver
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.AuthenticationFailureHandlerContext
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.AuthenticationSuccessHandlerContext
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.LoginRequestConverterContext
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.LogoutSuccessHandlerContext
import org.springframework.beans.factory.ObjectProvider
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.ProviderManager
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
/**
* 安全身份验证自动配置
*
* 主要用于配置与安全认证相关的 Bean Spring Security 的过滤器链
*
* @since 2025-06-11 15:04:58
* @author gewuyou
*/
@Configuration
@EnableConfigurationProperties(SecurityAuthenticateProperties::class)
class SecurityAuthenticateAutoConfiguration {
/**
* Spring Security 认证流程的自动配置类
*
* 该内部类负责构建主要的安全过滤器链处理登录登出以及异常入口点等核心功能
*
* @property logoutSuccessHandler 处理登出成功的处理器
* @property authenticationExceptionHandler 处理认证异常的入口点
* @property unifiedAuthenticationFilter 统一的身份验证过滤器
* @property customizers 可选的登录过滤器定制器集合
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SecurityFilterChain::class)
class SpringSecurityAuthenticateAutoConfiguration(
private val logoutSuccessHandler: CompositeLogoutSuccessHandler,
private val authenticationExceptionHandler: AuthenticationEntryPoint,
private val unifiedAuthenticationFilter: UnifiedAuthenticationFilter,
private val customizers: ObjectProvider<List<LoginFilterCustomizer>>,
) {
/**
* 构建并返回一个自定义的安全过滤器链
*
* 该方法配置了
* - 清除不必要地默认安全配置
* - 登出功能及其成功处理器
* - 异常处理认证失败入口点
* - 请求匹配规则和访问控制策略
* - 添加统一的身份验证过滤器到过滤器链中
* - 对可排序的登录过滤器定制器进行排序并应用
*
* @param http HttpSecurity 实例用于构建安全过滤器链
* @param properties 安全认证属性配置对象
* @return 构建完成的 SecurityFilterChain 实例
*/
@Bean(name = ["forgebootSecurityAuthenticationChain"])
fun loginFilterChain(
http: HttpSecurity,
properties: SecurityAuthenticateProperties,
): SecurityFilterChain {
return http
.cleanUnNeedConfig()
.logout {
it.logoutUrl(properties.logoutUrl)
.logoutSuccessHandler(logoutSuccessHandler)
}
.exceptionHandling {
it.authenticationEntryPoint(authenticationExceptionHandler)
}
.securityMatcher(AntPathRequestMatcher(properties.baseUrl))
.addFilterBefore(unifiedAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
.also {
customizers.ifAvailable?.sortedBy { c -> (c as? OrderedLoginFilterCustomizer)?.order ?: 0 }
?.forEach { it.customize(http) }
}
.build()
}
}
/**
* ForgeBoot安全组件的自动配置类
*
* 负责注册多个用于处理身份验证流程的组合式组件如解析器上下文处理器等
*/
@Configuration(proxyBeanMethods = false)
class ForgeBootSecurityAuthenticateAutoConfiguration {
/**
* 创建并返回一个组合式的登录请求解析器
*
* 将多个 LoginRequestResolver 实现组合成一个统一的接口调用入口
*
* @param resolvers 所有可用的 LoginRequestResolver 实例列表
* @return 组合后的 CompositeLoginRequestResolver 实例
*/
@Bean
fun compositeLoginRequestResolver(
resolvers: List<LoginRequestResolver>,
) = CompositeLoginRequestResolver(resolvers)
/**
* 创建并返回一个登出成功处理的上下文实例
*
* 用于根据注册的策略动态选择合适的 LogoutSuccessStrategy
*
* @param strategies 所有可用的 LogoutSuccessStrategy 实例列表
* @return 初始化好的 LogoutSuccessHandlerContext 实例
*/
@Bean
fun logoutSuccessHandlerContext(
strategies: List<LogoutSuccessStrategy>,
) = LogoutSuccessHandlerContext(strategies)
/**
* 创建并返回一个组合式的登出成功处理器
*
* 使用解析器和上下文来决定最终使用的登出成功策略
*
* @param resolver 登录请求解析器
* @param context 登出成功处理上下文
* @return 初始化好的 CompositeLogoutSuccessHandler 实例
*/
@Bean(name = ["logoutSuccessHandler"])
fun compositeLogoutSuccessHandler(
resolver: CompositeLoginRequestResolver,
context: LogoutSuccessHandlerContext,
) = CompositeLogoutSuccessHandler(resolver, context)
/**
* 创建并返回一个认证异常处理器
*
* 当用户未认证或认证失败时通过此处理器响应客户端
*
* @param objectMapper JSON 序列化工具
* @param properties 安全认证属性配置
* @return 初始化好的 AuthenticationExceptionHandler 实例
*/
@Bean
fun authenticationExceptionHandler(
objectMapper: ObjectMapper,
properties: SecurityAuthenticateProperties,
): AuthenticationEntryPoint = AuthenticationExceptionHandler(objectMapper, properties)
/**
* 创建并返回一个认证管理器
*
* 使用一组 AuthenticationProvider 来支持多种认证方式
*
* @param authenticationProviders 所有可用的认证提供者
* @return 初始化好的 ProviderManager 实例
*/
@Bean(name = ["authenticationManager"])
@Primary
fun authenticationManager(
authenticationProviders: List<AuthenticationProvider>,
): AuthenticationManager = ProviderManager(authenticationProviders)
/**
* 创建并返回一个认证成功处理的上下文
*
* 根据当前请求上下文动态选择合适的认证成功策略
*
* @param strategies 所有可用的 AuthenticationSuccessStrategy 实例列表
* @return 初始化好的 AuthenticationSuccessHandlerContext 实例
*/
@Bean
fun authenticationSuccessHandlerContext(
strategies: List<AuthenticationSuccessStrategy>,
) = AuthenticationSuccessHandlerContext(strategies)
/**
* 创建并返回一个组合式的认证成功处理器
*
* 委托给具体的策略实现来进行响应
*
* @param resolver 登录请求解析器
* @param context 认证成功处理上下文
* @return 初始化好的 CompositeAuthenticationSuccessHandler 实例
*/
@Bean(name = ["authenticationSuccessHandler"])
fun authenticationSuccessHandler(
resolver: CompositeLoginRequestResolver,
context: AuthenticationSuccessHandlerContext,
): AuthenticationSuccessHandler = CompositeAuthenticationSuccessHandler(resolver, context)
/**
* 创建并返回一个认证失败处理的上下文
*
* 根据当前请求上下文动态选择合适的认证失败策略
*
* @param strategies 所有可用的 AuthenticationFailureStrategy 实例列表
* @return 初始化好的 AuthenticationFailureHandlerContext 实例
*/
@Bean
fun authenticationFailureHandlerContext(
strategies: List<AuthenticationFailureStrategy>,
) = AuthenticationFailureHandlerContext(strategies)
/**
* 创建并返回一个组合式的认证失败处理器
*
* 委托给具体的策略实现来进行响应
*
* @param resolver 登录请求解析器
* @param context 认证失败处理上下文
* @return 初始化好的 CompositeAuthenticationFailureHandler 实例
*/
@Bean(name = ["authenticationFailureHandler"])
fun authenticationFailureHandler(
resolver: CompositeLoginRequestResolver,
context: AuthenticationFailureHandlerContext,
): AuthenticationFailureHandler = CompositeAuthenticationFailureHandler(resolver, context)
/**
* 创建并返回一个登录请求转换上下文
*
* 用于根据当前请求上下文动态选择合适的登录请求转换逻辑
*
* @param applicationContext Spring 应用上下文
* @return 初始化好的 LoginRequestConverterContext 实例
*/
@Bean
fun loginRequestConverterContext(
applicationContext: ApplicationContext,
) = LoginRequestConverterContext(applicationContext)
/**
* 创建并返回一个统一的身份验证过滤器
*
* 该过滤器是整个认证流程的核心处理登录请求并触发相应的成功/失败处理器
*
* @param properties 安全认证属性配置
* @param authenticationManager 认证管理器
* @param authenticationSuccessHandler 成功认证处理器
* @param authenticationFailureHandler 失败认证处理器
* @param compositeLoginRequestResolver 组合登录请求解析器
* @param loginRequestConverterContext 登录请求转换上下文
* @return 初始化好的 UnifiedAuthenticationFilter 实例
*/
@Bean
fun unifiedAuthenticationFilter(
properties: SecurityAuthenticateProperties,
authenticationManager: AuthenticationManager,
authenticationSuccessHandler: AuthenticationSuccessHandler,
authenticationFailureHandler: AuthenticationFailureHandler,
compositeLoginRequestResolver: CompositeLoginRequestResolver,
loginRequestConverterContext: LoginRequestConverterContext,
): UnifiedAuthenticationFilter =
UnifiedAuthenticationFilter(
AntPathRequestMatcher(
properties.baseUrl + properties.loginUrl,
properties.loginHttpMethod
),
authenticationManager,
authenticationSuccessHandler,
authenticationFailureHandler,
compositeLoginRequestResolver,
loginRequestConverterContext
)
}
@Configuration(proxyBeanMethods = false)
@ComponentScan(
basePackages = [
"com.gewuyou.forgeboot.security.authenticate.impl.strategy.impl",
"com.gewuyou.forgeboot.security.authenticate.impl.provider.impl",
]
)
class ForgeBootDefaultSecurityAuthenticateAutoConfiguration
}
/**
* 用于支持登录过滤器扩展器排序
*
* 通过 order 属性为 LoginFilterCustomizer 提供排序能力确保其按预期顺序执行
*/
interface OrderedLoginFilterCustomizer : LoginFilterCustomizer {
val order: Int
}

View File

@ -0,0 +1 @@
com.gewuyou.forgeboot.security.authenticate.autoconfigure.SecurityAuthenticateAutoConfiguration

View File

@ -0,0 +1,6 @@
extra{
setProperty(ProjectFlags.IS_ROOT_MODULE,true)
}
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,11 @@
plugins{
alias(libs.plugins.kotlin.plugin.spring)
}
dependencies {
api(project(Modules.Security.Authenticate.API))
implementation(project(Modules.Security.CORE))
implementation(project(Modules.Core.EXTENSION))
implementation(project(Modules.Webmvc.DTO))
compileOnly(libs.springBootStarter.web)
compileOnly(libs.springBootStarter.security)
}

View File

@ -0,0 +1,49 @@
package com.gewuyou.forgeboot.security.authenticate.impl.constants
/**
* Forge Boot安全身份验证响应信息
*
* 该对象包含用于身份验证过程中的各种预定义错误响应消息
* 每个属性代表一种特定的身份验证失败或系统错误情况供在认证流程中抛出或返回给调用者
*
* @since 2025-06-11 17:05:09
* @author gewuyou
*/
object ForgeBootSecurityAuthenticationResponseInformation {
const val INCORRECT_USERNAME_OR_PASSWORD: String = "the username or password is incorrect"
/**
* 当用户未提供密码时使用的响应消息
*/
const val PASSWORD_NOT_PROVIDED: String = "the password cannot be empty"
/**
* 当用户的凭证已过期时使用的响应消息
*/
const val ACCOUNT_CREDENTIALS_HAVE_EXPIRED: String = "the account credentials have expired"
/**
* 当用户账户被禁用时使用的响应消息
*/
const val ACCOUNT_IS_DISABLED: String = "the account is disabled"
/**
* 当用户账户被锁定时使用的响应消息
*/
const val ACCOUNT_IS_LOCKED: String = "the account is locked"
/**
* 当发生内部服务器错误时使用的通用错误响应消息
*/
const val INTERNAL_SERVER_ERROR: String = "internal server error occurred"
/**
* 当认证主体principal未提供时使用的常量字符串
* 这通常意味着认证请求缺少必要的身份标识信息
*/
const val PRINCIPAL_NOT_PROVIDED = "the authentication principal cannot be empty"
const val LOGIN_SUCCESS: String = "login success"
const val LOGOUT_SUCCESS: String = "logout success"
}

View File

@ -0,0 +1,69 @@
package com.gewuyou.forgeboot.security.authenticate.impl.filter
import com.gewuyou.forgeboot.security.authenticate.impl.resolver.CompositeLoginRequestResolver
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.LoginRequestConverterContext
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.core.Authentication
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
/**
* 统一的身份验证过滤器
*
* 该类实现了身份验证流程的核心逻辑通过组合不同的解析策略和转换上下文
* 来支持多种登录请求的处理方式
*
* @param pathRequestMatcher 请求路径匹配器用于判断当前请求是否需要处理
* @param authenticationManager 身份验证管理器用于执行实际的身份验证操作
* @param authenticationSuccessHandler 认证成功处理器用于处理认证成功后的响应
* @param authenticationFailureHandler 认证失败处理器用于处理认证失败后的响应
* @param compositeLoginRequestResolver 组合登录请求解析器用于解析不同类型的登录请求
* @param loginRequestConverterContext 登录请求转换上下文用于选择合适地转换策略
*
* @since 2025-06-10 20:28:08
* @author gewuyou
*/
class UnifiedAuthenticationFilter(
pathRequestMatcher: AntPathRequestMatcher,
authenticationManager: AuthenticationManager,
authenticationSuccessHandler: AuthenticationSuccessHandler,
authenticationFailureHandler: AuthenticationFailureHandler,
private val compositeLoginRequestResolver: CompositeLoginRequestResolver,
private val loginRequestConverterContext: LoginRequestConverterContext,
) : AbstractAuthenticationProcessingFilter(pathRequestMatcher) {
init {
// 初始化身份验证管理器、成功处理器和失败处理器
setAuthenticationManager(authenticationManager)
setAuthenticationSuccessHandler(authenticationSuccessHandler)
setAuthenticationFailureHandler(authenticationFailureHandler)
}
/**
* 尝试进行身份验证
*
* 此方法负责从请求中提取登录信息并使用组合解析器和转换上下文来构建认证请求
* 最终调用身份验证管理器执行认证操作
*
* @param request HTTP请求对象
* @param response HTTP响应对象
* @return 返回经过认证的Authentication对象
*/
override fun attemptAuthentication(
request: HttpServletRequest,
response: HttpServletResponse,
): Authentication {
// 尝试认证:通过组合解析器解析请求内容,再根据转换上下文选择合适的策略进行认证
return authenticationManager.authenticate(
// 通过上下文执行转换策略
loginRequestConverterContext.executeStrategy(
// 使用组合解析器解析HTTP请求
compositeLoginRequestResolver.resolve(request)
)
)
}
}

View File

@ -0,0 +1,37 @@
package com.gewuyou.forgeboot.security.authenticate.impl.handler
import com.gewuyou.forgeboot.security.authenticate.api.handler.DelegatingHandler
import com.gewuyou.forgeboot.security.authenticate.impl.resolver.CompositeLoginRequestResolver
import jakarta.servlet.http.HttpServletRequest
/**
* 抽象委派处理程序
*
* 该类为泛型抽象类用于实现委派处理逻辑通过解析请求获取登录类型并使用上下文解析器获取对应的处理上下文
*
* @param resolver 用于解析请求并获取登录类型的组件不可为空
* @param contextResolver 根据登录类型生成对应上下文对象的函数式接口不可为空
*
* @since 2025-06-11 14:53:46
* @author gewuyou
*/
abstract class AbstractDelegatingHandler<T : Any>(
private val resolver: CompositeLoginRequestResolver,
private val contextResolver: (String) -> T,
): DelegatingHandler<T> {
/**
* 解析请求并获取对应的处理上下文
*
* 该方法会调用注入的 [resolver] 来解析传入的 HTTP 请求获取当前请求的登录类型
* 然后通过 [contextResolver] 函数将登录类型转换为具体地处理上下文对象
*
* @param request HTTP 请求对象用于解析登录类型
* @return 返回与登录类型对应的处理上下文对象
*/
override fun resolveDelegate(request: HttpServletRequest): T {
// 解析请求获取登录类型
val loginType = resolver.resolve(request).getType()
// 使用上下文解析器生成对应类型的处理上下文
return contextResolver(loginType)
}
}

View File

@ -0,0 +1,37 @@
package com.gewuyou.forgeboot.security.authenticate.impl.handler
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.security.authenticate.api.config.SecurityAuthenticateProperties
import com.gewuyou.forgeboot.webmvc.dto.R
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.AuthenticationEntryPoint
/**
*身份验证异常处理程序
*
* @since 2025-01-03 14:50:46
* @author gewuyou
*/
class AuthenticationExceptionHandler(
private val objectMapper: ObjectMapper,
private val securityAuthenticateProperties: SecurityAuthenticateProperties
) : AuthenticationEntryPoint {
override fun commence(
request: HttpServletRequest,
response: HttpServletResponse,
authException: AuthenticationException
) {
log.error("身份验证异常: ", authException)
response.status = HttpStatus.OK.value()
response.contentType = MediaType.APPLICATION_JSON_VALUE
val writer = response.writer
writer.print(objectMapper.writeValueAsString(R.failure<String>(HttpStatus.UNAUTHORIZED.value(), authException.message?:securityAuthenticateProperties.defaultExceptionResponse)))
writer.flush()
writer.close()
}
}

View File

@ -0,0 +1,43 @@
package com.gewuyou.forgeboot.security.authenticate.impl.handler
import com.gewuyou.forgeboot.security.authenticate.impl.resolver.CompositeLoginRequestResolver
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.AuthenticationFailureHandlerContext
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.web.authentication.AuthenticationFailureHandler
/**
* 组合身份验证故障处理程序
*
* 该类通过组合不同的身份验证失败处理器来处理认证失败场景利用解析器和上下文策略来动态选择实际的处理器
*
* @property resolver 用于解析请求并确定适用的身份验证失败处理器的组件
* @property context 提供身份验证失败处理策略的上下文对象
*
* @since 2025-06-11 00:11:52
* @author gewuyou
*/
class CompositeAuthenticationFailureHandler(
private val resolver: CompositeLoginRequestResolver,
private val context: AuthenticationFailureHandlerContext,
) : AbstractDelegatingHandler<AuthenticationFailureHandler>(
resolver, context::resolve
), AuthenticationFailureHandler {
/**
* 在身份验证失败时调用的方法
*
* 此方法会解析出适用于当前请求的具体身份验证失败处理器并委托其进行失败处理
*
* @param request 来自客户端的 HTTP 请求
* @param response 发送回客户端的 HTTP 响应
* @param exception 身份验证过程中抛出的异常信息
*/
override fun onAuthenticationFailure(
request: HttpServletRequest,
response: HttpServletResponse,
exception: org.springframework.security.core.AuthenticationException,
) {
resolveDelegate(request).onAuthenticationFailure(request, response, exception)
}
}

View File

@ -0,0 +1,45 @@
package com.gewuyou.forgeboot.security.authenticate.impl.handler
import com.gewuyou.forgeboot.security.authenticate.impl.resolver.CompositeLoginRequestResolver
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.AuthenticationSuccessHandlerContext
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.core.Authentication
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
/**
* 统一身份验证成功处理程序
*
* 该类实现了 [AuthenticationSuccessHandler] 接口用于在身份验证成功后执行相应的处理逻辑
* 它通过组合多个具体的处理器来实现灵活的身份验证成功响应策略
*
* @property resolver 用于解析登录请求并确定使用哪个具体的成功处理器
* @property context 提供处理上下文信息用于解析和执行成功处理逻辑
*
* @since 2025-06-10 23:48:32
* @author gewuyou
*/
class CompositeAuthenticationSuccessHandler(
private val resolver: CompositeLoginRequestResolver,
private val context: AuthenticationSuccessHandlerContext,
) : AbstractDelegatingHandler<AuthenticationSuccessHandler>(
resolver, context::resolve
), AuthenticationSuccessHandler {
/**
* 身份验证成功时的回调方法
*
* 此方法会在用户成功通过身份验证后被调用它使用 [resolveDelegate] 方法根据当前请求解析出
* 合适的具体处理器并委托给该处理器执行后续操作
*
* @param request HTTP 请求对象提供客户端的请求信息
* @param response HTTP 响应对象用于向客户端发送响应
* @param authentication 包含身份验证成功的用户信息和权限等数据的对象
*/
override fun onAuthenticationSuccess(
request: HttpServletRequest,
response: HttpServletResponse,
authentication: Authentication,
) {
resolveDelegate(request).onAuthenticationSuccess(request, response, authentication)
}
}

Some files were not shown because too many files have changed in this diff Show More