mirror of
https://github.moeyy.xyz/https://github.com/GeWuYou/forgeboot
synced 2025-10-27 13:26:40 +08:00
refactor(dto): 重构数据传输对象和查询相关类
This commit is contained in:
parent
61b3467e93
commit
ace61952dd
@ -9,6 +9,12 @@ publish:
|
||||
script:
|
||||
- echo "🔧 授予 gradlew 执行权限..."
|
||||
- chmod +x gradlew
|
||||
- ./gradlew publishMavenJavaPublicationToGitLabRepository
|
||||
- ./gradlew printVersion
|
||||
# 强制刷新 SNAPSHOT 缓存并发布
|
||||
- ./gradlew clean publishMavenJavaPublicationToGitLabRepository \
|
||||
--no-daemon \
|
||||
--refresh-dependencies \
|
||||
-Porg.gradle.caching=false \
|
||||
-Dorg.gradle.configuration-cache=false
|
||||
tags:
|
||||
- java
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.enums.SortDirection
|
||||
|
||||
|
||||
/**
|
||||
* 排序条件
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-01-16 16:11:47
|
||||
*/
|
||||
class SortCondition {
|
||||
/**
|
||||
* 排序字段
|
||||
*/
|
||||
var field: String = "createdAt"
|
||||
|
||||
/**
|
||||
* 排序方向
|
||||
*/
|
||||
var direction: SortDirection = SortDirection.DESC
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.entities
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* 日期范围条件
|
||||
*
|
||||
* 表示一个用于描述日期范围的条件类,包含字段名、起始日期和结束日期。
|
||||
*
|
||||
* @property fieldName 用于存储该日期范围所对应字段的名称
|
||||
* @property startDate 日期范围的开始时间,可为空,默认值为 null
|
||||
* @property endDate 日期范围的结束时间,可为空,默认值为 null
|
||||
*
|
||||
* @since 2025-07-19 12:11:53
|
||||
* @author gewuyou
|
||||
*/
|
||||
data class DateRangeCondition(
|
||||
val fieldName: String,
|
||||
val startDate: Instant? = null,
|
||||
val endDate: Instant? = null
|
||||
) {
|
||||
/**
|
||||
* 验证日期范围的有效性
|
||||
*
|
||||
* 检查日期范围是否有效。当起始日期和结束日期都存在时,要求起始日期不能晚于结束日期。
|
||||
*
|
||||
* @return 如果日期范围有效则返回 true,否则返回 false
|
||||
*/
|
||||
fun isValid(): Boolean = startDate == null || endDate == null || !startDate.isAfter(endDate)
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.entities
|
||||
|
||||
import org.hibernate.query.SortDirection
|
||||
|
||||
/**
|
||||
* 排序条件
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-01-16 16:11:47
|
||||
*/
|
||||
class SortCondition {
|
||||
/**
|
||||
* 排序字段
|
||||
*/
|
||||
var property: String = "createdAt"
|
||||
|
||||
/**
|
||||
* 排序方向
|
||||
*/
|
||||
var direction: SortDirection = SortDirection.DESCENDING
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.enums
|
||||
|
||||
/**
|
||||
* 排序方向枚举
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-01-16 17:56:01
|
||||
*/
|
||||
enum class SortDirection {
|
||||
ASC, DESC
|
||||
}
|
||||
@ -0,0 +1,190 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.extension
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.*
|
||||
import jakarta.persistence.criteria.CriteriaBuilder
|
||||
import jakarta.persistence.criteria.Predicate
|
||||
import jakarta.persistence.criteria.Root
|
||||
import org.hibernate.query.SortDirection
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.domain.Sort
|
||||
import org.springframework.data.jpa.domain.Specification
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* 将 Hibernate 的 SortDirection 转换为 Spring Data 的 Sort.Direction。
|
||||
*
|
||||
* @return 返回对应的 Sort.Direction 实例
|
||||
*/
|
||||
fun SortDirection.toSpringDirection(): Sort.Direction =
|
||||
if (this == SortDirection.DESCENDING) Sort.Direction.DESC else Sort.Direction.ASC
|
||||
|
||||
/**
|
||||
* 将当前排序配置转换为可用于 Spring Data Pageable 的排序条件列表
|
||||
*
|
||||
* @param defaultSort 当没有其他排序条件时使用的默认排序,默认为按 createdAt 降序
|
||||
* @return 适用于 Pageable 的排序条件列表
|
||||
*
|
||||
* 处理逻辑优先级:
|
||||
* 1. 如果存在多个排序条件,使用 sortConditions 构建排序列表
|
||||
* 2. 如果指定了单一排序字段,使用 sortBy 和 sortDirection 构建排序条件
|
||||
* 3. 如果没有指定任何排序条件,使用默认排序
|
||||
*/
|
||||
fun Sortable.toSortOrders(defaultSort: Sort.Order = Sort.Order.desc("createdAt")): List<Sort.Order> {
|
||||
return when {
|
||||
sortConditions.isNotEmpty() -> {
|
||||
// 使用 map 将每个 SortCondition 转换为 Spring 的 Sort.Order 对象
|
||||
sortConditions.map {
|
||||
Sort.Order(
|
||||
it.direction.toSpringDirection(),
|
||||
it.property
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sortBy.isNotBlank() -> {
|
||||
// 当存在单一排序字段时,创建对应的排序条件列表
|
||||
listOf(Sort.Order(sortDirection.toSpringDirection(), sortBy))
|
||||
}
|
||||
|
||||
else -> listOf(defaultSort)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建关键字搜索对应的 JPA Predicate 条件列表
|
||||
*
|
||||
* 该扩展方法用于为实现了 KeywordSearchable 接口的对象构建 JPA 查询中的关键字搜索条件。
|
||||
* 遍历 keywordSearchFields() 提供的字段列表,对每个字段执行不区分大小写的模糊匹配查询。
|
||||
*
|
||||
* @param root 实体类的 JPA 查询根对象,用于获取实体字段
|
||||
* @param cb JPA 的 CriteriaBuilder 实例,用于构建查询条件
|
||||
* @return 返回构建的 [Predicate] 条件列表,若关键字为空则返回空列表
|
||||
*
|
||||
* 重要逻辑说明:
|
||||
* 1. 如果 keyword 为空或空白字符串,直接返回空列表,跳过所有构建逻辑
|
||||
* 2. 对每个 keywordSearchFields() 提供的字段尝试构建模糊匹配条件
|
||||
* 3. 使用 lower() 方法实现不区分大小写的比较
|
||||
* 4. 如果字段路径获取失败(如字段不存在),捕获异常并跳过该字段
|
||||
*/
|
||||
fun <T> KeywordSearchable.buildKeywordPredicates(
|
||||
root: Root<T>,
|
||||
cb: CriteriaBuilder,
|
||||
): List<Predicate> {
|
||||
val kw = keyword?.trim()?.takeIf { it.isNotEmpty() } ?: return emptyList()
|
||||
return keywordSearchFields().mapNotNull { field ->
|
||||
try {
|
||||
val path = root.get<String>(field)
|
||||
cb.like(cb.lower(path), "%${kw.lowercase()}%")
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 DateRangeFilterable 构建 JPA 日期范围谓词
|
||||
*
|
||||
* 该扩展方法用于为实现了 DateRangeFilterable 接口的对象构建 JPA 查询中的日期范围过滤条件。
|
||||
* 遍历 dataRangeFields 字段列表,为每个字段根据提供的 startDate 和 endDate 创建对应的谓词条件。
|
||||
*
|
||||
* @param root 实体类的 JPA 查询根对象,用于获取实体字段
|
||||
* @param cb JPA 的 CriteriaBuilder 实例,用于构建查询条件
|
||||
* @return 返回构建的 [Predicate] 条件列表,若未设置日期范围则返回空列表
|
||||
*/
|
||||
fun <T> DateRangeFilterable.buildDateRangePredicates(
|
||||
root: Root<T>,
|
||||
cb: CriteriaBuilder,
|
||||
): List<Predicate> {
|
||||
val predicates = mutableListOf<Predicate>()
|
||||
this.dateRanges().forEach { condition ->
|
||||
/**
|
||||
* 获取数据库字段的 Instant 类型路径,用于构建时间范围查询条件
|
||||
*/
|
||||
val dbField = root.get<Instant>(condition.fieldName)
|
||||
|
||||
/**
|
||||
* 如果 startDate 不为 null,添加大于等于该日期时间的谓词条件
|
||||
*/
|
||||
predicates.addIfNotNull(condition.startDate) {
|
||||
cb.greaterThanOrEqualTo(dbField, it)
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果 endDate 不为 null,添加小于等于该日期时间的谓词条件
|
||||
*/
|
||||
predicates.addIfNotNull(condition.endDate) {
|
||||
cb.lessThanOrEqualTo(dbField, it)
|
||||
}
|
||||
}
|
||||
return predicates
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将 QueryComponent 转换为 JPA 查询所需的 Specification(查询规范)
|
||||
*
|
||||
* 该扩展方法将实现了不同过滤接口的 QueryComponent 对象转换为 Spring Data JPA 兼容的 Specification。
|
||||
* 转换过程包含以下主要步骤:
|
||||
* 1. 检查对象是否实现了 DateRangeFilterable 接口,并构建日期范围查询条件
|
||||
* 2. 检查对象是否实现了 StatusFilterable 接口,并构建状态过滤查询条件
|
||||
* 3. 检查对象是否实现了 KeywordSearchable 接口,并构建关键字搜索查询条件
|
||||
* 4. 检查对象是否实现了 Filterable 接口,并使用其自定义的查询规范
|
||||
* 5. 将所有查询条件组合为一个逻辑 AND 条件返回
|
||||
*
|
||||
* @return 返回构建的 Specification<T> 查询规范
|
||||
*/
|
||||
fun <T> QueryComponent.toSpecification(): Specification<T> {
|
||||
return Specification { root, _, cb ->
|
||||
val predicates = mutableListOf<Predicate>()
|
||||
// 判断并组合 DateRange
|
||||
(this as? DateRangeFilterable)?.let {
|
||||
predicates += it.buildDateRangePredicates(root, cb)
|
||||
}
|
||||
// 判断并组合 Status
|
||||
(this as? StatusFilterable)?.let {
|
||||
predicates += it.buildStatusPredicates(root, cb)
|
||||
}
|
||||
// 判断并组合 KeywordSearchable
|
||||
(this as? KeywordSearchable)?.let {
|
||||
predicates += it.buildKeywordPredicates(root, cb)
|
||||
}
|
||||
// 判断并组合 Filterable
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(this as? Filterable<*, T>)?.let {
|
||||
predicates += it.buildSpecification(root, cb)
|
||||
}
|
||||
// 组合所有查询条件,返回逻辑 AND 结果
|
||||
cb.and(*predicates.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 QueryComponent 转换为 JPA 查询所需的 Specification 和分页请求对象
|
||||
*
|
||||
* 该扩展方法主要用于将自定义的查询组件对象转换为 Spring Data JPA 兼容的查询规范和分页参数。
|
||||
* 转换过程包含以下主要步骤:
|
||||
* 1. 通过 toSpecification 方法构建查询条件规范 (Specification)
|
||||
* 2. 从对象中提取分页参数 (Pageable),并转换为 Spring Data 的 PageRequest 实例
|
||||
* 3. 从对象中提取排序信息 (Sortable),并结合 toSortOrders 方法构建排序条件
|
||||
*
|
||||
* @return 返回一个 Pair,包含以下两个元素:
|
||||
* - Specification<T>:JPA 查询条件规范
|
||||
* - PageRequest:包含分页和排序信息的请求对象
|
||||
*
|
||||
* 重要逻辑说明:
|
||||
* 1. currentPage 从 1 开始计数,转换为 Spring Data 从 0 开始的页码
|
||||
* 2. pageSize 保持不变,直接用于分页请求
|
||||
* 3. 如果对象实现了 Sortable 接口,则使用其排序条件;否则使用未排序状态
|
||||
* 4. 如果对象未实现 Pageable 接口,则使用默认分页参数(第一页,每页10条)
|
||||
*/
|
||||
fun <T> QueryComponent.toJpaQuery(): Pair<Specification<T>, PageRequest> {
|
||||
val specification = this.toSpecification<T>()
|
||||
val pageable = this as? Pageable
|
||||
val sortable = this as? Sortable
|
||||
// 默认分页参数
|
||||
val page = pageable?.let { (it.currentPage - 1).coerceAtLeast(0) } ?: 0
|
||||
val size = pageable?.pageSize ?: 10
|
||||
// 排序规则
|
||||
val sort = sortable?.let { Sort.by(it.toSortOrders()) } ?: Sort.unsorted()
|
||||
return specification to PageRequest.of(page, size, sort)
|
||||
}
|
||||
@ -1,140 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.extension
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.enums.SortDirection
|
||||
import com.gewuyou.forgeboot.webmvc.dto.request.PageQueryReq
|
||||
import jakarta.persistence.criteria.CriteriaBuilder
|
||||
import jakarta.persistence.criteria.Predicate
|
||||
import jakarta.persistence.criteria.Root
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.domain.Sort
|
||||
|
||||
/**
|
||||
* 标记属性用于模糊匹配查询条件
|
||||
*
|
||||
* 该注解应用于实体类属性,表示在构建查询条件时使用LIKE操作符进行模糊匹配
|
||||
*/
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Like
|
||||
|
||||
/**
|
||||
* 标记属性用于精确匹配查询条件
|
||||
*
|
||||
* 该注解应用于实体类属性,表示在构建查询条件时使用EQUAL操作符进行精确匹配
|
||||
*/
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Equal
|
||||
|
||||
/**
|
||||
* 标记属性用于IN集合查询条件
|
||||
*
|
||||
* 该注解应用于实体类属性,表示在构建查询条件时使用IN操作符进行集合匹配
|
||||
*/
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class In
|
||||
|
||||
/**
|
||||
* 标记属性用于OR逻辑分组
|
||||
*
|
||||
* 该注解应用于实体类属性,表示该属性的查询条件需要与其他标记相同value值的属性
|
||||
* 进行OR逻辑组合,不同value值的组之间使用AND逻辑连接
|
||||
*
|
||||
* @property value 分组标识符,相同value的注解属性会被组合为一个OR条件组
|
||||
*/
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class OrGroup(val value: String)
|
||||
|
||||
/**
|
||||
* 分页查询扩展类
|
||||
*
|
||||
* 提供分页和查询条件构建的扩展方法,用于将 PageQueryReq 转换为 Spring Data 的分页对象,
|
||||
* 并生成通用的查询条件谓词列表。
|
||||
*
|
||||
* @since 2025-01-17
|
||||
* @author gewuyou
|
||||
*/
|
||||
fun <T> PageQueryReq<T>.toPageable(defaultSort: Sort.Order = Sort.Order.desc("createdAt")): Pageable {
|
||||
val orders = when {
|
||||
// 检查排序条件
|
||||
sortConditions.isNotEmpty() -> {
|
||||
sortConditions.map {
|
||||
if (it.direction == SortDirection.ASC) Sort.Order.asc(it.field) else Sort.Order.desc(it.field)
|
||||
}
|
||||
}
|
||||
// 检查是否有单字段排序
|
||||
sortBy.isNotBlank() -> {
|
||||
listOf(
|
||||
if (sortDirection == SortDirection.ASC) Sort.Order.asc(sortBy) else Sort.Order.desc(sortBy)
|
||||
)
|
||||
}
|
||||
// 如果都没有则默认按创建时间排序
|
||||
else -> listOf(defaultSort)
|
||||
}
|
||||
return PageRequest.of(currentPage - 1, pageSize, Sort.by(orders))
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件谓词列表
|
||||
*
|
||||
* 根据请求参数生成标准查询条件,包括时间范围、启用状态和删除状态等常用过滤条件。
|
||||
*
|
||||
* @param builder CriteriaBuilder 标准查询构造器
|
||||
* @param root Root<S> 查询的根对象
|
||||
* @param createAtName 创建时间字段名,默认 "createdAt"
|
||||
* @param enabledName 是否启用字段名,默认 "enabled"
|
||||
* @param deletedName 是否删除字段名,默认 "deleted"
|
||||
* @return MutableList<Predicate> 查询条件谓词列表
|
||||
*/
|
||||
fun <T, S> PageQueryReq<T>.getPredicates(
|
||||
builder: CriteriaBuilder,
|
||||
root: Root<S>,
|
||||
createAtName: String = "createdAt",
|
||||
enabledName: String = "enabled",
|
||||
deletedName: String = "deleted"
|
||||
): MutableList<Predicate> {
|
||||
val predicates = mutableListOf<Predicate>()
|
||||
|
||||
// 添加开始日期条件(容错)
|
||||
startDate?.let {
|
||||
try {
|
||||
predicates.add(builder.greaterThanOrEqualTo(root.get(createAtName), it))
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// 字段不存在,忽略
|
||||
}
|
||||
}
|
||||
|
||||
// 添加结束日期条件(容错)
|
||||
endDate?.let {
|
||||
try {
|
||||
predicates.add(builder.lessThanOrEqualTo(root.get(createAtName), it))
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// 字段不存在,忽略
|
||||
}
|
||||
}
|
||||
|
||||
// 添加是否启用条件(通常字段存在,可不做容错)
|
||||
enabled?.let {
|
||||
try {
|
||||
predicates.add(builder.equal(root.get<Boolean>(enabledName), it))
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// 可选容错
|
||||
}
|
||||
}
|
||||
|
||||
// 添加是否删除条件(通常字段存在,可不做容错)
|
||||
deleted.let {
|
||||
try {
|
||||
predicates.add(builder.equal(root.get<Boolean>(deletedName), it))
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// 可选容错
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return predicates
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.page
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.entities.DateRangeCondition
|
||||
|
||||
/**
|
||||
* 日期范围可过滤接口
|
||||
* 用于定义支持日期范围过滤的对象结构,包含开始时间和结束时间属性
|
||||
*
|
||||
* 该接口继承自QueryComponent,用于构建支持多日期范围过滤条件的查询组件。
|
||||
* 实现该接口的类需要提供具体的日期范围条件集合。
|
||||
*
|
||||
* @since 2025-07-19 08:59:44
|
||||
* @author gewuyou
|
||||
*/
|
||||
fun interface DateRangeFilterable : QueryComponent {
|
||||
/**
|
||||
* 获取所有日期范围条件集合
|
||||
*
|
||||
* 该方法返回一个包含所有日期范围条件的列表,每个元素都是一个DateRangeCondition对象,
|
||||
* 表示一个独立的日期范围过滤条件。
|
||||
*
|
||||
* @return List<DateRangeCondition> 类型的列表,包含所有的日期范围条件
|
||||
*/
|
||||
fun dateRanges(): List<DateRangeCondition>
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
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>
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.page
|
||||
|
||||
/**
|
||||
* 关键字可搜索接口,用于支持包含关键字搜索条件的数据结构
|
||||
*
|
||||
* 该接口定义了关键字搜索所需的基础结构,包括关键字本身和需要进行模糊匹配的字段列表。
|
||||
* 实现该接口的数据结构可以在查询中支持基于关键字的模糊匹配功能。
|
||||
*
|
||||
* @since 2025-07-19 08:56:31
|
||||
* @author gewuyou
|
||||
*/
|
||||
interface KeywordSearchable : QueryComponent {
|
||||
/**
|
||||
* 关键字,用于模糊匹配。
|
||||
*
|
||||
* 如果值为 null 或空字符串,则表示不需要进行关键字过滤。
|
||||
*/
|
||||
val keyword: String?
|
||||
|
||||
/**
|
||||
* 获取需要被关键字模糊匹配的字段列表。
|
||||
*
|
||||
* 返回的字段列表用于构建模糊查询条件,每个字段将与关键字进行模糊匹配。
|
||||
* 字段名称应为数据结构中的实际字段名,且应支持模糊匹配操作。
|
||||
*
|
||||
* @return 需要进行模糊匹配的字段列表,不为 null。
|
||||
*/
|
||||
fun keywordSearchFields(): List<String>
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.page
|
||||
|
||||
/**
|
||||
* 可分页接口,用于支持分页功能的类实现
|
||||
*
|
||||
* @since 2025-07-19 08:52:58
|
||||
* @author gewuyou
|
||||
*/
|
||||
interface Pageable : QueryComponent {
|
||||
/**
|
||||
* 当前页码,从1开始计数
|
||||
*/
|
||||
val currentPage: Int
|
||||
|
||||
/**
|
||||
* 每页显示的数据条数
|
||||
*/
|
||||
val pageSize: Int
|
||||
|
||||
/**
|
||||
* 计算并返回当前页的起始偏移量
|
||||
*
|
||||
* @return 返回计算后的偏移量,用于数据库查询等场景
|
||||
*/
|
||||
fun getOffset(): Int = (currentPage - 1) * pageSize
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.page
|
||||
|
||||
/**
|
||||
* QueryComponent 是一个密封接口,用于定义查询组件的公共契约。
|
||||
* 密封接口的实现类必须与接口本身在同一个包中,以确保接口的封闭性和安全性。
|
||||
*/
|
||||
sealed interface QueryComponent
|
||||
@ -0,0 +1,27 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.page
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.entities.SortCondition
|
||||
import org.hibernate.query.SortDirection
|
||||
|
||||
/**
|
||||
* 可排序接口,用于定义排序相关属性
|
||||
*
|
||||
* @since 2025-07-19 08:53:56
|
||||
* @author gewuyou
|
||||
*/
|
||||
interface Sortable : QueryComponent {
|
||||
/**
|
||||
* 获取排序字段名称
|
||||
*/
|
||||
val sortBy: String
|
||||
|
||||
/**
|
||||
* 获取排序方向(升序或降序)
|
||||
*/
|
||||
val sortDirection: SortDirection
|
||||
|
||||
/**
|
||||
* 获取排序条件列表,用于支持多条件排序
|
||||
*/
|
||||
val sortConditions: List<SortCondition>
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
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 的查询构建。
|
||||
* 实现该接口的类可通过 [buildStatusPredicates]方法提供具体过滤逻辑。
|
||||
*
|
||||
* @since 2025-07-19 09:00:42
|
||||
* @author gewuyou
|
||||
*/
|
||||
interface StatusFilterable : QueryComponent {
|
||||
/**
|
||||
* 构建与启用状态相关的查询条件列表。
|
||||
*
|
||||
* 该方法用于生成一个 Predicate 列表,用于 JPA Criteria 查询中对实体的状态字段进行过滤。
|
||||
* 通常用于支持启用/禁用状态的动态查询场景。
|
||||
*
|
||||
* @param <T> 查询所涉及的实体类型
|
||||
* @param root Criteria 查询的根对象,用于访问实体属性
|
||||
* @param cb CriteriaBuilder 实例,用于构建查询条件
|
||||
* @return 构建完成的 Predicate 条件列表
|
||||
*/
|
||||
fun <T> buildStatusPredicates(root: Root<T>, cb: CriteriaBuilder): List<Predicate>
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.request
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.DateRangeFilterable
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
|
||||
|
||||
/**
|
||||
* 抽象日期分页请求
|
||||
*
|
||||
* 用于封装带有日期范围过滤的分页请求参数,实现分页和日期范围过滤功能的统一请求数据结构。
|
||||
*
|
||||
* @property currentPage 当前页码,默认值为1。用于标识请求的页码位置。
|
||||
* @property pageSize 每页记录数,默认值为10。用于控制分页时每页返回的数据量。
|
||||
*
|
||||
* @since 2025-07-19 10:19:23
|
||||
* @author gewuyou
|
||||
*/
|
||||
abstract class AbstractDatePageRequest(
|
||||
override val currentPage: Int = 1,
|
||||
override val pageSize: Int = 10,
|
||||
) : Pageable, DateRangeFilterable
|
||||
@ -0,0 +1,21 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.request
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.DateRangeFilterable
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.StatusFilterable
|
||||
|
||||
/**
|
||||
* 日期状态页面请求
|
||||
* 用于封装包含分页、日期范围和状态过滤条件的复合请求参数
|
||||
*
|
||||
* @property currentPage 当前页码,从1开始计数的分页参数
|
||||
* @property pageSize 每页记录数量,控制分页大小
|
||||
* @property statusConditions 状态条件映射,键值对形式表示的状态过滤条件
|
||||
*
|
||||
* @since 2025-07-19 09:15:12
|
||||
* @author gewuyou
|
||||
*/
|
||||
abstract class AbstractDateStatusPageRequest(
|
||||
override val currentPage: Int = 1,
|
||||
override val pageSize: Int = 10,
|
||||
) : Pageable, DateRangeFilterable, StatusFilterable
|
||||
@ -0,0 +1,23 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.request
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.Filterable
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
|
||||
|
||||
|
||||
/**
|
||||
* 抽象过滤的分页请求
|
||||
*
|
||||
* 该类实现了分页和过滤功能的请求数据封装,用于构建带过滤条件的分页查询。
|
||||
*
|
||||
* @param currentPage 当前页码,从1开始计数,默认为1
|
||||
* @param pageSize 每页显示的数据数量,默认为10
|
||||
* @param filter 过滤条件对象,泛型类型T的可空实例,默认为null
|
||||
*
|
||||
* @since 2025-07-19 09:16:36
|
||||
* @author gewuyou
|
||||
*/
|
||||
abstract class AbstractFilteredPageRequest<T,E>(
|
||||
override val currentPage: Int = 1,
|
||||
override val pageSize: Int = 10,
|
||||
override val filter: T? = null,
|
||||
) : Pageable, Filterable<T,E>
|
||||
@ -0,0 +1,33 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.request
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.*
|
||||
|
||||
/**
|
||||
* 抽象分页请求类,用于封装分页查询的通用参数
|
||||
*
|
||||
* 该类实现了多种分页相关接口,支持分页、排序、过滤、关键词搜索、状态条件筛选和日期范围过滤功能
|
||||
*
|
||||
* @param T 泛型类型参数,表示自定义过滤条件的数据类型
|
||||
*
|
||||
* @property currentPage 当前页码,默认值为1
|
||||
* @property pageSize 每页记录数,默认值为10
|
||||
* @property sortBy 排序字段,默认值为"createdAt"
|
||||
* @property sortDirection 排序方向,默认值为降序(SortDirection.DESCENDING)
|
||||
* @property sortConditions 排序条件列表,默认为空列表
|
||||
* @property keyword 关键词搜索内容,可为空
|
||||
* @property filter 自定义过滤条件,可为空,类型为泛型T
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-07-19 10:25:55
|
||||
*/
|
||||
abstract class AbstractFullPageRequest<T,E>(
|
||||
override val currentPage: Int = 1,
|
||||
override val pageSize: Int = 10,
|
||||
override val keyword: String? = null,
|
||||
override val filter: T? = null,
|
||||
) : Pageable,
|
||||
Sortable,
|
||||
KeywordSearchable,
|
||||
Filterable<T,E>,
|
||||
DateRangeFilterable,
|
||||
StatusFilterable
|
||||
@ -0,0 +1,26 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.request
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.Filterable
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.KeywordSearchable
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
|
||||
|
||||
/**
|
||||
* 抽象搜索过滤分页请求
|
||||
*
|
||||
* 该类实现了分页、关键词搜索和过滤功能的通用请求对象。
|
||||
* 作为基类用于构建具有统一分页和搜索能力的请求数据结构。
|
||||
*
|
||||
* @param currentPage 当前页码,从1开始,默认为1
|
||||
* @param pageSize 每页记录数,默认为10
|
||||
* @param keyword 关键词搜索内容,可为空
|
||||
* @param filter 过滤条件对象,泛型类型,可为空
|
||||
*
|
||||
* @since 2025-07-19 10:22:41
|
||||
* @author gewuyou
|
||||
*/
|
||||
abstract class AbstractSearchFilterPageRequest<T,E>(
|
||||
override val currentPage: Int = 1,
|
||||
override val pageSize: Int = 10,
|
||||
override val keyword: String? = null,
|
||||
override val filter: T? = null,
|
||||
) : Pageable, KeywordSearchable, Filterable<T,E>
|
||||
@ -0,0 +1,23 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.request
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.KeywordSearchable
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
|
||||
|
||||
/**
|
||||
* 抽象搜索分页请求类
|
||||
*
|
||||
* 该类用于封装分页搜索请求的基本参数,包括当前页码、页大小和关键字。
|
||||
* 通过实现 [Pageable] 和 [KeywordSearchable] 接口,提供分页和关键字搜索功能。
|
||||
*
|
||||
* @property currentPage 当前页码,默认为 1
|
||||
* @property pageSize 每页条目数,默认为 10
|
||||
* @property keyword 搜索关键字,可为空,默认为 null
|
||||
*
|
||||
* @since 2025-07-19 09:14:28
|
||||
* @author gewuyou
|
||||
*/
|
||||
abstract class AbstractSearchPageRequest(
|
||||
override val currentPage: Int = 1,
|
||||
override val pageSize: Int = 10,
|
||||
override val keyword: String? = null
|
||||
) : Pageable, KeywordSearchable
|
||||
@ -0,0 +1,28 @@
|
||||
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>
|
||||
@ -0,0 +1,23 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.request
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.DateRangeFilterable
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.Sortable
|
||||
|
||||
/**
|
||||
* 抽象排序日期分页请求
|
||||
* 用于封装包含排序、分页和日期范围过滤条件的请求参数
|
||||
*
|
||||
* @property currentPage 当前页码,默认值为1
|
||||
* @property pageSize 每页记录数,默认值为10
|
||||
* @property sortBy 排序字段名称,默认值为"createdAt"
|
||||
* @property sortDirection 排序方向,默认值为降序(SortDirection.DESCENDING)
|
||||
* @property sortConditions 排序条件集合,默认值为空列表
|
||||
*
|
||||
* @since 2025-07-19 10:23:05
|
||||
* @author gewuyou
|
||||
*/
|
||||
abstract class AbstractSortedDatePageRequest(
|
||||
override val currentPage: Int = 1,
|
||||
override val pageSize: Int = 10,
|
||||
) : Pageable, Sortable, DateRangeFilterable
|
||||
@ -0,0 +1,22 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.request
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.Sortable
|
||||
|
||||
/**
|
||||
* 抽象排序分页请求数据类
|
||||
*
|
||||
* 该类用于封装带有排序功能的分页请求参数,包含当前页码、每页大小、默认排序字段、
|
||||
* 排序方向以及扩展的排序条件集合。通过实现 [Pageable] 和 [Sortable] 接口,
|
||||
* 提供标准化的分页与排序属性。
|
||||
*
|
||||
* @property currentPage 当前页码,从1开始,默认值为1
|
||||
* @property pageSize 每页记录数,默认值为10
|
||||
*
|
||||
* @since 2025-07-19 09:13:19
|
||||
* @author gewuyou
|
||||
*/
|
||||
abstract class AbstractSortedPageRequest(
|
||||
override val currentPage: Int = 1,
|
||||
override val pageSize: Int = 10,
|
||||
) : Pageable, Sortable
|
||||
@ -0,0 +1,20 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.request
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.Pageable
|
||||
|
||||
/**
|
||||
* 基本分页请求数据类
|
||||
*
|
||||
* 该类用于封装分页请求的基础信息,包括当前页码和每页记录数。
|
||||
* 通过实现 Pageable 接口,支持与分页处理相关的操作。
|
||||
*
|
||||
* @property currentPage 当前页码,默认值为 1
|
||||
* @property pageSize 每页记录数,默认值为 10
|
||||
*
|
||||
* @since 2025-07-19 09:12:22
|
||||
* @author gewuyou
|
||||
*/
|
||||
data class BasicPageRequest(
|
||||
override val currentPage: Int = 1,
|
||||
override val pageSize: Int = 10
|
||||
) : Pageable
|
||||
@ -1,93 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.request
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
import com.gewuyou.forgeboot.webmvc.dto.SortCondition
|
||||
import com.gewuyou.forgeboot.webmvc.dto.enums.SortDirection
|
||||
import jakarta.validation.constraints.Min
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* 分页查询条件请求实体类
|
||||
*
|
||||
* 用于通用分页、排序、关键字搜索、日期范围与状态过滤。
|
||||
* 支持自定义泛型过滤器实体。
|
||||
*
|
||||
* @author gewuyou
|
||||
* @since 2025-01-16 16:01:12
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
open class PageQueryReq<T> {
|
||||
|
||||
/**
|
||||
* 当前页码(默认1)
|
||||
*/
|
||||
@field:Min(1)
|
||||
var currentPage: Int = 1
|
||||
|
||||
/**
|
||||
* 每页条数(默认10)
|
||||
*/
|
||||
@field:Min(1)
|
||||
var pageSize: Int = 10
|
||||
|
||||
/**
|
||||
* 排序字段(单一字段)
|
||||
*/
|
||||
var sortBy: String = "createdAt"
|
||||
|
||||
/**
|
||||
* 排序方向(单一字段,ASC或DESC)
|
||||
*/
|
||||
var sortDirection: SortDirection = SortDirection.DESC
|
||||
|
||||
/**
|
||||
* 排序条件实体类(支持多字段排序)
|
||||
*/
|
||||
var sortConditions: MutableList<SortCondition> = mutableListOf()
|
||||
|
||||
/**
|
||||
* 关键字搜索,常用于模糊查询
|
||||
*/
|
||||
var keyword: String? = null
|
||||
|
||||
/**
|
||||
* 自定义过滤条件实体类
|
||||
*/
|
||||
var filter: T? = null
|
||||
|
||||
/**
|
||||
* 开始日期
|
||||
*/
|
||||
var startDate: Instant? = null
|
||||
|
||||
/**
|
||||
* 结束日期
|
||||
*/
|
||||
var endDate: Instant? = null
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
var enabled: Boolean? = null
|
||||
|
||||
/**
|
||||
* 是否删除
|
||||
*/
|
||||
var deleted: Boolean = false
|
||||
|
||||
/**
|
||||
* 计算分页偏移量
|
||||
*/
|
||||
fun getOffset(): Int = (currentPage - 1) * pageSize
|
||||
|
||||
/**
|
||||
* 校验日期范围是否合法(开始时间不能晚于结束时间)
|
||||
*/
|
||||
fun isDateRangeValid(): Boolean {
|
||||
return startDate == null || endDate == null || !startDate!!.isAfter(endDate)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "PageQueryReq(currentPage=$currentPage, pageSize=$pageSize, sortBy='$sortBy', sortDirection=$sortDirection, sortConditions=$sortConditions, keyword=$keyword, filter=$filter, startDate=$startDate, endDate=$endDate, enabled=$enabled, deleted=$deleted)"
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,104 +0,0 @@
|
||||
package com.gewuyou.forgeboot.webmvc.dto.util
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.Equal
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.In
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.Like
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.OrGroup
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.getPredicates
|
||||
import com.gewuyou.forgeboot.webmvc.dto.request.PageQueryReq
|
||||
import jakarta.persistence.criteria.Path
|
||||
import jakarta.persistence.criteria.Predicate
|
||||
import jakarta.persistence.criteria.Root
|
||||
import org.springframework.data.jpa.domain.Specification
|
||||
import kotlin.collections.isNotEmpty
|
||||
import kotlin.collections.toTypedArray
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
|
||||
/**
|
||||
* 动态构建 JPA Specification 的工具类
|
||||
*
|
||||
* 支持字段注解控制查询方式(@Like, @Equal, @In)
|
||||
* 支持嵌套字段路径(如 "user.name")
|
||||
* 支持 OR 条件组(@OrGroup("groupName"))
|
||||
* @since 2025-05-28 23:37:17
|
||||
* @author gewuyou
|
||||
*/
|
||||
object DynamicSpecificationBuilder {
|
||||
|
||||
/**
|
||||
* 构建动态查询 Specification
|
||||
*
|
||||
* @param query PageQuery<T> 查询参数对象
|
||||
* @param createAtName 时间字段名
|
||||
* @param enabledName 是否启用字段名
|
||||
* @param deletedName 是否删除字段名
|
||||
*/
|
||||
inline fun <reified T : Any, reified E> build(
|
||||
query: PageQueryReq<T>,
|
||||
createAtName: String = "createdAt",
|
||||
enabledName: String = "enabled",
|
||||
deletedName: String = "deleted",
|
||||
): Specification<E> {
|
||||
return Specification { root, _, builder ->
|
||||
val predicates = mutableListOf<Predicate>()
|
||||
|
||||
// 1. 加入基本条件(时间、启用、删除)
|
||||
predicates += query.getPredicates(builder, root, createAtName, enabledName, deletedName)
|
||||
|
||||
// 2. 处理 filter 字段
|
||||
val filterObj = query.filter ?: return@Specification builder.and(*predicates.toTypedArray())
|
||||
|
||||
// 3. OR 分组
|
||||
val orGroups = mutableMapOf<String, MutableList<Predicate>>()
|
||||
|
||||
T::class.memberProperties.forEach { prop ->
|
||||
prop.isAccessible = true
|
||||
val value = prop.get(filterObj) ?: return@forEach
|
||||
|
||||
val fieldPath = prop.name
|
||||
val path = getPath(root, fieldPath)
|
||||
|
||||
val predicate = when {
|
||||
prop.findAnnotation<Like>() != null && value is String ->
|
||||
builder.like(path as Path<String>, "%$value%")
|
||||
|
||||
prop.findAnnotation<Equal>() != null ->
|
||||
builder.equal(path, value)
|
||||
|
||||
prop.findAnnotation<In>() != null && value is Collection<*> ->
|
||||
path.`in`(value)
|
||||
|
||||
// 默认策略:非空值执行 equal
|
||||
else -> builder.equal(path, value)
|
||||
}
|
||||
|
||||
val orGroup = prop.findAnnotation<OrGroup>()?.value
|
||||
if (orGroup != null) {
|
||||
orGroups.getOrPut(orGroup) { mutableListOf() }.add(predicate)
|
||||
} else {
|
||||
predicates.add(predicate)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 添加 OR 条件组
|
||||
orGroups.values.forEach { group ->
|
||||
if (group.isNotEmpty()) {
|
||||
predicates.add(builder.or(*group.toTypedArray()))
|
||||
}
|
||||
}
|
||||
builder.and(*predicates.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持嵌套字段路径解析,例如 "user.name"
|
||||
*/
|
||||
fun <E> getPath(root: Root<E>, pathStr: String): Path<*> {
|
||||
return pathStr.split(".").fold(root as Path<*>) { path, part ->
|
||||
path.get<Any>(part)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
package com.gewuyou.forgeboot.webmvc.spec.service
|
||||
|
||||
import com.gewuyou.forgeboot.webmvc.dto.PageResult
|
||||
import com.gewuyou.forgeboot.webmvc.dto.request.PageQueryReq
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.QueryComponent
|
||||
|
||||
/**
|
||||
* CRUD 服务规范
|
||||
@ -10,11 +10,10 @@ import com.gewuyou.forgeboot.webmvc.dto.request.PageQueryReq
|
||||
*
|
||||
* @param Entity 实体类型
|
||||
* @param Id 实体标识符类型
|
||||
* @param Filter 查询过滤器类型
|
||||
* @since 2025-05-29 20:18:22
|
||||
* @author gewuyou
|
||||
*/
|
||||
interface CrudServiceSpec<Entity: Any, Id: Any, Filter: Any> {
|
||||
interface CrudServiceSpec<Entity: Any, Id: Any> {
|
||||
/**
|
||||
* 根据ID获取实体
|
||||
*
|
||||
@ -101,29 +100,39 @@ interface CrudServiceSpec<Entity: Any, Id: Any, Filter: Any> {
|
||||
*/
|
||||
fun saveAll(entities: List<Entity>): List<Entity>
|
||||
|
||||
|
||||
/**
|
||||
* 分页查询实体列表
|
||||
*
|
||||
* 通过提供的查询组件进行分页数据检索,返回包含分页信息的结果对象
|
||||
*
|
||||
* @param query 查询组件,包含分页和过滤条件等信息
|
||||
* @return 返回分页结果对象,包含当前页的数据列表、总记录数等信息
|
||||
*/
|
||||
fun page(query: QueryComponent): PageResult<Entity>
|
||||
|
||||
|
||||
/**
|
||||
* 分页查询并映射结果
|
||||
*
|
||||
* @param query 分页查询请求,包含查询参数和分页信息
|
||||
* @return 返回映射后的分页结果
|
||||
* 通过提供的查询组件进行分页数据检索,并使用给定的映射函数将结果转换为另一种类型
|
||||
* 适用于需要将实体转换为DTO或其他形式的场景
|
||||
*
|
||||
* @param query 查询组件,包含分页和过滤条件等信息
|
||||
* @param mapping 将实体转换为目标类型的函数
|
||||
* @return 返回分页结果对象,包含转换后的数据列表、总记录数等信息
|
||||
*/
|
||||
fun page(query: PageQueryReq<Filter>): PageResult<Entity>
|
||||
fun <V> pageMapped(query: QueryComponent, mapping: (Entity) -> V): PageResult<V>
|
||||
|
||||
|
||||
/**
|
||||
* 分页查询并使用给定函数映射结果
|
||||
* 查询符合条件的记录总数
|
||||
*
|
||||
* @param query 分页查询请求,包含查询参数和分页信息
|
||||
* @param mapping 映射函数,用于将实体映射为其他类型
|
||||
* @param <V> 映射后的类型
|
||||
* @return 返回映射后的分页结果
|
||||
*/
|
||||
fun <V> pageMapped(query: PageQueryReq<Filter>, mapping: (Entity) -> V): PageResult<V>
|
||||
|
||||
/**
|
||||
* 根据过滤条件统计实体数量
|
||||
* 根据查询组件中的过滤条件统计匹配的记录数量
|
||||
* 通常用于分页时计算总页数或显示记录总数
|
||||
*
|
||||
* @param filter 查询过滤器
|
||||
* @return 返回满足条件的实体数量
|
||||
* @param query 查询组件,包含过滤条件等信息
|
||||
* @return 返回符合条件的记录总数
|
||||
*/
|
||||
fun count(filter: Filter): Long
|
||||
fun count(query: QueryComponent): Long
|
||||
}
|
||||
|
||||
@ -2,13 +2,12 @@ package com.gewuyou.forgeboot.webmvc.spec.service.impl
|
||||
|
||||
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.forgeboot.webmvc.dto.extension.toPageResult
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.toPageable
|
||||
import com.gewuyou.forgeboot.webmvc.dto.request.PageQueryReq
|
||||
import com.gewuyou.forgeboot.webmvc.dto.extension.toSpecification
|
||||
import com.gewuyou.forgeboot.webmvc.dto.page.QueryComponent
|
||||
import com.gewuyou.forgeboot.webmvc.spec.repository.CrudRepositorySpec
|
||||
import com.gewuyou.forgeboot.webmvc.spec.service.CrudServiceSpec
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.jpa.domain.Specification
|
||||
|
||||
|
||||
/**
|
||||
@ -17,9 +16,9 @@ import org.springframework.data.jpa.domain.Specification
|
||||
* @since 2025-05-29 20:37:27
|
||||
* @author gewuyou
|
||||
*/
|
||||
abstract class CrudServiceImplSpec<Entity : Any, Id : Any, Filter : Any>(
|
||||
abstract class CrudServiceImplSpec<Entity : Any, Id : Any>(
|
||||
private val repository: CrudRepositorySpec<Entity, Id>,
|
||||
) : CrudServiceSpec<Entity, Id, Filter> {
|
||||
) : CrudServiceSpec<Entity, Id> {
|
||||
|
||||
/**
|
||||
* 根据实体的唯一标识符查找实体对象。
|
||||
@ -31,41 +30,6 @@ abstract class CrudServiceImplSpec<Entity : Any, Id : Any, Filter : Any>(
|
||||
return repository.findById(id).orElse(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行分页查询并返回原始实体的分页结果。
|
||||
*
|
||||
* @param query 分页查询请求,包含过滤条件和分页信息
|
||||
* @return 返回实体类型的分页结果
|
||||
*/
|
||||
override fun page(query: PageQueryReq<Filter>): PageResult<Entity> {
|
||||
return repository
|
||||
.findAll(
|
||||
buildSpecification(query),
|
||||
resolvePageable(query)
|
||||
)
|
||||
.toPageResult()
|
||||
}
|
||||
|
||||
/**
|
||||
* 将分页请求解析为 Pageable 对象。
|
||||
* 子类可重写此方法来自定义分页逻辑(例如默认排序)。
|
||||
*
|
||||
* @param query 分页查询请求
|
||||
* @return 分页参数
|
||||
*/
|
||||
protected open fun resolvePageable(query: PageQueryReq<Filter>): Pageable {
|
||||
return query.toPageable()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建 JPA Specification 查询条件。
|
||||
*
|
||||
* @param query 包含过滤条件的分页请求
|
||||
* @return 返回构建好的 Specification 查询条件
|
||||
*/
|
||||
protected abstract fun buildSpecification(query: PageQueryReq<Filter>): Specification<Entity>
|
||||
|
||||
/**
|
||||
* 获取所有实体列表。
|
||||
*
|
||||
@ -154,32 +118,50 @@ abstract class CrudServiceImplSpec<Entity : Any, Id : Any, Filter : Any>(
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行分页查询并将结果使用给定的映射函数转换为其他类型。
|
||||
* 分页查询实体列表
|
||||
*
|
||||
* @param query 分页查询请求,包含查询参数和分页信息
|
||||
* @param mapping 映射函数,用于将实体映射为其他类型
|
||||
* @param <V> 映射后的目标类型
|
||||
* @return 返回映射后的分页结果
|
||||
* 通过提供的查询组件进行分页数据检索,返回包含分页信息的结果对象
|
||||
*
|
||||
* @param query 查询组件,包含分页和过滤条件等信息
|
||||
* @return 返回分页结果对象,包含当前页的数据列表、总记录数等信息
|
||||
*/
|
||||
override fun <V> pageMapped(
|
||||
query: PageQueryReq<Filter>,
|
||||
mapping: (Entity) -> V,
|
||||
): PageResult<V> {
|
||||
val page = repository.findAll(
|
||||
buildSpecification(query),
|
||||
resolvePageable(query)
|
||||
)
|
||||
return page.toPageResult().map(mapping)
|
||||
override fun page(query: QueryComponent): PageResult<Entity> {
|
||||
val (specification, pageable) = query.toJpaQuery<Entity>()
|
||||
return repository.findAll(specification, pageable).toPageResult()
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据给定的过滤条件统计实体数量。
|
||||
* 分页查询并映射结果
|
||||
*
|
||||
* @param filter 查询过滤器
|
||||
* @return 返回满足条件的实体数量
|
||||
* 通过提供的查询组件进行分页数据检索,并使用给定的映射函数将结果转换为另一种类型
|
||||
* 适用于需要将实体转换为DTO或其他形式的场景
|
||||
*
|
||||
* @param query 查询组件,包含分页和过滤条件等信息
|
||||
* @param mapping 将实体转换为目标类型的函数
|
||||
* @return 返回分页结果对象,包含转换后的数据列表、总记录数等信息
|
||||
*/
|
||||
override fun count(filter: Filter): Long {
|
||||
return repository.count(buildSpecification(PageQueryReq<Filter>().apply { this.filter = filter }))
|
||||
override fun <V> pageMapped(
|
||||
query: QueryComponent,
|
||||
mapping: (Entity) -> V,
|
||||
): PageResult<V> {
|
||||
val (specification, pageable) = query.toJpaQuery<Entity>()
|
||||
return repository.findAll(specification, pageable)
|
||||
.toPageResult()
|
||||
.map(mapping)
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询符合条件的记录总数
|
||||
*
|
||||
* 根据查询组件中的过滤条件统计匹配的记录数量
|
||||
* 通常用于分页时计算总页数或显示记录总数
|
||||
*
|
||||
* @param query 查询组件,包含过滤条件等信息
|
||||
* @return 返回符合条件的记录总数
|
||||
*/
|
||||
override fun count(query: QueryComponent): Long {
|
||||
val specification = query.toSpecification<Entity>()
|
||||
return repository.count(specification)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user