feat: secret application.yaml #6
+122
-19
@@ -1,75 +1,178 @@
|
||||
package club.itstudio.itmanager.controller.v1
|
||||
|
||||
import club.itstudio.itmanager.entity.ApiResponse
|
||||
import club.itstudio.itmanager.entity.dto.Attachment
|
||||
import club.itstudio.itmanager.entity.dto.Category
|
||||
import club.itstudio.itmanager.entity.dto.Item
|
||||
import club.itstudio.itmanager.service.v1.ItemsApiService
|
||||
import club.itstudio.itmanager.utils.Const
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page
|
||||
import jakarta.validation.Valid
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.codec.multipart.FilePart
|
||||
import org.springframework.validation.annotation.Validated
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/items")
|
||||
class ItemsController {
|
||||
class ItemsController(
|
||||
private val service: ItemsApiService
|
||||
) {
|
||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
// items/ GET/POST/PUT/DELETE 物品 CRUD 操作
|
||||
@GetMapping("", "/")
|
||||
fun getItems(): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun getItems(
|
||||
@RequestParam(defaultValue = "1") pageNum: Long,
|
||||
@RequestParam(defaultValue = "10") pageSize: Long,
|
||||
@RequestParam(required = false) keyword: String?,
|
||||
@RequestParam(required = false) status: String?
|
||||
): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to list items")
|
||||
val page = Page<Item>(pageNum, pageSize)
|
||||
return service.getItems(page, keyword, status)
|
||||
.map { ApiResponse.success(it) }
|
||||
}
|
||||
|
||||
@PostMapping("", "/")
|
||||
fun createItem(): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun createItem(@RequestBody @Valid item: Item): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to create item")
|
||||
return service.createItem(item)
|
||||
.map(Const::messageHandler)
|
||||
}
|
||||
|
||||
@PutMapping("", "/")
|
||||
fun updateItem(): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun updateItem(@RequestBody @Valid item: Item): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to update item")
|
||||
return service.updateItem(item)
|
||||
.map(Const::messageHandler)
|
||||
}
|
||||
|
||||
@DeleteMapping("", "/")
|
||||
fun deleteItem(@RequestParam(required = false) id: String?): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun deleteItem(@RequestParam(required = false) id: Long?): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to delete item")
|
||||
return service.deleteItem(id)
|
||||
.map(Const::messageHandler)
|
||||
}
|
||||
|
||||
// items/{id}/upload_images/ POST 上传物品图片
|
||||
@PostMapping("/{id}/upload_images", "/{id}/upload_images/")
|
||||
fun uploadImages(@PathVariable("id") id: String): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun uploadImages(
|
||||
@PathVariable("id") id: Long,
|
||||
@RequestPart("files") files: Flux<FilePart>
|
||||
): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to upload images for item {}", id)
|
||||
return service.uploadImages(id, files)
|
||||
.map(Const::messageHandler)
|
||||
}
|
||||
|
||||
// items/{id}/set_primary_image/ POST 设置主图片
|
||||
@PostMapping("/{id}/set_primary_image", "/{id}/set_primary_image/")
|
||||
fun setPrimaryImage(@PathVariable("id") id: String): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun setPrimaryImage(
|
||||
@PathVariable("id") id: Long,
|
||||
@RequestParam imageId: Long
|
||||
): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to set primary image for item {}", id)
|
||||
return service.setPrimaryImage(id, imageId)
|
||||
.map(Const::messageHandler)
|
||||
}
|
||||
|
||||
// items/{id}/borrow/ POST 借用物品
|
||||
@PostMapping("/{id}/borrow", "/{id}/borrow/")
|
||||
fun borrowItem(@PathVariable("id") id: String): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun borrowItem(
|
||||
@PathVariable("id") id: Long,
|
||||
@RequestParam(defaultValue = "1") quantity: Int
|
||||
): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to borrow item {}", id)
|
||||
return service.borrowItem(id, quantity)
|
||||
.map(Const::messageHandler)
|
||||
}
|
||||
|
||||
// items/{id}/return_item/ POST 归还物品
|
||||
@PostMapping("/{id}/return_item", "/{id}/return_item/")
|
||||
fun returnItem(@PathVariable("id") id: String): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun returnItem(
|
||||
@PathVariable("id") id: Long,
|
||||
@RequestParam(defaultValue = "1") quantity: Int
|
||||
): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to return item {}", id)
|
||||
return service.returnItem(id, quantity)
|
||||
.map(Const::messageHandler)
|
||||
}
|
||||
|
||||
// items/available/ GET 获取可用物品列表
|
||||
@GetMapping("/available", "/available/")
|
||||
fun getAvailableItems(): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun getAvailableItems(): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to list available items")
|
||||
return service.getAvailableItems()
|
||||
.map { ApiResponse.success(it) }
|
||||
}
|
||||
|
||||
// items/in_use/ GET 获取使用中的物品列表
|
||||
@GetMapping("/in_use", "/in_use/")
|
||||
fun getInUseItems(): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun getInUseItems(): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to list in-use items")
|
||||
return service.getInUseItems()
|
||||
.map { ApiResponse.success(it) }
|
||||
}
|
||||
|
||||
// items/categories/ GET/POST/PUT/DELETE 物品类别 CRUD 操作
|
||||
@GetMapping("/categories", "/categories/")
|
||||
fun getItemCategories(): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun getItemCategories(): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to list item categories")
|
||||
return service.getItemCategories()
|
||||
.map { ApiResponse.success(it) }
|
||||
}
|
||||
|
||||
@PostMapping("/categories", "/categories/")
|
||||
fun createItemCategory(): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun createItemCategory(@RequestBody @Valid category: Category): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to create item category")
|
||||
return service.createItemCategory(category)
|
||||
.map(Const::messageHandler)
|
||||
}
|
||||
|
||||
@PutMapping("/categories", "/categories/")
|
||||
fun updateItemCategory(): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun updateItemCategory(@RequestBody @Valid category: Category): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to update item category")
|
||||
return service.updateItemCategory(category)
|
||||
.map(Const::messageHandler)
|
||||
}
|
||||
|
||||
@DeleteMapping("/categories", "/categories/")
|
||||
fun deleteItemCategory(@RequestParam(required = false) id: String?): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun deleteItemCategory(@RequestParam(required = false) id: Long?): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to delete item category")
|
||||
return service.deleteItemCategory(id)
|
||||
.map(Const::messageHandler)
|
||||
}
|
||||
|
||||
// items/images/ GET/POST/PUT/DELETE 物品图片 CRUD 操作
|
||||
@GetMapping("/images", "/images/")
|
||||
fun getItemImages(): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun getItemImages(): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to list item images")
|
||||
return service.getItemImages()
|
||||
.map { ApiResponse.success(it) }
|
||||
}
|
||||
|
||||
@PostMapping("/images", "/images/")
|
||||
fun createItemImage(): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun createItemImage(@RequestBody @Valid attachment: Attachment): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to create item image")
|
||||
return service.createItemImage(attachment)
|
||||
.map(Const::messageHandler)
|
||||
}
|
||||
|
||||
@PutMapping("/images", "/images/")
|
||||
fun updateItemImage(): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun updateItemImage(@RequestBody @Valid attachment: Attachment): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to update item image")
|
||||
return service.updateItemImage(attachment)
|
||||
.map(Const::messageHandler)
|
||||
}
|
||||
|
||||
@DeleteMapping("/images", "/images/")
|
||||
fun deleteItemImage(@RequestParam(required = false) id: String?): Mono<String> = Const.INTERNAL_ERROR_MONO
|
||||
fun deleteItemImage(@RequestParam(required = false) id: Long?): Mono<ApiResponse<Any?>> {
|
||||
logger.info("Trying to delete item image")
|
||||
return service.deleteItemImage(id)
|
||||
.map(Const::messageHandler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType
|
||||
import com.baomidou.mybatisplus.annotation.TableId
|
||||
import com.baomidou.mybatisplus.annotation.TableName
|
||||
import club.itstudio.itmanager.entity.DataCopy
|
||||
import club.itstudio.itmanager.entity.enums.TransactionType
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@@ -13,7 +14,7 @@ data class ItemTransaction(
|
||||
@TableId(type = IdType.AUTO)
|
||||
val id: Long? = null,
|
||||
val itemId: Long = 0,
|
||||
val transType: String = "", // IN / OUT / ADJUST / TRANSFER
|
||||
val transType: TransactionType = TransactionType.IN,
|
||||
val quantity: Int = 0,
|
||||
val fromLocation: String? = null,
|
||||
val toLocation: String? = null,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package club.itstudio.itmanager.entity.enums
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class TransactionType {
|
||||
@SerialName("IN")
|
||||
IN,
|
||||
|
||||
@SerialName("OUT")
|
||||
OUT,
|
||||
|
||||
@SerialName("ADJUST")
|
||||
ADJUST,
|
||||
|
||||
@SerialName("TRANSFER")
|
||||
TRANSFER,
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import club.itstudio.itmanager.entity.dto.Attachment
|
||||
import club.itstudio.itmanager.entity.dto.Category
|
||||
import club.itstudio.itmanager.entity.dto.Item
|
||||
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.ItemService
|
||||
@@ -15,12 +16,339 @@ 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
|
||||
class ItemsApiService(
|
||||
private val itemService: ItemService,
|
||||
private val categoryService: CategoryService,
|
||||
private val attachmentService: AttachmentService,
|
||||
private val transactionService: ItemTransactionService,
|
||||
|
||||
@Value($$"${app.file.data-path}")
|
||||
private val dataPath: String
|
||||
) {
|
||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
fun getItems(page: Page<Item>, keyword: String?, status: String?): Mono<Page<Item>> {
|
||||
logger.info("Listing items with keyword and status")
|
||||
return itemService.pageItems(page, keyword, status)
|
||||
}
|
||||
|
||||
fun createItem(item: Item): Mono<String> {
|
||||
logger.info("Creating item")
|
||||
val rightNow = getCurrentDateTime()
|
||||
return itemService.createItem(
|
||||
item.copy(
|
||||
id = null,
|
||||
createdAt = rightNow,
|
||||
updatedAt = rightNow
|
||||
)
|
||||
).flatMap { success ->
|
||||
if (success) Mono.just("")
|
||||
else Mono.just("item create failed.")
|
||||
}
|
||||
}
|
||||
|
||||
fun updateItem(item: Item): Mono<String> {
|
||||
logger.info("Updating item")
|
||||
if (item.id == null) return Mono.just("item id is required.")
|
||||
return itemService.updateItem(
|
||||
item.copy(updatedAt = getCurrentDateTime())
|
||||
).flatMap { success ->
|
||||
if (success) Mono.just("")
|
||||
else Mono.just("item update failed.")
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteItem(id: Long?): Mono<String> {
|
||||
logger.info("Deleting item with id {}", id)
|
||||
if (id == null) return Mono.just("item id is required.")
|
||||
return itemService.removeItemById(id).flatMap { success ->
|
||||
if (success) {
|
||||
cleanupItemAttachments(id)
|
||||
} else {
|
||||
Mono.just("item delete failed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun uploadImages(id: Long, files: Flux<FilePart>): Mono<String> {
|
||||
logger.info("Uploading images for item {}", id)
|
||||
return itemService.getItemById(id)
|
||||
.flatMap { item ->
|
||||
files.flatMap { file -> saveItemImage(id, file) }
|
||||
.collectList()
|
||||
.flatMap { attachments ->
|
||||
if (attachments.isEmpty()) {
|
||||
Mono.just("no image files provided.")
|
||||
} else {
|
||||
Flux.fromIterable(attachments)
|
||||
.flatMap { attachmentService.createAttachment(it) }
|
||||
.collectList()
|
||||
.flatMap { results ->
|
||||
if (results.any { !it }) {
|
||||
Mono.just("item image upload failed.")
|
||||
} else {
|
||||
setPrimaryImageIfBlank(item, attachments.first(), id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onErrorResume(NoSuchElementException::class.java) { Mono.just("item not found.") }
|
||||
.onErrorResume { Mono.just(Const.INTERNAL_ERROR_STR) }
|
||||
}
|
||||
|
||||
fun setPrimaryImage(id: Long, imageId: Long): Mono<String> {
|
||||
logger.info("Setting primary image for item {}", id)
|
||||
return itemService.getItemById(id)
|
||||
.flatMap { _ ->
|
||||
attachmentService.getAttachmentById(imageId)
|
||||
.flatMap { attachment ->
|
||||
if (attachment.relatedTable == "items" && attachment.relatedId == id) {
|
||||
val path = attachment.filePath
|
||||
if (path.isNullOrBlank()) {
|
||||
Mono.just("image file path is empty.")
|
||||
} else {
|
||||
itemService.setPrimaryImage(id, path).flatMap { success ->
|
||||
if (success) Mono.just("")
|
||||
else Mono.just("set primary image failed.")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Mono.just("image does not belong to this item.")
|
||||
}
|
||||
}
|
||||
.onErrorResume(NoSuchElementException::class.java) { _ -> Mono.just("image not found.") }
|
||||
}
|
||||
.onErrorResume(NoSuchElementException::class.java) { _ -> Mono.just("item not found.") }
|
||||
.onErrorResume { _ -> Mono.just(Const.INTERNAL_ERROR_STR) }
|
||||
}
|
||||
|
||||
fun borrowItem(id: Long, quantity: Int): Mono<String> {
|
||||
logger.info("Borrowing item {} with quantity {}", id, quantity)
|
||||
if (quantity <= 0) return Mono.just("quantity must be positive.")
|
||||
return itemService.getItemById(id)
|
||||
.flatMap { item ->
|
||||
itemService.borrowItem(id, quantity)
|
||||
.flatMap { success ->
|
||||
if (success) {
|
||||
transactionService.createTransaction(
|
||||
ItemTransaction(
|
||||
itemId = id,
|
||||
transType = TransactionType.OUT,
|
||||
quantity = quantity,
|
||||
fromLocation = item.location,
|
||||
createdAt = getCurrentDateTime()
|
||||
)
|
||||
).onErrorResume { error ->
|
||||
logger.warn("Failed to record OUT transaction for item {}: {}", id, error.message)
|
||||
Mono.just(false)
|
||||
}.thenReturn("")
|
||||
} else {
|
||||
Mono.just("item available quantity is not enough.")
|
||||
}
|
||||
}
|
||||
}
|
||||
.onErrorResume(NoSuchElementException::class.java) { Mono.just("item not found.") }
|
||||
.onErrorResume { Mono.just(Const.INTERNAL_ERROR_STR) }
|
||||
}
|
||||
|
||||
fun returnItem(id: Long, quantity: Int): Mono<String> {
|
||||
logger.info("Returning item {} with quantity {}", id, quantity)
|
||||
if (quantity <= 0) return Mono.just("quantity must be positive.")
|
||||
return itemService.getItemById(id)
|
||||
.flatMap { item ->
|
||||
itemService.returnItem(id, quantity)
|
||||
.flatMap { success ->
|
||||
if (success) {
|
||||
transactionService.createTransaction(
|
||||
ItemTransaction(
|
||||
itemId = id,
|
||||
transType = TransactionType.IN,
|
||||
quantity = quantity,
|
||||
toLocation = item.location,
|
||||
createdAt = getCurrentDateTime()
|
||||
)
|
||||
).onErrorResume { error ->
|
||||
logger.warn("Failed to record IN transaction for item {}: {}", id, error.message)
|
||||
Mono.just(false)
|
||||
}.thenReturn("")
|
||||
} else {
|
||||
Mono.just("item cannot be returned with the given quantity.")
|
||||
}
|
||||
}
|
||||
}
|
||||
.onErrorResume(NoSuchElementException::class.java) { Mono.just("item not found.") }
|
||||
.onErrorResume { Mono.just(Const.INTERNAL_ERROR_STR) }
|
||||
}
|
||||
|
||||
fun getAvailableItems(): Mono<List<Item>> {
|
||||
logger.info("Listing available items")
|
||||
return itemService.listAvailableItems()
|
||||
}
|
||||
|
||||
fun getInUseItems(): Mono<List<Item>> {
|
||||
logger.info("Listing in-use items")
|
||||
return itemService.listInUseItems()
|
||||
}
|
||||
|
||||
fun getItemCategories(): Mono<List<Category>> {
|
||||
logger.info("Listing item categories")
|
||||
return categoryService.listAllCategories()
|
||||
}
|
||||
|
||||
fun createItemCategory(category: Category): Mono<String> {
|
||||
logger.info("Creating item category")
|
||||
return categoryService.createCategory(
|
||||
category.copy(
|
||||
id = null,
|
||||
createdAt = getCurrentDateTime(),
|
||||
updatedAt = getCurrentDateTime()
|
||||
)
|
||||
).flatMap { success ->
|
||||
if (success) Mono.just("")
|
||||
else Mono.just("category create failed.")
|
||||
}
|
||||
}
|
||||
|
||||
fun updateItemCategory(category: Category): Mono<String> {
|
||||
logger.info("Updating item category")
|
||||
if (category.id == null) return Mono.just("category id is required.")
|
||||
return categoryService.updateCategory(
|
||||
category.copy(updatedAt = getCurrentDateTime())
|
||||
).flatMap { success ->
|
||||
if (success) Mono.just("")
|
||||
else Mono.just("category update failed.")
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteItemCategory(id: Long?): Mono<String> {
|
||||
logger.info("Deleting item category with id {}", id)
|
||||
if (id == null) return Mono.just("category id is required.")
|
||||
return categoryService.deleteCategory(id).flatMap { success ->
|
||||
if (success) Mono.just("")
|
||||
else Mono.just("category delete failed.")
|
||||
}
|
||||
}
|
||||
|
||||
fun getItemImages(): Mono<List<Attachment>> {
|
||||
logger.info("Listing item images")
|
||||
return attachmentService.exportAllAttachments()
|
||||
.map { list -> list.filter { it.relatedTable == "items" } }
|
||||
}
|
||||
|
||||
fun createItemImage(attachment: Attachment): Mono<String> {
|
||||
logger.info("Creating item image")
|
||||
return attachmentService.createAttachment(
|
||||
attachment.copy(
|
||||
id = null,
|
||||
uploadedAt = getCurrentDateTime()
|
||||
)
|
||||
).flatMap { success ->
|
||||
if (success) Mono.just("")
|
||||
else Mono.just("item image create failed.")
|
||||
}
|
||||
}
|
||||
|
||||
fun updateItemImage(attachment: Attachment): Mono<String> {
|
||||
logger.info("Updating item image")
|
||||
if (attachment.id == null) return Mono.just("item image id is required.")
|
||||
return Mono.fromCallable { attachmentService.updateById(attachment) }
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.flatMap { success ->
|
||||
if (success) Mono.just("")
|
||||
else Mono.just("item image update failed.")
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteItemImage(id: Long?): Mono<String> {
|
||||
logger.info("Deleting item image with id {}", id)
|
||||
if (id == null) return Mono.just("item image id is required.")
|
||||
return attachmentService.getAttachmentById(id)
|
||||
.flatMap { attachment ->
|
||||
attachmentService.deleteAttachment(id).flatMap { success ->
|
||||
if (success) {
|
||||
deletePhysicalFile(attachment.filePath).thenReturn("")
|
||||
} else {
|
||||
Mono.just("item image delete failed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
.onErrorResume(NoSuchElementException::class.java) { Mono.just("item image not found.") }
|
||||
.onErrorResume { Mono.just(Const.INTERNAL_ERROR_STR) }
|
||||
}
|
||||
|
||||
private fun setPrimaryImageIfBlank(item: Item, attachment: Attachment, id: Long): Mono<String> {
|
||||
if (!item.imagePath.isNullOrBlank()) return Mono.just("")
|
||||
val path = attachment.filePath ?: return Mono.just("")
|
||||
return itemService.setPrimaryImage(id, path).flatMap { success ->
|
||||
if (success) Mono.just("")
|
||||
else Mono.just("set primary image failed.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveItemImage(itemId: Long, file: FilePart): Mono<Attachment> {
|
||||
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 if (extension != null) "${UUID.randomUUID()}.$extension" else UUID.randomUUID().toString()
|
||||
}
|
||||
|
||||
private fun cleanupItemAttachments(itemId: Long): Mono<String> {
|
||||
return attachmentService.listByRelated("items", itemId)
|
||||
.flatMap { attachments ->
|
||||
Flux.fromIterable(attachments)
|
||||
.flatMap { deletePhysicalFile(it.filePath) }
|
||||
.then(attachmentService.deleteByRelated("items", itemId))
|
||||
.thenReturn("")
|
||||
}
|
||||
.onErrorResume { error ->
|
||||
logger.warn("Failed to clean up attachments for item {}: {}", itemId, error.message)
|
||||
Mono.just("")
|
||||
}
|
||||
}
|
||||
|
||||
private fun deletePhysicalFile(path: String?): Mono<Void> {
|
||||
if (path.isNullOrBlank()) return Mono.empty()
|
||||
return Mono.fromRunnable<Void> {
|
||||
try {
|
||||
Files.deleteIfExists(Paths.get(path))
|
||||
} catch (e: IOException) {
|
||||
logger.warn("Failed to delete file: {}", path, e)
|
||||
}
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user