refactor(webmvc): 重构 Web 项目通用 CRUD 接口规范模块

- 将 `forgeboot-webmvc-spec` 模块拆分为 `spec-core` 和 `spec-jpa` 两个子模块- 重新组织代码结构,提高模块化和可维护性
- 更新包名和类名,使其更加清晰和一致
- 移除冗余代码,优化接口定义
This commit is contained in:
gewuyou 2025-07-24 22:14:21 +08:00
parent 61e5ea3130
commit 1432b2380b
42 changed files with 323 additions and 289 deletions

View File

@ -29,6 +29,10 @@ object Modules {
const val VALIDATION = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-validation" 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 VERSION = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-version-spring-boot-starter"
const val LOGGER = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-logger-spring-boot-starter" const val LOGGER = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-logger-spring-boot-starter"
object Spec{
const val CORE = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-spec-core"
const val JPA = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-spec-jpa"
}
} }
/** /**

View File

@ -7,8 +7,6 @@ dependencies {
implementation(libs.kotlinReflect) implementation(libs.kotlinReflect)
compileOnly(libs.springBootDependencies.bom) compileOnly(libs.springBootDependencies.bom)
compileOnly(libs.jackson.annotations) compileOnly(libs.jackson.annotations)
compileOnly(libs.springBootStarter.jpa)
compileOnly(libs.springBootStarter.validation)
} }
i18nKeyGen { i18nKeyGen {
rootPackage.set("com.gewuyou.forgeboot.webmvc.dto.i18n") rootPackage.set("com.gewuyou.forgeboot.webmvc.dto.i18n")

View File

@ -1,38 +0,0 @@
package com.gewuyou.forgeboot.webmvc.dto.page
import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.Predicate
import jakarta.persistence.criteria.Root
/**
* 可过滤接口用于支持泛型类型的过滤操作
*
* 该接口定义了过滤功能的基本契约允许实现类根据给定的过滤条件构建查询规范
* 主要用于与 JPA Criteria API 集成以构建动态查询条件
*
* @param <FilterType> 过滤条件的类型通常是一个包含过滤参数的数据传输对象DTO
* @param <EntityType> 被查询的实体类型通常对应数据库中的某个实体类
*
* @since 2025-07-19 08:56:56
* @author gewuyou
*/
interface Filterable<FilterType, EntityType> : QueryComponent {
/**
* 获取当前的过滤条件对象
*
* @return 返回一个可空的 FilterType 实例表示当前的过滤条件
*/
val filter: FilterType?
/**
* 构建查询条件的谓词列表
*
* 此方法根据当前的过滤条件使用给定的 CriteriaBuilder 创建一组 Predicate 对象
* 这些谓词可以用于构建最终查询语句
*
* @param root 代表查询的根实体用于访问实体的属性
* @param cb CriteriaBuilder 实例用于创建查询条件
* @return 返回一个包含查询条件的 Predicate 列表
*/
fun buildSpecification(root: Root<EntityType>, cb: CriteriaBuilder): List<Predicate>
}

View File

@ -1,28 +0,0 @@
package com.gewuyou.forgeboot.webmvc.dto.request
import com.gewuyou.forgeboot.webmvc.dto.entities.SortCondition
import com.gewuyou.forgeboot.webmvc.dto.page.DateRangeFilterable
import com.gewuyou.forgeboot.webmvc.dto.page.Filterable
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
import com.gewuyou.forgeboot.webmvc.dto.page.Sortable
/**
* 抽象排序日期过滤分页请求基类
*
* 该抽象类为分页请求提供了基础结构支持以下功能
* - 分页控制通过Pageable接口
* - 排序功能通过Sortable接口
* - 日期范围过滤通过DateRangeFilterable接口
* - 通用数据过滤通过Filterable<T,E>接口
*
* @property currentPage 当前页码从1开始计数默认值为1
* @property pageSize 每页记录数默认值为10
*
* @since 2025-07-19 14:50:36
* @author gewuyou
*/
abstract class AbstractSortedDateFilterPageRequest<T,E>(
override val currentPage: Int = 1,
override val pageSize: Int = 10,
override val sortConditions: List<SortCondition> = mutableListOf()
) : Pageable, Sortable, DateRangeFilterable, Filterable<T,E>

