Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package com.wafflestudio.csereal.common.entity
import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity

interface MainImageAttachable {
val mainImage: MainImageEntity?
var mainImage: MainImageEntity?
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import com.wafflestudio.csereal.core.seminar.database.SeminarEntity
import jakarta.persistence.*

@Entity(name = "attachment")
@Table(uniqueConstraints = [ UniqueConstraint(columnNames = ["filename", "directory"])])
class AttachmentEntity(
var isDeleted: Boolean? = false,

@Column(unique = true)
val filename: String,

val directory: String? = null,

val attachmentsOrder: Int,
val size: Long,

Expand Down Expand Up @@ -57,4 +60,8 @@ class AttachmentEntity(
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "council_file_id")
var councilFile: CouncilFileEntity? = null
) : BaseTimeEntity()
) : BaseTimeEntity() {
fun filePath(): String {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 수정해주세요보다는 방향성을 고민해보면 좋을 거 같아서 코멘트 남깁니다.
현재 각 entity에서 구현한 attachable 인터페이스는 결국 패턴이 "{attachment,mainImage}/{domain}"의 형태로 고정되는거 같고,
각 도메인의 entity가 '어떤 경로에 저장되어야 한다'라는 정보까지 들고 있을 필요가 있을지는 고민되요.

위에 문제를 벗어나기 위해서 각 entity에 method를 정의한다기보다는
(fileType, attachable) -> String 의 식으로 resource/common 패키지 하위에 method를 추가하는 것도 한가지 방법일 거 같아요.

다만 어떻게 하든 장단점은 있어보이네요..!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

원래 attachable -> String 형식으로 메소드를 AttachmentService에 만들었는데, 저장 위치는 attachable이라면 가져야 할 속성이라 생각해 이렇게 수정했습니다.
다시 생각해보니 저장 위치를 하나의 함수에서 관리해줘야 한눈에 보기 편할 것 같기도 합니다.
수정해보겠습니다.

return if (directory.isNullOrBlank()) return filename else "$directory/$filename"
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.wafflestudio.csereal.core.resource.attachment.service

import com.wafflestudio.csereal.common.CserealException
import com.wafflestudio.csereal.core.resource.directory.Directory
import com.wafflestudio.csereal.common.entity.AttachmentAttachable
import com.wafflestudio.csereal.common.properties.EndpointProperties
import com.wafflestudio.csereal.core.about.database.AboutEntity
Expand All @@ -21,6 +22,7 @@ import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.multipart.MultipartFile
import java.lang.invoke.WrongMethodTypeException
import java.nio.file.Files
import java.nio.file.Paths

Expand Down Expand Up @@ -53,22 +55,25 @@ class AttachmentServiceImpl(
private val eventPublisher: ApplicationEventPublisher
) : AttachmentService {
override fun uploadAttachmentInLabEntity(labEntity: LabEntity, requestAttachment: MultipartFile): AttachmentDto {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

흠 얘도 추후에 추상화가 되면 좋겠군요..

Files.createDirectories(Paths.get(path))
val directory = "attachment/" + Directory.LAB.toString().lowercase()
val uploadDir = Paths.get(path, directory)
Files.createDirectories(uploadDir)

val timeMillis = System.currentTimeMillis()

val filename = "${timeMillis}_${requestAttachment.originalFilename}"
val totalFilename = path + filename
val saveFile = Paths.get(totalFilename)
val saveFile = Paths.get(path, directory, filename)
requestAttachment.transferTo(saveFile)

val attachment = AttachmentEntity(
filename = filename,
directory = directory,
attachmentsOrder = 1,
size = requestAttachment.size
)

labEntity.pdf = attachment
attachment.lab = labEntity
attachmentRepository.save(attachment)

return AttachmentDto(
Expand All @@ -83,20 +88,22 @@ class AttachmentServiceImpl(
contentEntityType: AttachmentAttachable,
requestAttachments: List<MultipartFile>
): List<AttachmentDto> {
Files.createDirectories(Paths.get(path))
val directory = attachmentDirectoryOf(contentEntityType)
val uploadDir = Paths.get(path, directory)
Files.createDirectories(uploadDir)

val attachmentsList = mutableListOf<AttachmentDto>()

for ((index, requestAttachment) in requestAttachments.withIndex()) {
val timeMillis = System.currentTimeMillis()

val filename = "${timeMillis}_${requestAttachment.originalFilename}"
val totalFilename = path + filename
val saveFile = Paths.get(totalFilename)
val saveFile = uploadDir.resolve(filename)
requestAttachment.transferTo(saveFile)

val attachment = AttachmentEntity(
filename = filename,
directory = directory,
attachmentsOrder = index + 1,
size = requestAttachment.size
)
Expand Down Expand Up @@ -124,7 +131,7 @@ class AttachmentServiceImpl(
attachmentDto = AttachmentResponse(
id = attachment.id,
name = attachment.filename.substringAfter("_"),
url = "${endpointProperties.backend}/v1/file/${attachment.filename}",
url = "${endpointProperties.backend}/v1/file/${attachment.filePath()}",
bytes = attachment.size
)
}
Expand All @@ -142,7 +149,7 @@ class AttachmentServiceImpl(
val attachmentDto = AttachmentResponse(
id = attachment.id,
name = attachment.filename.substringAfter("_"),
url = "${endpointProperties.backend}/v1/file/${attachment.filename}",
url = "${endpointProperties.backend}/v1/file/${attachment.filePath()}",
bytes = attachment.size
)
list.add(attachmentDto)
Expand Down Expand Up @@ -170,7 +177,7 @@ class AttachmentServiceImpl(

@Transactional
override fun deleteAttachment(attachment: AttachmentEntity) {
val fileDirectory = path + attachment.filename
val fileDirectory = path + attachment.filePath()
attachmentRepository.delete(attachment)
eventPublisher.publishEvent(FileDeleteEvent(fileDirectory))
}
Expand Down Expand Up @@ -217,6 +224,22 @@ class AttachmentServiceImpl(
contentEntity.attachments.add(attachment)
attachment.councilFile = contentEntity
}

else -> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 메서드도 main image처럼 추후 간단하게 할 수 있다면 좋겠네용...

throw WrongMethodTypeException("파일을 엔티티에 연결할 수 없습니다")
}
}
}

private fun attachmentDirectoryOf(contentEntityType: AttachmentAttachable): String {
return "attachment/" + when (contentEntityType) {
is NewsEntity -> Directory.NEWS.toString().lowercase()
is NoticeEntity -> Directory.NOTICE.toString().lowercase()
is SeminarEntity -> Directory.SEMINAR.toString().lowercase()
is AboutEntity -> Directory.ABOUT.toString().lowercase()
is AcademicsEntity -> Directory.ACADEMICS.toString().lowercase()
is CouncilFileEntity -> Directory.COUNCIL.toString().lowercase()
else -> ""
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ class FileController(
private val uploadPath: String,
private val endpointProperties: EndpointProperties
) {

@GetMapping("/{filename:.+}")
@GetMapping("/{*filepath}")
fun serveFile(
@PathVariable filename: String,
@PathVariable filepath: String,
request: HttpServletRequest
): ResponseEntity<Resource> {
val file = Paths.get(uploadPath, filename)
val file = Paths.get(uploadPath, filepath)
val resource = UrlResource(file.toUri())

if (resource.exists() || resource.isReadable) {
Expand All @@ -41,6 +40,7 @@ class FileController(

headers.contentType = MediaType.parseMediaType(contentType ?: "application/octet-stream")

val filename = filepath.substringAfterLast("/")
val originalFilename = filename.substringAfter("_")
val encodedFilename = URLEncoder.encode(originalFilename, UTF_8.toString()).replace("+", "%20")

Expand Down Expand Up @@ -69,7 +69,7 @@ class FileController(
val saveFile = Paths.get(totalFilename)
file.transferTo(saveFile)

val imageUrl = "${endpointProperties.backend}/v1/file/$filename"
val imageUrl = "/v1/file/$filename"

results.add(
UploadFileInfo(
Expand All @@ -93,9 +93,11 @@ class FileController(
}

@PreAuthorize("hasRole('STAFF')")
@DeleteMapping("/{filename:.+}")
fun deleteFile(@PathVariable filename: String): ResponseEntity<Any> {
val file = Paths.get(uploadPath, filename)
@DeleteMapping("/{*filepath}")
fun deleteFile(
@PathVariable filepath: String
): ResponseEntity<Any> {
val file = Paths.get(uploadPath, filepath)

if (Files.exists(file)) {
Files.delete(file)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.wafflestudio.csereal.core.resource.directory

enum class Directory {
LAB, // for attachment
NEWS, // for attachment, mainImage
NOTICE, // for attachment
SEMINAR, // for attachment, mainImage
ABOUT, // for attachment, mainImage
ACADEMICS, // for attachment
COUNCIL, // for attachment, mainImage
PROFESSOR, // for mainImage
STAFF, // for mainImage
RESEARCH, // for mainImage
RECRUIT // for mainImage
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ import com.wafflestudio.csereal.common.entity.BaseTimeEntity
import jakarta.persistence.*

@Entity(name = "mainImage")
@Table(uniqueConstraints = [ UniqueConstraint(columnNames = ["filename", "directory"])])
class MainImageEntity(
var isDeleted: Boolean? = false,

@Column(unique = true)
val filename: String,

val directory: String? = null,

val imagesOrder: Int,
val size: Long

) : BaseTimeEntity()
) : BaseTimeEntity() {
fun filePath(): String {
return if (directory.isNullOrBlank()) return filename else "$directory/$filename"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.wafflestudio.csereal.core.news.database.NewsEntity
import com.wafflestudio.csereal.core.recruit.database.RecruitEntity
import com.wafflestudio.csereal.core.research.database.ResearchEntity
import com.wafflestudio.csereal.core.resource.common.event.FileDeleteEvent
import com.wafflestudio.csereal.core.resource.directory.Directory
import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageRepository
import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity
import com.wafflestudio.csereal.core.resource.mainImage.dto.MainImageDto
Expand All @@ -21,7 +22,6 @@ import org.springframework.transaction.annotation.Transactional
import org.springframework.web.multipart.MultipartFile
import org.apache.commons.io.FilenameUtils
import org.springframework.context.ApplicationEventPublisher
import java.lang.invoke.WrongMethodTypeException
import java.nio.file.Files
import java.nio.file.Paths

Expand Down Expand Up @@ -50,7 +50,9 @@ class MainImageServiceImpl(
contentEntityType: MainImageAttachable,
requestImage: MultipartFile
): MainImageDto {
Files.createDirectories(Paths.get(path))
val directory = mainImageDirectoryOf(contentEntityType)
val uploadDir = Paths.get(path, directory)
Files.createDirectories(uploadDir)

val extension = FilenameUtils.getExtension(requestImage.originalFilename)

Expand All @@ -61,17 +63,17 @@ class MainImageServiceImpl(
val timeMillis = System.currentTimeMillis()

val filename = "${timeMillis}_${requestImage.originalFilename}"
val totalFilename = path + filename
val saveFile = Paths.get(totalFilename)
val saveFile = uploadDir.resolve(filename)
requestImage.transferTo(saveFile)

val mainImage = MainImageEntity(
filename = filename,
directory = directory,
imagesOrder = 1,
size = requestImage.size
)

connectMainImageToEntity(contentEntityType, mainImage)
contentEntityType.mainImage = mainImage
mainImageRepository.save(mainImage)

return MainImageDto(
Expand All @@ -83,59 +85,32 @@ class MainImageServiceImpl(

// TODO: `MainImageEntity`의 메서드로 refactoring하기.
@Transactional
override fun createImageURL(mainImage: MainImageEntity?): String? {
return if (mainImage != null) {
"${endpointProperties.backend}/v1/file/${mainImage.filename}"
override fun createImageURL(image: MainImageEntity?): String? {
return if (image != null) {
"${endpointProperties.backend}/v1/file/${image.filePath()}"
} else {
null
}
}

@Transactional
override fun removeImage(image: MainImageEntity) {
val fileDirectory = path + image.filename
val fileDirectory = path + image.filePath()
mainImageRepository.delete(image)
eventPublisher.publishEvent(FileDeleteEvent(fileDirectory))
}

// TODO: 각 entity의 interface로 refactoring하기.
private fun connectMainImageToEntity(contentEntity: MainImageAttachable, mainImage: MainImageEntity) {
when (contentEntity) {
is NewsEntity -> {
contentEntity.mainImage = mainImage
}

is SeminarEntity -> {
contentEntity.mainImage = mainImage
}

is AboutEntity -> {
contentEntity.mainImage = mainImage
}

is ProfessorEntity -> {
contentEntity.mainImage = mainImage
}

is StaffEntity -> {
contentEntity.mainImage = mainImage
}

is ResearchEntity -> {
contentEntity.mainImage = mainImage
}

is RecruitEntity -> {
contentEntity.mainImage = mainImage
}

is CouncilEntity -> {
contentEntity.mainImage = mainImage
}

else -> {
throw WrongMethodTypeException("해당하는 엔티티가 없습니다")
}
private fun mainImageDirectoryOf(contentEntityType: MainImageAttachable): String {
return "mainImage/" + when (contentEntityType) {
is NewsEntity -> Directory.NEWS.toString().lowercase()
is SeminarEntity -> Directory.SEMINAR.toString().lowercase()
is AboutEntity -> Directory.ABOUT.toString().lowercase()
is ProfessorEntity -> Directory.PROFESSOR.toString().lowercase()
is StaffEntity -> Directory.STAFF.toString().lowercase()
is ResearchEntity -> Directory.RESEARCH.toString().lowercase()
is RecruitEntity -> Directory.RECRUIT.toString().lowercase()
is CouncilEntity -> Directory.COUNCIL.toString().lowercase()
else -> ""
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ALTER TABLE attachment
ADD COLUMN directory VARCHAR(255) NULL,
DROP CONSTRAINT UQ_attachment_filename,
ADD CONSTRAINT UQ_attachment_directory_filename UNIQUE (directory, filename);

ALTER TABLE main_image
ADD COLUMN directory VARCHAR(255) NULL,
DROP CONSTRAINT UQ_main_image_filename,
ADD CONSTRAINT UQ_main_image_directory_filename UNIQUE (directory, filename);
Loading