From 36d1b94fd89af4ee36546917743c9aabd562ebb8 Mon Sep 17 00:00:00 2001 From: SOR <3272194354@qq.com> Date: Tue, 18 Aug 2026 16:05:17 +0800 Subject: [PATCH] fix: page query --- src/backend/build.gradle.kts | 1 + .../itmanager/config/MybatisConfig.kt | 18 ++++++ .../itmanager/service/FileStorageService.kt | 61 ++++++++++++++++++ .../service/impl/BorrowRecordServiceImpl.kt | 3 +- .../itmanager/service/v1/FinanceApiService.kt | 61 ++++-------------- .../itmanager/service/v1/ItemsApiService.kt | 64 +++++-------------- .../itmanager/service/v1/UsagesApiService.kt | 64 +++++-------------- 7 files changed, 126 insertions(+), 146 deletions(-) create mode 100644 src/backend/src/main/kotlin/club/itstudio/itmanager/config/MybatisConfig.kt create mode 100644 src/backend/src/main/kotlin/club/itstudio/itmanager/service/FileStorageService.kt diff --git a/src/backend/build.gradle.kts b/src/backend/build.gradle.kts index a3e273f..77a64e1 100644 --- a/src/backend/build.gradle.kts +++ b/src/backend/build.gradle.kts @@ -23,6 +23,7 @@ repositories { dependencies { implementation("com.auth0:java-jwt:4.4.0") implementation("com.baomidou:mybatis-plus-spring-boot4-starter:3.5.15") + implementation("com.baomidou:mybatis-plus-jsqlparser:3.5.15") implementation("org.springframework.boot:spring-boot-starter-amqp") implementation("org.springframework.boot:spring-boot-starter-data-redis-reactive") implementation("org.springframework.boot:spring-boot-starter-json") diff --git a/src/backend/src/main/kotlin/club/itstudio/itmanager/config/MybatisConfig.kt b/src/backend/src/main/kotlin/club/itstudio/itmanager/config/MybatisConfig.kt new file mode 100644 index 0000000..b4daa46 --- /dev/null +++ b/src/backend/src/main/kotlin/club/itstudio/itmanager/config/MybatisConfig.kt @@ -0,0 +1,18 @@ +package club.itstudio.itmanager.config + +import com.baomidou.mybatisplus.annotation.DbType +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class MybatisConfig { + + @Bean + fun mybatisPlusInterceptor(): MybatisPlusInterceptor { + val interceptor = MybatisPlusInterceptor() + interceptor.addInnerInterceptor(PaginationInnerInterceptor(DbType.MARIADB)) + return interceptor + } +} diff --git a/src/backend/src/main/kotlin/club/itstudio/itmanager/service/FileStorageService.kt b/src/backend/src/main/kotlin/club/itstudio/itmanager/service/FileStorageService.kt new file mode 100644 index 0000000..b2073bd --- /dev/null +++ b/src/backend/src/main/kotlin/club/itstudio/itmanager/service/FileStorageService.kt @@ -0,0 +1,61 @@ +package club.itstudio.itmanager.service + +import club.itstudio.itmanager.utils.Const +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.codec.multipart.FilePart +import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.util.* + +@Service +class FileStorageService( + @Value($$"${app.file.data-path}") + private val dataPath: String +) { + data class StoredFile( + val path: Path, + val size: Long + ) + + private val logger = LoggerFactory.getLogger(this::class.java) + + fun saveImage(file: FilePart, subDir: String, entityId: Long): Mono { + val directory = Paths.get(dataPath, Const.FILE_DIR_IMAGE, subDir, entityId.toString()) + val savedName = generateSafeFileName(file.filename()) + val target = directory.resolve(savedName) + return Mono.fromCallable { Files.createDirectories(directory); null } + .then(file.transferTo(target)) + .then(Mono.fromCallable { StoredFile(target, Files.size(target)) }) + .subscribeOn(Schedulers.boundedElastic()) + } + + fun deleteFile(path: String?): Mono { + if (path.isNullOrBlank()) return Mono.empty() + return Mono.fromRunnable { + try { + Files.deleteIfExists(Paths.get(path)) + } catch (e: IOException) { + logger.warn("Failed to delete file: {}", path, e) + } + }.subscribeOn(Schedulers.boundedElastic()) + } + + fun generateSafeFileName(original: String?): String { + val extension = original + ?.substringAfterLast('.') + ?.lowercase() + ?.takeIf { + it.isNotBlank() && + it.length <= 10 && + it.all { ch -> ch.isLetterOrDigit() } + } + return if (extension != null) "${UUID.randomUUID()}.$extension" else UUID.randomUUID().toString() + } +} + diff --git a/src/backend/src/main/kotlin/club/itstudio/itmanager/service/impl/BorrowRecordServiceImpl.kt b/src/backend/src/main/kotlin/club/itstudio/itmanager/service/impl/BorrowRecordServiceImpl.kt index f88cbb4..b268263 100644 --- a/src/backend/src/main/kotlin/club/itstudio/itmanager/service/impl/BorrowRecordServiceImpl.kt +++ b/src/backend/src/main/kotlin/club/itstudio/itmanager/service/impl/BorrowRecordServiceImpl.kt @@ -3,6 +3,7 @@ package club.itstudio.itmanager.service.impl import club.itstudio.itmanager.entity.dto.BorrowRecord import club.itstudio.itmanager.mapper.BorrowRecordMapper import club.itstudio.itmanager.service.BorrowRecordService +import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper import com.baomidou.mybatisplus.extension.plugins.pagination.Page import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import org.slf4j.LoggerFactory @@ -32,7 +33,7 @@ class BorrowRecordServiceImpl : ServiceImpl(), ): Mono> = blockingCall { logger.info("Paging borrow records") - val wrapper = ktQuery() + val wrapper = KtQueryWrapper(BorrowRecord()) if (!keyword.isNullOrBlank()) { wrapper.like(BorrowRecord::borrower, keyword) } diff --git a/src/backend/src/main/kotlin/club/itstudio/itmanager/service/v1/FinanceApiService.kt b/src/backend/src/main/kotlin/club/itstudio/itmanager/service/v1/FinanceApiService.kt index d46bc55..1ac3cc8 100644 --- a/src/backend/src/main/kotlin/club/itstudio/itmanager/service/v1/FinanceApiService.kt +++ b/src/backend/src/main/kotlin/club/itstudio/itmanager/service/v1/FinanceApiService.kt @@ -9,22 +9,18 @@ import club.itstudio.itmanager.service.AccountService import club.itstudio.itmanager.service.AttachmentService import club.itstudio.itmanager.service.CategoryService import club.itstudio.itmanager.service.FinanceRecordService +import club.itstudio.itmanager.service.FileStorageService import club.itstudio.itmanager.utils.Const import club.itstudio.itmanager.utils.DateUtils.getCurrentDateTime import com.baomidou.mybatisplus.extension.plugins.pagination.Page import org.slf4j.LoggerFactory import org.springframework.amqp.core.AmqpTemplate -import org.springframework.beans.factory.annotation.Value import org.springframework.http.codec.multipart.FilePart import org.springframework.stereotype.Service import reactor.core.publisher.Flux import reactor.core.publisher.Mono import reactor.core.scheduler.Schedulers -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Paths import java.util.NoSuchElementException -import java.util.UUID @Service class FinanceApiService( @@ -33,9 +29,7 @@ class FinanceApiService( private val attachmentService: AttachmentService, private val accountService: AccountService, private val amqpTemplate: AmqpTemplate, - - @Value($$"${app.file.data-path}") - private val dataPath: String + private val fileStorageService: FileStorageService ) { private val logger = LoggerFactory.getLogger(this::class.java) @@ -171,41 +165,25 @@ class FinanceApiService( } private fun saveFinanceImage(financeRecordId: Long, file: FilePart): Mono { - val directory = Paths.get(dataPath, Const.FILE_DIR_IMAGE, "finance", financeRecordId.toString()) - val savedName = generateSafeFileName(file.filename()) - val target = directory.resolve(savedName) - return Mono.fromCallable { - Files.createDirectories(directory) - file.transferTo(target) - Attachment( - relatedTable = "finance_records", - relatedId = financeRecordId, - fileName = savedName, - filePath = target.toString(), - fileSize = Files.size(target), - contentType = file.headers().contentType?.toString(), - uploadedAt = getCurrentDateTime() - ) - }.subscribeOn(Schedulers.boundedElastic()) - } - - private fun generateSafeFileName(original: String?): String { - val extension = original - ?.substringAfterLast('.') - ?.lowercase() - ?.takeIf { - it.isNotBlank() && - it.length <= 10 && - it.all { ch -> ch.isLetterOrDigit() } + return fileStorageService.saveImage(file, "finance", financeRecordId) + .map { stored -> + Attachment( + relatedTable = "finance_records", + relatedId = financeRecordId, + fileName = stored.path.fileName.toString(), + filePath = stored.path.toString(), + fileSize = stored.size, + contentType = file.headers().contentType?.toString(), + uploadedAt = getCurrentDateTime() + ) } - return if (extension != null) "${UUID.randomUUID()}.$extension" else UUID.randomUUID().toString() } private fun cleanupFinanceAttachments(financeRecordId: Long): Mono { return attachmentService.listByRelated("finance_records", financeRecordId) .flatMap { attachments -> Flux.fromIterable(attachments) - .flatMap { deletePhysicalFile(it.filePath) } + .flatMap { fileStorageService.deleteFile(it.filePath) } .then(attachmentService.deleteByRelated("finance_records", financeRecordId)) } .onErrorResume { error -> @@ -236,15 +214,4 @@ class FinanceApiService( } } } - - private fun deletePhysicalFile(path: String?): Mono { - if (path.isNullOrBlank()) return Mono.empty() - return Mono.fromRunnable { - try { - Files.deleteIfExists(Paths.get(path)) - } catch (e: IOException) { - logger.warn("Failed to delete file: {}", path, e) - } - }.subscribeOn(Schedulers.boundedElastic()) - } } \ No newline at end of file diff --git a/src/backend/src/main/kotlin/club/itstudio/itmanager/service/v1/ItemsApiService.kt b/src/backend/src/main/kotlin/club/itstudio/itmanager/service/v1/ItemsApiService.kt index ce157f5..e3813ba 100644 --- a/src/backend/src/main/kotlin/club/itstudio/itmanager/service/v1/ItemsApiService.kt +++ b/src/backend/src/main/kotlin/club/itstudio/itmanager/service/v1/ItemsApiService.kt @@ -7,23 +7,18 @@ import club.itstudio.itmanager.entity.dto.ItemTransaction import club.itstudio.itmanager.entity.enums.TransactionType import club.itstudio.itmanager.service.AttachmentService import club.itstudio.itmanager.service.CategoryService +import club.itstudio.itmanager.service.FileStorageService import club.itstudio.itmanager.service.ItemService import club.itstudio.itmanager.service.ItemTransactionService import club.itstudio.itmanager.utils.Const import club.itstudio.itmanager.utils.DateUtils.getCurrentDateTime import com.baomidou.mybatisplus.extension.plugins.pagination.Page import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Value import org.springframework.http.codec.multipart.FilePart import org.springframework.stereotype.Service import reactor.core.publisher.Flux import reactor.core.publisher.Mono -import reactor.core.scheduler.Schedulers -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Paths import java.util.NoSuchElementException -import java.util.UUID @Service class ItemsApiService( @@ -31,9 +26,7 @@ class ItemsApiService( private val categoryService: CategoryService, private val attachmentService: AttachmentService, private val transactionService: ItemTransactionService, - - @Value($$"${app.file.data-path}") - private val dataPath: String + private val fileStorageService: FileStorageService ) { private val logger = LoggerFactory.getLogger(this::class.java) @@ -276,7 +269,7 @@ class ItemsApiService( .flatMap { attachment -> attachmentService.deleteAttachment(id).flatMap { success -> if (success) { - deletePhysicalFile(attachment.filePath).thenReturn("") + fileStorageService.deleteFile(attachment.filePath).thenReturn("") } else { Mono.just("item image delete failed.") } @@ -296,41 +289,25 @@ class ItemsApiService( } private fun saveItemImage(itemId: Long, file: FilePart): Mono { - val directory = Paths.get(dataPath, Const.FILE_DIR_IMAGE, "items", itemId.toString()) - val savedName = generateSafeFileName(file.filename()) - val target = directory.resolve(savedName) - return Mono.fromCallable { - Files.createDirectories(directory) - file.transferTo(target) - Attachment( - relatedTable = "items", - relatedId = itemId, - fileName = savedName, - filePath = target.toString(), - fileSize = Files.size(target), - contentType = file.headers().contentType?.toString(), - uploadedAt = getCurrentDateTime() - ) - }.subscribeOn(Schedulers.boundedElastic()) - } - - private fun generateSafeFileName(original: String?): String { - val extension = original - ?.substringAfterLast('.') - ?.lowercase() - ?.takeIf { - it.isNotBlank() && - it.length <= 10 && - it.all { ch -> ch.isLetterOrDigit() } + return fileStorageService.saveImage(file, "items", itemId) + .map { stored -> + Attachment( + relatedTable = "items", + relatedId = itemId, + fileName = stored.path.fileName.toString(), + filePath = stored.path.toString(), + fileSize = stored.size, + contentType = file.headers().contentType?.toString(), + uploadedAt = getCurrentDateTime() + ) } - return if (extension != null) "${UUID.randomUUID()}.$extension" else UUID.randomUUID().toString() } private fun cleanupItemAttachments(itemId: Long): Mono { return attachmentService.listByRelated("items", itemId) .flatMap { attachments -> Flux.fromIterable(attachments) - .flatMap { deletePhysicalFile(it.filePath) } + .flatMap { fileStorageService.deleteFile(it.filePath) } .then(attachmentService.deleteByRelated("items", itemId)) .thenReturn("") } @@ -339,15 +316,4 @@ class ItemsApiService( Mono.just("") } } - - private fun deletePhysicalFile(path: String?): Mono { - if (path.isNullOrBlank()) return Mono.empty() - return Mono.fromRunnable { - try { - Files.deleteIfExists(Paths.get(path)) - } catch (e: IOException) { - logger.warn("Failed to delete file: {}", path, e) - } - }.subscribeOn(Schedulers.boundedElastic()) - } } diff --git a/src/backend/src/main/kotlin/club/itstudio/itmanager/service/v1/UsagesApiService.kt b/src/backend/src/main/kotlin/club/itstudio/itmanager/service/v1/UsagesApiService.kt index d6aa810..71c52a8 100644 --- a/src/backend/src/main/kotlin/club/itstudio/itmanager/service/v1/UsagesApiService.kt +++ b/src/backend/src/main/kotlin/club/itstudio/itmanager/service/v1/UsagesApiService.kt @@ -4,29 +4,22 @@ import club.itstudio.itmanager.entity.dto.Attachment import club.itstudio.itmanager.entity.dto.BorrowRecord import club.itstudio.itmanager.service.AttachmentService import club.itstudio.itmanager.service.BorrowRecordService +import club.itstudio.itmanager.service.FileStorageService import club.itstudio.itmanager.utils.Const import club.itstudio.itmanager.utils.DateUtils.getCurrentDateTime import com.baomidou.mybatisplus.extension.plugins.pagination.Page import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Value import org.springframework.http.codec.multipart.FilePart import org.springframework.stereotype.Service import reactor.core.publisher.Flux import reactor.core.publisher.Mono -import reactor.core.scheduler.Schedulers -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Paths import java.util.NoSuchElementException -import java.util.UUID @Service class UsagesApiService( private val borrowRecordService: BorrowRecordService, private val attachmentService: AttachmentService, - - @Value($$"${app.file.data-path}") - private val dataPath: String + private val fileStorageService: FileStorageService ) { private val logger = LoggerFactory.getLogger(this::class.java) @@ -149,7 +142,7 @@ class UsagesApiService( .flatMap { attachment -> attachmentService.deleteAttachment(id).flatMap { success -> if (success) { - deletePhysicalFile(attachment.filePath).thenReturn("") + fileStorageService.deleteFile(attachment.filePath).thenReturn("") } else { Mono.just("usage image delete failed.") } @@ -160,41 +153,25 @@ class UsagesApiService( } private fun saveUsageImage(usageId: Long, file: FilePart): Mono { - val directory = Paths.get(dataPath, Const.FILE_DIR_IMAGE, "borrow_records", usageId.toString()) - val savedName = generateSafeFileName(file.filename()) - val target = directory.resolve(savedName) - return Mono.fromCallable { - Files.createDirectories(directory) - file.transferTo(target) - Attachment( - relatedTable = RELATED_TABLE, - relatedId = usageId, - fileName = savedName, - filePath = target.toString(), - fileSize = Files.size(target), - contentType = file.headers().contentType?.toString(), - uploadedAt = getCurrentDateTime() - ) - }.subscribeOn(Schedulers.boundedElastic()) - } - - private fun generateSafeFileName(original: String?): String { - val extension = original - ?.substringAfterLast('.') - ?.lowercase() - ?.takeIf { - it.isNotBlank() && - it.length <= 10 && - it.all { ch -> ch.isLetterOrDigit() } + return fileStorageService.saveImage(file, "borrow_records", usageId) + .map { stored -> + Attachment( + relatedTable = RELATED_TABLE, + relatedId = usageId, + fileName = stored.path.fileName.toString(), + filePath = stored.path.toString(), + fileSize = stored.size, + contentType = file.headers().contentType?.toString(), + uploadedAt = getCurrentDateTime() + ) } - return if (extension != null) "${UUID.randomUUID()}.$extension" else UUID.randomUUID().toString() } private fun cleanupUsageAttachments(usageId: Long): Mono { return attachmentService.listByRelated(RELATED_TABLE, usageId) .flatMap { attachments -> Flux.fromIterable(attachments) - .flatMap { deletePhysicalFile(it.filePath) } + .flatMap { fileStorageService.deleteFile(it.filePath) } .then(attachmentService.deleteByRelated(RELATED_TABLE, usageId)) .thenReturn("") } @@ -204,17 +181,6 @@ class UsagesApiService( } } - private fun deletePhysicalFile(path: String?): Mono { - if (path.isNullOrBlank()) return Mono.empty() - return Mono.fromRunnable { - try { - Files.deleteIfExists(Paths.get(path)) - } catch (e: IOException) { - logger.warn("Failed to delete file: {}", path, e) - } - }.subscribeOn(Schedulers.boundedElastic()) - } - companion object { private const val RELATED_TABLE = "borrow_records" private const val STATUS_BORROWED = "BORROWED"