View File

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

View File

@ -1,4 +1,4 @@
package com.gewuyou.forgeboot.webmvc.dto.entities package com.gewuyou.webmvc.spec.core.entities
import java.time.Instant import java.time.Instant

View File

@ -1,6 +1,6 @@
package com.gewuyou.forgeboot.webmvc.dto.entities package com.gewuyou.webmvc.spec.core.entities
import org.hibernate.query.SortDirection import com.gewuyou.webmvc.spec.core.enums.SortDirection
/** /**
* 排序条件 * 排序条件
@ -17,5 +17,5 @@ class SortCondition {
/** /**
* 排序方向 * 排序方向
*/ */
var direction: SortDirection = SortDirection.DESCENDING var direction: SortDirection = SortDirection.DESC
} }

View File

@ -0,0 +1,64 @@
package com.gewuyou.webmvc.spec.core.enums
import java.util.Locale
/**
* 排序方向枚举
*
* @author gewuyou
* @since 2025-01-16 17:56:01
*/
enum class SortDirection {
ASC, DESC;
/**
* 获取当前排序方向的反方向
*
* @return 返回与当前方向相反的排序方向枚举值
* - 当前为ASC时返回DESC
* - 当前为DESC时返回ASC
*/
fun reverse(): SortDirection = when (this) {
ASC -> DESC
DESC -> ASC
}
companion object {
/**
* 将字符串解析为排序方向枚举值忽略大小写
*
* 支持的输入包括
* - "asc", "ascending" ASC
* - "desc", "descending" DESC
*
* @param value 待解析的字符串可为空
* @return 解析成功返回对应的枚举值value为空时返回null无法解析时抛出异常
* @throws IllegalArgumentException 当输入字符串无法识别为有效的排序方向时
*/
@JvmStatic
fun interpret(value: String?): SortDirection? {
if (value == null) return null
return when (value.lowercase(Locale.ROOT)) {
"asc", "ascending" -> ASC
"desc", "descending" -> DESC
else -> throw IllegalArgumentException("Unknown sort direction: $value")
}
}
/**
* 安全地将字符串解析为排序方向枚举值如果解析失败则返回默认值
*
* @param value 待解析的字符串可为空
* @param default 解析失败时返回的默认值默认为ASC
* @return 解析成功返回对应的枚举值解析失败或value为空时返回默认值
*/
fun interpretOrDefault(value: String?, default: SortDirection = ASC): SortDirection {
return try {
interpret(value) ?: default
} catch (_: IllegalArgumentException) {
default
}
}
}
}

View File

@ -1,6 +1,7 @@
package com.gewuyou.forgeboot.webmvc.dto.page package com.gewuyou.webmvc.spec.core.page
import com.gewuyou.webmvc.spec.core.entities.DateRangeCondition
import com.gewuyou.forgeboot.webmvc.dto.entities.DateRangeCondition
/** /**
* 日期范围可过滤接口 * 日期范围可过滤接口

View File

@ -0,0 +1,19 @@
package com.gewuyou.webmvc.spec.core.page
/**
* 可过滤接口用于支持泛型类型的过滤操作
*
*
* @param <FilterType> 过滤条件的类型通常是一个包含过滤参数的数据传输对象DTO
*
* @since 2025-07-19 08:56:56
* @author gewuyou
*/
interface Filterable<FilterType> : QueryComponent {
/**
* 获取当前的过滤条件对象
*
* @return 返回一个可空的 FilterType 实例表示当前的过滤条件
*/
val filter: FilterType?
}

View File

