diff --git a/forgeboot-webmvc/spec/.gitattributes b/forgeboot-webmvc/spec/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/forgeboot-webmvc/spec/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/forgeboot-webmvc/spec/.gitignore b/forgeboot-webmvc/spec/.gitignore new file mode 100644 index 0000000..5a979af --- /dev/null +++ b/forgeboot-webmvc/spec/.gitignore @@ -0,0 +1,40 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Kotlin ### +.kotlin diff --git a/forgeboot-webmvc/spec/build.gradle.kts b/forgeboot-webmvc/spec/build.gradle.kts new file mode 100644 index 0000000..6fc90de --- /dev/null +++ b/forgeboot-webmvc/spec/build.gradle.kts @@ -0,0 +1,6 @@ + +dependencies { + compileOnly(libs.springBootStarter.jpa) + compileOnly(libs.org.mapstruct) + implementation(project(Modules.Webmvc.DTO)) +} diff --git a/forgeboot-webmvc/spec/src/main/kotlin/com/gewuyou/forgeboot/webmvc/spec/repository/CrudRepositorySpec.kt b/forgeboot-webmvc/spec/src/main/kotlin/com/gewuyou/forgeboot/webmvc/spec/repository/CrudRepositorySpec.kt new file mode 100644 index 0000000..8376466 --- /dev/null +++ b/forgeboot-webmvc/spec/src/main/kotlin/com/gewuyou/forgeboot/webmvc/spec/repository/CrudRepositorySpec.kt @@ -0,0 +1,12 @@ +package com.gewuyou.forgeboot.webmvc.spec.repository + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.JpaSpecificationExecutor + +/** + *CRUD 存储库规范 + * + * @since 2025-05-29 20:39:11 + * @author gewuyou + */ +interface CrudRepositorySpec: JpaRepository, JpaSpecificationExecutor \ No newline at end of file diff --git a/forgeboot-webmvc/spec/src/main/kotlin/com/gewuyou/forgeboot/webmvc/spec/service/CrudServiceSpec.kt b/forgeboot-webmvc/spec/src/main/kotlin/com/gewuyou/forgeboot/webmvc/spec/service/CrudServiceSpec.kt new file mode 100644 index 0000000..769f227 --- /dev/null +++ b/forgeboot-webmvc/spec/src/main/kotlin/com/gewuyou/forgeboot/webmvc/spec/service/CrudServiceSpec.kt @@ -0,0 +1,119 @@ +package com.gewuyou.forgeboot.webmvc.spec.service + +import com.gewuyou.forgeboot.webmvc.dto.PageResult +import com.gewuyou.forgeboot.webmvc.dto.request.PageQueryReq + +/** + * CRUD 服务规范 + * + * 定义了对实体进行基本操作的服务接口,包括增删查改和分页查询等功能 + * + * @param Entity 实体类型 + * @param Id 实体标识符类型 + * @param Filter 查询过滤器类型 + * @since 2025-05-29 20:18:22 + * @author gewuyou + */ +interface CrudServiceSpec { + /** + * 根据ID获取实体 + * + * @param id 实体的唯一标识符 + * @return 返回实体,如果不存在则返回null + */ + fun findById(id: Id): Entity? + + /** + * 获取所有实体列表 + * + * @return 返回实体列表 + */ + fun list(): List + + /** + * 保存一个实体 + * + * @param entity 要保存的实体 + * @return 返回保存后的实体 + */ + fun save(entity: Entity): Entity + + /** + * 更新一个实体 + * + * @param entity 要更新的实体 + * @return 返回更新后的实体 + */ + fun update(entity: Entity): Entity + + /** + * 删除一个实体 + * + * @param id 要删除的实体的ID + */ + fun delete(id: Id) + + /** + * 批量删除实体 + * + * @param ids 要删除的实体的ID列表 + */ + fun delete(ids: List) + + /** + * 删除一个实体 + * + * @param entity 要删除的实体 + */ + fun delete(entity: Entity) + + /** + * 批量删除实体 + * + * @param entities 要删除的实体列表 + */ + fun delete(entities: List) + + /** + * 根据ID检查实体是否存在 + * + * @param id 实体的ID + * @return 如果实体存在返回true,否则返回false + */ + fun existsById(id: Id): Boolean + + + /** + * 批量保存实体 + * + * @param entities 要保存的实体列表 + * @return 返回保存后的实体列表 + */ + fun saveAll(entities: List): List + + /** + * 分页查询并映射结果 + * + * @param query 分页查询请求,包含查询参数和分页信息 + * @return 返回映射后的分页结果 + */ + fun page(query: PageQueryReq): PageResult + + /** + * 分页查询并使用给定函数映射结果 + * + * @param query 分页查询请求,包含查询参数和分页信息 + * @param mapping 映射函数,用于将实体映射为其他类型 + * @param 映射后的类型 + * @return 返回映射后的分页结果 + */ + fun pageMapped(query: PageQueryReq, mapping: (Entity) -> V): PageResult + + /** + * 根据过滤条件统计实体数量 + * + * @param filter 查询过滤器 + * @return 返回满足条件的实体数量 + */ + fun count(filter: Filter): Long +} diff --git a/forgeboot-webmvc/spec/src/main/kotlin/com/gewuyou/forgeboot/webmvc/spec/service/impl/CrudServiceImplSpec.kt b/forgeboot-webmvc/spec/src/main/kotlin/com/gewuyou/forgeboot/webmvc/spec/service/impl/CrudServiceImplSpec.kt new file mode 100644 index 0000000..44ed112 --- /dev/null +++ b/forgeboot-webmvc/spec/src/main/kotlin/com/gewuyou/forgeboot/webmvc/spec/service/impl/CrudServiceImplSpec.kt @@ -0,0 +1,191 @@ +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.toPageResult +import com.gewuyou.forgeboot.webmvc.dto.extension.toPageable +import com.gewuyou.forgeboot.webmvc.dto.request.PageQueryReq +import com.gewuyou.forgeboot.webmvc.spec.repository.CrudRepositorySpec +import com.gewuyou.forgeboot.webmvc.spec.service.CrudServiceSpec +import org.springframework.data.jpa.domain.Specification + + +/** + * CRUD 服务实现的抽象类,提供通用的数据访问操作。 + * + * @since 2025-05-29 20:37:27 + * @author gewuyou + */ +abstract class CrudServiceImplSpec( + private val repository: CrudRepositorySpec, +) : CrudServiceSpec { + + /** + * 根据实体的唯一标识符查找实体对象。 + * + * @param id 实体的唯一标识符 + * @return 如果存在则返回对应的实体对象,否则返回 null + */ + override fun findById(id: Id): Entity? { + return repository.findById(id).orElse(null) + } + + /** + * 执行分页查询并返回原始实体的分页结果。 + * + * @param query 分页查询请求,包含过滤条件和分页信息 + * @return 返回实体类型的分页结果 + */ + override fun page(query: PageQueryReq): PageResult { + return repository + .findAll( + buildSpecification(query), + query.toPageable() + ) + .toPageResult() + } + + /** + * 构建 JPA Specification 查询条件。 + * + * @param query 包含过滤条件的分页请求 + * @return 返回构建好的 Specification 查询条件 + */ + protected abstract fun buildSpecification(query: PageQueryReq): Specification + + /** + * 获取所有实体列表。 + * + * @return 返回实体列表 + */ + override fun list(): List { + return repository.findAll() + } + + /** + * 保存一个实体对象。 + * + * @param entity 要保存的实体对象 + * @return 返回保存后的实体对象 + */ + override fun save(entity: Entity): Entity { + return repository.save(entity) + } + + /** + * 根据实体 ID 删除指定的实体。 + * + * @param id 要删除的实体的唯一标识符 + */ + override fun delete(id: Id) { + repository.deleteById(id) + } + + /** + * 批量删除多个实体(根据 ID 列表)。 + * + * @param ids 要删除的实体 ID 列表 + */ + override fun delete(ids: List) { + repository.deleteAllById(ids) + } + + /** + * 删除指定的单个实体。 + * + * @param entity 要删除的实体对象 + */ + override fun delete(entity: Entity) { + repository.delete(entity) + } + + /** + * 批量删除多个实体。 + * + * @param entities 要删除的实体列表 + */ + override fun delete(entities: List) { + repository.deleteAll(entities) + } + + /** + * 将更新实体的非 null 字段合并到已有持久化对象上。 + * 此方法通常由 MapStruct 等映射工具实现。 + * + * @param entity 包含更新数据的实体对象 + * @param existing 数据库中已存在的实体对象 + * @return 返回合并后的实体对象 + */ + protected abstract fun merge(entity: Entity, existing: Entity): Entity + + /** + * 提取实体对象中的唯一标识符。 + * + * @param entity 实体对象 + * @return 返回实体的唯一标识符 + */ + protected abstract fun extractId(entity: Entity): Id + + /** + * 更新一个实体对象。 + * + * @param entity 包含更新数据的实体对象 + * @return 返回更新后的实体对象 + */ + override fun update(entity: Entity): Entity { + val existing = repository.findById(extractId(entity)) + .orElseThrow { IllegalArgumentException("Entity not found") } + + val merged = merge(entity, existing) + return repository.save(merged) + } + + /** + * 执行分页查询并将结果使用给定的映射函数转换为其他类型。 + * + * @param query 分页查询请求,包含查询参数和分页信息 + * @param mapping 映射函数,用于将实体映射为其他类型 + * @param 映射后的目标类型 + * @return 返回映射后的分页结果 + */ + override fun pageMapped( + query: PageQueryReq, + mapping: (Entity) -> V, + ): PageResult { + val page = repository.findAll( + buildSpecification(query), + query.toPageable() + ) + return page.toPageResult().map(mapping) + } + + /** + * 根据给定的过滤条件统计实体数量。 + * + * @param filter 查询过滤器 + * @return 返回满足条件的实体数量 + */ + override fun count(filter: Filter): Long { + return repository.count(buildSpecification(PageQueryReq().apply { this.filter = filter })) + } + + /** + * 检查具有指定 ID 的实体是否存在。 + * + * @param id 实体的唯一标识符 + * @return 如果实体存在返回 true,否则返回 false + */ + override fun existsById(id: Id): Boolean { + return repository.existsById(id) + } + + /** + * 批量保存多个实体对象。 + * + * @param entities 要保存的实体列表 + * @return 返回保存后的实体列表 + */ + override fun saveAll(entities: List): List { + return repository.saveAll(entities) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index a622bb1..b25bd63 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,7 +30,8 @@ include( ":forgeboot-webmvc:logger", ":forgeboot-webmvc:exception", ":forgeboot-webmvc:dto", - ":forgeboot-webmvc:validation" + ":forgeboot-webmvc:validation", + ":forgeboot-webmvc:spec" ) project(":forgeboot-webmvc").name = "forgeboot-webmvc-spring-boot-starter" project(":forgeboot-webmvc:version").name = "forgeboot-webmvc-version-spring-boot-starter" @@ -38,6 +39,7 @@ project(":forgeboot-webmvc:logger").name = "forgeboot-webmvc-logger-spring-boot- project(":forgeboot-webmvc:exception").name = "forgeboot-webmvc-exception-spring-boot-starter" project(":forgeboot-webmvc:dto").name = "forgeboot-webmvc-dto" project(":forgeboot-webmvc:validation").name = "forgeboot-validation" +project(":forgeboot-webmvc:spec").name = "forgeboot-webmvc-spec" //endregion //region module core