@ -1,4 +1,4 @@
package com.gewuyou.forgeboot.webmvc.dto.page package com.gewuyou.webmvc.spec.core.page
/** /**
* 关键字可搜索接口用于支持包含关键字搜索条件的数据结构 * 关键字可搜索接口用于支持包含关键字搜索条件的数据结构

View File

@ -1,4 +1,4 @@
package com.gewuyou.forgeboot.webmvc.dto.page package com.gewuyou.webmvc.spec.core.page
/** /**
* 可分页接口用于支持分页功能的类实现 * 可分页接口用于支持分页功能的类实现

View File

@ -1,4 +1,4 @@
package com.gewuyou.forgeboot.webmvc.dto.page package com.gewuyou.webmvc.spec.core.page
/** /**
* QueryComponent 是一个密封接口用于定义查询组件的公共契约 * QueryComponent 是一个密封接口用于定义查询组件的公共契约

View File

@ -1,7 +1,7 @@
package com.gewuyou.forgeboot.webmvc.dto.page package com.gewuyou.webmvc.spec.core.page
import com.gewuyou.forgeboot.webmvc.dto.entities.SortCondition import com.gewuyou.webmvc.spec.core.entities.SortCondition
import org.hibernate.query.SortDirection import com.gewuyou.webmvc.spec.core.enums.SortDirection
/** /**
* 可排序接口用于定义排序相关属性 * 可排序接口用于定义排序相关属性

View File

@ -0,0 +1,10 @@
package com.gewuyou.webmvc.spec.core.page
/**
* 启用状态可过滤接口用于支持启用/禁用状态的过滤功能
*
*
* @since 2025-07-19 09:00:42
* @author gewuyou
*/
interface StatusFilterable : QueryComponent

View File

@ -1,7 +1,8 @@
package com.gewuyou.forgeboot.webmvc.dto.request package com.gewuyou.webmvc.spec.core.request
import com.gewuyou.webmvc.spec.core.page.DateRangeFilterable
import com.gewuyou.webmvc.spec.core.page.Pageable
import com.gewuyou.forgeboot.webmvc.dto.page.DateRangeFilterable
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
/** /**
* 抽象日期分页请求 * 抽象日期分页请求

View File

@ -1,8 +1,9 @@
package com.gewuyou.forgeboot.webmvc.dto.request package com.gewuyou.webmvc.spec.core.request
import com.gewuyou.webmvc.spec.core.page.DateRangeFilterable
import com.gewuyou.webmvc.spec.core.page.Pageable
import com.gewuyou.webmvc.spec.core.page.StatusFilterable
import com.gewuyou.forgeboot.webmvc.dto.page.DateRangeFilterable
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
import com.gewuyou.forgeboot.webmvc.dto.page.StatusFilterable
/** /**
* 日期状态页面请求 * 日期状态页面请求

View File

@ -1,7 +1,8 @@
package com.gewuyou.forgeboot.webmvc.dto.request package com.gewuyou.webmvc.spec.core.request
import com.gewuyou.webmvc.spec.core.page.KeywordSearchable
import com.gewuyou.webmvc.spec.core.page.Pageable
import com.gewuyou.forgeboot.webmvc.dto.page.KeywordSearchable
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
/** /**
* 抽象搜索分页请求类 * 抽象搜索分页请求类

View File

@ -1,8 +1,9 @@
package com.gewuyou.forgeboot.webmvc.dto.request package com.gewuyou.webmvc.spec.core.request
import com.gewuyou.webmvc.spec.core.page.DateRangeFilterable
import com.gewuyou.webmvc.spec.core.page.Pageable
import com.gewuyou.webmvc.spec.core.page.Sortable
import com.gewuyou.forgeboot.webmvc.dto.page.DateRangeFilterable
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
import com.gewuyou.forgeboot.webmvc.dto.page.Sortable
/** /**
* 抽象排序日期分页请求 * 抽象排序日期分页请求

View File

@ -1,7 +1,8 @@
package com.gewuyou.forgeboot.webmvc.dto.request package com.gewuyou.webmvc.spec.core.request
import com.gewuyou.webmvc.spec.core.page.Pageable
import com.gewuyou.webmvc.spec.core.page.Sortable
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
import com.gewuyou.forgeboot.webmvc.dto.page.Sortable
/** /**
* 抽象排序分页请求数据类 * 抽象排序分页请求数据类

View File

@ -1,6 +1,7 @@
package com.gewuyou.forgeboot.webmvc.dto.request package com.gewuyou.webmvc.spec.core.request
import com.gewuyou.webmvc.spec.core.page.Pageable
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
/** /**
* 基本分页请求数据类 * 基本分页请求数据类

View File

@ -1,7 +1,4 @@
package com.gewuyou.forgeboot.webmvc.dto.request package com.gewuyou.webmvc.spec.core.request
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import jakarta.validation.constraints.NotEmpty
/** /**
* 根据id列表删除请求 * 根据id列表删除请求
@ -12,16 +9,13 @@ import jakarta.validation.constraints.NotEmpty
* @author gewuyou * @author gewuyou
* @since 2025-01-18 17:39:18 * @since 2025-01-18 17:39:18
*/ */
@JsonIgnoreProperties(ignoreUnknown = true)
open class DeleteByIdsReq<T>( open class DeleteByIdsReq<T>(
/** /**
* 待删除的实体id列表 * 待删除的实体id列表
* *
* 这个字段是删除操作的核心参数它不能为空以确保至少有一个id被指定用于删除 * 这个字段是删除操作的核心参数它不能为空以确保至少有一个id被指定用于删除
* 使用@NotNull注解来确保在序列化和反序列化过程中该字段不能为空
* *
* @param ids 实体的唯一标识符列表用于指定哪些实体应当被删除 * @param ids 实体的唯一标识符列表用于指定哪些实体应当被删除
*/ */
@field:NotEmpty(message = "The list of Ids requested to be removed cannot be empty!")
var ids: List<T>, var ids: List<T>,
) )

View File

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

40
forgeboot-webmvc/spec-jpa/.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

@ -1,6 +1,5 @@
dependencies { dependencies {
compileOnly(libs.springBootStarter.jpa)
api(project(Modules.Webmvc.DTO)) api(project(Modules.Webmvc.DTO))
api(project(Modules.Core.EXTENSION)) api(project(Modules.Webmvc.Spec.CORE))
compileOnly(libs.springBootStarter.jpa)
} }

View File

@ -1,22 +1,31 @@
package com.gewuyou.forgeboot.webmvc.dto.extension package com.gewuyou.webmvc.spec.jpa.extension
import com.gewuyou.forgeboot.webmvc.dto.page.*
import com.gewuyou.webmvc.spec.core.enums.SortDirection
import com.gewuyou.webmvc.spec.core.page.DateRangeFilterable
import com.gewuyou.webmvc.spec.core.page.KeywordSearchable
import com.gewuyou.webmvc.spec.core.page.Pageable
import com.gewuyou.webmvc.spec.core.page.QueryComponent
import com.gewuyou.webmvc.spec.core.page.Sortable
import com.gewuyou.webmvc.spec.jpa.page.JpaFilterable
import com.gewuyou.webmvc.spec.jpa.page.JpaStatusFilterable
import jakarta.persistence.criteria.CriteriaBuilder import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.Predicate import jakarta.persistence.criteria.Predicate
import jakarta.persistence.criteria.Root import jakarta.persistence.criteria.Root
import org.hibernate.query.SortDirection
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort import org.springframework.data.domain.Sort
import org.springframework.data.jpa.domain.Specification import org.springframework.data.jpa.domain.Specification
import java.time.Instant import java.time.Instant
/** /**
* Hibernate SortDirection 转换为 Spring Data Sort.Direction * 自定义的排序方向枚举转换为 Spring Data JPA 的排序方向
* *
* @return 返回对应的 Sort.Direction 实例 * @return 对应的 Spring Data Sort.Direction 枚举值
*/ */
fun SortDirection.toSpringDirection(): Sort.Direction = fun SortDirection.toSpringDirection(): Sort.Direction =
if (this == SortDirection.DESCENDING) Sort.Direction.DESC else Sort.Direction.ASC if (this == SortDirection.DESC) Sort.Direction.DESC else Sort.Direction.ASC
/** /**
* 将当前排序配置转换为可用于 Spring Data Pageable 的排序条件列表 * 将当前排序配置转换为可用于 Spring Data Pageable 的排序条件列表
@ -140,17 +149,17 @@ fun <T> QueryComponent.toSpecification(): Specification<T> {
(this as? DateRangeFilterable)?.let { (this as? DateRangeFilterable)?.let {
predicates += it.buildDateRangePredicates(root, cb) predicates += it.buildDateRangePredicates(root, cb)
} }
// 判断并组合 Status // 判断并组合 JpaStatusFilterable
(this as? StatusFilterable)?.let { (this as? JpaStatusFilterable)?.let {
predicates += it.buildStatusPredicates(root, cb) predicates += it.buildStatusPredicates(root, cb)
} }
// 判断并组合 KeywordSearchable // 判断并组合 KeywordSearchable
(this as? KeywordSearchable)?.let { (this as? KeywordSearchable)?.let {
predicates += it.buildKeywordPredicates(root, cb) predicates += it.buildKeywordPredicates(root, cb)
} }
// 判断并组合 Filterable // 判断并组合 JpaFilterable
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
(this as? Filterable<*, T>)?.let { (this as? JpaFilterable<*, T>)?.let {
predicates += it.buildSpecification(root, cb) predicates += it.buildSpecification(root, cb)
} }
// 组合所有查询条件,返回逻辑 AND 结果 // 组合所有查询条件,返回逻辑 AND 结果

View File

@ -1,4 +1,4 @@
package com.gewuyou.forgeboot.webmvc.dto.extension package com.gewuyou.webmvc.spec.jpa.extension
import com.gewuyou.forgeboot.webmvc.dto.PageResult import com.gewuyou.forgeboot.webmvc.dto.PageResult
import org.springframework.data.domain.Page import org.springframework.data.domain.Page

View File

@ -1,4 +1,4 @@
package com.gewuyou.forgeboot.webmvc.dto.extension package com.gewuyou.webmvc.spec.jpa.extension
import com.gewuyou.forgeboot.webmvc.dto.PageResult import com.gewuyou.forgeboot.webmvc.dto.PageResult

View File

@ -1,4 +1,4 @@
package com.gewuyou.forgeboot.webmvc.dto.extension package com.gewuyou.webmvc.spec.jpa.extension
import jakarta.persistence.criteria.Predicate import jakarta.persistence.criteria.Predicate

View File

@ -0,0 +1,26 @@
package com.gewuyou.webmvc.spec.jpa.page
import com.gewuyou.webmvc.spec.core.page.Filterable
import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.Predicate
import jakarta.persistence.criteria.Root
/**
*JPA可过滤接口
*
* @since 2025-07-24 21:40:33
* @author gewuyou
*/
interface JpaFilterable<FilterType, EntityType> : Filterable<FilterType> {
/**
* 构建JPA查询条件规范
*
* 根据当前过滤条件构建JPA Criteria查询所需的谓词列表用于动态生成查询条件
* 这些谓词将被用于构建最终的JPA查询语句
*
* @param root 查询根对象用于访问实体属性
* @param cb CriteriaBuilder对象用于创建查询条件谓词
* @return 包含查询条件谓词的列表
*/
fun buildSpecification(root: Root<EntityType>, cb: CriteriaBuilder): List<Predicate>
}

View File

@ -1,19 +1,19 @@
package com.gewuyou.forgeboot.webmvc.dto.page package com.gewuyou.webmvc.spec.jpa.page
import com.gewuyou.webmvc.spec.core.page.StatusFilterable
import jakarta.persistence.criteria.CriteriaBuilder import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.Predicate import jakarta.persistence.criteria.Predicate
import jakarta.persistence.criteria.Root import jakarta.persistence.criteria.Root
/** /**
* 启用状态可过滤接口用于支持启用/禁用状态的过滤功能 * 启用状态可过滤接口用于支持启用/禁用状态的过滤功能
* *
* 此接口定义了构建状态过滤条件的方法适用于基于 JPA Criteria API 的查询构建
* 实现该接口的类可通过 [buildStatusPredicates]方法提供具体过滤逻辑
* *
* @since 2025-07-19 09:00:42 * @since 2025-07-19 09:00:42
* @author gewuyou * @author gewuyou
*/ */
interface StatusFilterable : QueryComponent { interface JpaStatusFilterable : StatusFilterable{
/** /**
* 构建与启用状态相关的查询条件列表 * 构建与启用状态相关的查询条件列表
* *

View File

@ -1,12 +1,12 @@
package com.gewuyou.forgeboot.webmvc.spec.repository package com.gewuyou.webmvc.spec.jpa.repository
import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.JpaSpecificationExecutor import org.springframework.data.jpa.repository.JpaSpecificationExecutor
/** /**
*CRUD 存储库规范 * Jpa CRUD 存储库规范
* *
* @since 2025-05-29 20:39:11 * @since 2025-05-29 20:39:11
* @author gewuyou * @author gewuyou
*/ */
interface CrudRepositorySpec<Entity, Id>: JpaRepository<Entity, Id>, JpaSpecificationExecutor<Entity> interface JpaCrudRepositorySpec<Entity, Id>: JpaRepository<Entity, Id>, JpaSpecificationExecutor<Entity>

View File

@ -1,7 +1,9 @@
package com.gewuyou.forgeboot.webmvc.dto.request package com.gewuyou.webmvc.spec.jpa.request
import com.gewuyou.forgeboot.webmvc.dto.page.Filterable
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable import com.gewuyou.webmvc.spec.core.page.Pageable
import com.gewuyou.webmvc.spec.jpa.page.JpaFilterable
import org.hibernate.query.SortDirection
/** /**
@ -16,8 +18,9 @@ import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
* @since 2025-07-19 09:16:36 * @since 2025-07-19 09:16:36
* @author gewuyou * @author gewuyou
*/ */
abstract class AbstractFilteredPageRequest<T,E>( abstract class AbstractFilteredPageRequest<T, E>(
override val currentPage: Int = 1, override val currentPage: Int = 1,
override val pageSize: Int = 10, override val pageSize: Int = 10,
override val filter: T? = null, override val filter: T? = null,
) : Pageable, Filterable<T,E> sortDirection: SortDirection
) : Pageable, JpaFilterable<T, E>

View File

@ -1,6 +1,12 @@
package com.gewuyou.forgeboot.webmvc.dto.request package com.gewuyou.webmvc.spec.jpa.request
import com.gewuyou.forgeboot.webmvc.dto.page.*
import com.gewuyou.webmvc.spec.core.page.DateRangeFilterable
import com.gewuyou.webmvc.spec.core.page.KeywordSearchable
import com.gewuyou.webmvc.spec.core.page.Pageable
import com.gewuyou.webmvc.spec.core.page.Sortable
import com.gewuyou.webmvc.spec.core.page.StatusFilterable
import com.gewuyou.webmvc.spec.jpa.page.JpaFilterable
/** /**
* 抽象分页请求类用于封装分页查询的通用参数 * 抽象分页请求类用于封装分页查询的通用参数
@ -11,9 +17,6 @@ import com.gewuyou.forgeboot.webmvc.dto.page.*
* *
* @property currentPage 当前页码默认值为1 * @property currentPage 当前页码默认值为1
* @property pageSize 每页记录数默认值为10 * @property pageSize 每页记录数默认值为10
* @property sortBy 排序字段默认值为"createdAt"
* @property sortDirection 排序方向默认值为降序(SortDirection.DESCENDING)
* @property sortConditions 排序条件列表默认为空列表
* @property keyword 关键词搜索内容可为空 * @property keyword 关键词搜索内容可为空
* @property filter 自定义过滤条件可为空类型为泛型T * @property filter 自定义过滤条件可为空类型为泛型T
* *
@ -28,6 +31,6 @@ abstract class AbstractFullPageRequest<T,E>(
) : Pageable, ) : Pageable,
Sortable, Sortable,
KeywordSearchable, KeywordSearchable,
Filterable<T,E>, JpaFilterable<T, E>,
DateRangeFilterable, DateRangeFilterable,
StatusFilterable StatusFilterable

View File

@ -1,8 +1,9 @@
package com.gewuyou.forgeboot.webmvc.dto.request package com.gewuyou.webmvc.spec.jpa.request
import com.gewuyou.forgeboot.webmvc.dto.page.Filterable
import com.gewuyou.forgeboot.webmvc.dto.page.KeywordSearchable import com.gewuyou.webmvc.spec.core.page.KeywordSearchable
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable import com.gewuyou.webmvc.spec.core.page.Pageable
import com.gewuyou.webmvc.spec.jpa.page.JpaFilterable
/** /**
* 抽象搜索过滤分页请求 * 抽象搜索过滤分页请求
@ -18,9 +19,9 @@ import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
* @since 2025-07-19 10:22:41 * @since 2025-07-19 10:22:41
* @author gewuyou * @author gewuyou
*/ */
abstract class AbstractSearchFilterPageRequest<T,E>( abstract class AbstractSearchFilterPageRequest<T, E>(
override val currentPage: Int = 1, override val currentPage: Int = 1,
override val pageSize: Int = 10, override val pageSize: Int = 10,
override val keyword: String? = null, override val keyword: String? = null,
override val filter: T? = null, override val filter: T? = null,
) : Pageable, KeywordSearchable, Filterable<T,E> ) : Pageable, KeywordSearchable, JpaFilterable<T, E>

View File

@ -0,0 +1,29 @@
package com.gewuyou.webmvc.spec.jpa.request
import com.gewuyou.webmvc.spec.core.entities.SortCondition
import com.gewuyou.webmvc.spec.core.page.DateRangeFilterable
import com.gewuyou.webmvc.spec.core.page.Pageable
import com.gewuyou.webmvc.spec.core.page.Sortable
import com.gewuyou.webmvc.spec.jpa.page.JpaFilterable
/**
* 抽象排序日期过滤分页请求基类
*
* 该抽象类为分页请求提供了基础结构支持以下功能
* - 分页控制通过[Pageable]接口
* - 排序功能通过[Sortable]接口
* - 日期范围过滤通过[DateRangeFilterable]接口
* - 通用数据过滤通过[JpaFilterable]<T,E>接口
*
* @property currentPage 当前页码从1开始计数默认值为1
* @property pageSize 每页记录数默认值为10
*
* @since 2025-07-19 14:50:36
* @author gewuyou
*/
abstract class AbstractSortedDateFilterPageRequest<T, E>(
override val currentPage: Int = 1,
override val pageSize: Int = 10,
override val sortConditions: List<SortCondition> = mutableListOf(),
) : Pageable, Sortable, DateRangeFilterable, JpaFilterable<T, E>

View File

@ -1,10 +1,11 @@
package com.gewuyou.forgeboot.webmvc.spec.service package com.gewuyou.webmvc.spec.jpa.service
import com.gewuyou.forgeboot.webmvc.dto.PageResult import com.gewuyou.forgeboot.webmvc.dto.PageResult
import com.gewuyou.forgeboot.webmvc.dto.page.QueryComponent import com.gewuyou.webmvc.spec.core.page.QueryComponent
/** /**
* CRUD 服务规范 * Jpa CRUD 服务规范
* *
* 定义了对实体进行基本操作的服务接口包括增删查改和分页查询等功能 * 定义了对实体进行基本操作的服务接口包括增删查改和分页查询等功能
* *
@ -13,7 +14,7 @@ import com.gewuyou.forgeboot.webmvc.dto.page.QueryComponent
* @since 2025-05-29 20:18:22 * @since 2025-05-29 20:18:22
* @author gewuyou * @author gewuyou
*/ */
interface CrudServiceSpec<Entity: Any, Id: Any> { interface JpaCrudServiceSpec<Entity: Any, Id: Any> {
/** /**
* 根据ID获取实体 * 根据ID获取实体
* *

View File

@ -1,24 +1,25 @@
package com.gewuyou.forgeboot.webmvc.spec.service.impl package com.gewuyou.webmvc.spec.jpa.service.impl
import com.gewuyou.forgeboot.webmvc.dto.PageResult import com.gewuyou.forgeboot.webmvc.dto.PageResult
import com.gewuyou.forgeboot.webmvc.dto.extension.map
import com.gewuyou.forgeboot.webmvc.dto.extension.toJpaQuery import com.gewuyou.webmvc.spec.core.page.QueryComponent
import com.gewuyou.forgeboot.webmvc.dto.extension.toPageResult import com.gewuyou.webmvc.spec.jpa.extension.map
import com.gewuyou.forgeboot.webmvc.dto.extension.toSpecification import com.gewuyou.webmvc.spec.jpa.extension.toJpaQuery
import com.gewuyou.forgeboot.webmvc.dto.page.QueryComponent import com.gewuyou.webmvc.spec.jpa.extension.toPageResult
import com.gewuyou.forgeboot.webmvc.spec.repository.CrudRepositorySpec import com.gewuyou.webmvc.spec.jpa.extension.toSpecification
import com.gewuyou.forgeboot.webmvc.spec.service.CrudServiceSpec import com.gewuyou.webmvc.spec.jpa.repository.JpaCrudRepositorySpec
import com.gewuyou.webmvc.spec.jpa.service.JpaCrudServiceSpec
/** /**
* CRUD 服务实现的抽象类提供通用的数据访问操作 * Jpa CRUD 服务实现的抽象类提供通用的数据访问操作
* *
* @since 2025-05-29 20:37:27 * @since 2025-05-29 20:37:27
* @author gewuyou * @author gewuyou
*/ */
abstract class CrudServiceImplSpec<Entity : Any, Id : Any>( abstract class JpaCrudServiceImplSpec<Entity : Any, Id : Any>(
private val repository: CrudRepositorySpec<Entity, Id>, private val repository: JpaCrudRepositorySpec<Entity, Id>,
) : CrudServiceSpec<Entity, Id> { ) : JpaCrudServiceSpec<Entity, Id> {
/** /**
* 根据实体的唯一标识符查找实体对象 * 根据实体的唯一标识符查找实体对象
@ -184,15 +185,16 @@ abstract class CrudServiceImplSpec<Entity : Any, Id : Any>(
return repository.saveAll(entities) return repository.saveAll(entities)
} }
/** /**
* 标记实体为软删除状态 * 标记实体为软删除状态
* *
* 此方法应在子类中实现用于定义如何将实体标记为已删除例如设置一个 deleted 字段 * 此方法应在子类中实现用于定义如何将实体标记为已删除例如设置一个 deleted 字段
* 软删除不会从数据库中物理移除记录而是将其标记为已删除状态以便保留历史数据 * 软删除不会从数据库中物理移除记录而是将其标记为已删除状态以便保留历史数据
* *
* @param entity 实体对象表示要标记为删除状态的对象 * @param entity 实体对象表示要标记为删除状态的对象
*/ */
protected abstract fun setDeleted(entity: Entity) protected abstract fun setDeleted(entity: Entity)
/** /**
* 执行软删除操作 * 执行软删除操作
* *

View File

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

View File

@ -75,7 +75,8 @@ include(
":forgeboot-webmvc:exception-i18n", ":forgeboot-webmvc:exception-i18n",
":forgeboot-webmvc:dto", ":forgeboot-webmvc:dto",
":forgeboot-webmvc:validation", ":forgeboot-webmvc:validation",
":forgeboot-webmvc:spec" ":forgeboot-webmvc:spec-core",
":forgeboot-webmvc:spec-jpa",
) )
project(":forgeboot-webmvc").name = "forgeboot-webmvc-spring-boot-starter" project(":forgeboot-webmvc").name = "forgeboot-webmvc-spring-boot-starter"
project(":forgeboot-webmvc:version").name = "forgeboot-webmvc-version-spring-boot-starter" project(":forgeboot-webmvc:version").name = "forgeboot-webmvc-version-spring-boot-starter"
@ -84,7 +85,8 @@ project(":forgeboot-webmvc:exception").name = "forgeboot-webmvc-exception-spring
project(":forgeboot-webmvc:exception-i18n").name = "forgeboot-webmvc-exception-i18n-spring-boot-starter" project(":forgeboot-webmvc:exception-i18n").name = "forgeboot-webmvc-exception-i18n-spring-boot-starter"
project(":forgeboot-webmvc:dto").name = "forgeboot-webmvc-dto" project(":forgeboot-webmvc:dto").name = "forgeboot-webmvc-dto"
project(":forgeboot-webmvc:validation").name = "forgeboot-webmvc-validation" project(":forgeboot-webmvc:validation").name = "forgeboot-webmvc-validation"
project(":forgeboot-webmvc:spec").name = "forgeboot-webmvc-spec" project(":forgeboot-webmvc:spec-core").name = "forgeboot-webmvc-spec-core"
project(":forgeboot-webmvc:spec-jpa").name = "forgeboot-webmvc-spec-jpa"
//endregion //endregion
//region module core //region module core