diff --git a/src/main/java/balancetalk/global/exception/ErrorCode.java b/src/main/java/balancetalk/global/exception/ErrorCode.java index 60b2d78ee..84b3004b0 100644 --- a/src/main/java/balancetalk/global/exception/ErrorCode.java +++ b/src/main/java/balancetalk/global/exception/ErrorCode.java @@ -32,6 +32,7 @@ public enum ErrorCode { NOT_FOUND_VOTE(NOT_FOUND, "해당 게시글에서 투표한 기록이 존재하지 않습니다."), NOT_FOUND_BOOKMARK(NOT_FOUND, "해당 게시글에서 북마크한 기록이 존재하지 않습니다."), NOT_FOUND_COMMENT(NOT_FOUND, "존재하지 않는 댓글입니다."), + NOT_FOUND_DIRECTORY(NOT_FOUND, "존재하지 않는 디렉토리입니다."), // 409 ALREADY_VOTE(CONFLICT, "투표는 한 번만 가능합니다."), diff --git a/src/main/java/balancetalk/module/file/application/FileService.java b/src/main/java/balancetalk/module/file/application/FileService.java new file mode 100644 index 000000000..44a378458 --- /dev/null +++ b/src/main/java/balancetalk/module/file/application/FileService.java @@ -0,0 +1,62 @@ +package balancetalk.module.file.application; + +import balancetalk.global.exception.BalanceTalkException; +import balancetalk.module.file.domain.File; +import balancetalk.module.file.domain.FileRepository; +import balancetalk.module.file.domain.FileType; +import balancetalk.module.file.dto.FileDto; +import jakarta.servlet.ServletContext; +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.UUID; + +import static balancetalk.global.exception.ErrorCode.NOT_FOUND_DIRECTORY; + +@Service +@RequiredArgsConstructor +public class FileService { + private final FileRepository fileRepository; + private final ServletContext servletContext; + + // 파일 업로드 + @Transactional + public File uploadFile(MultipartFile file) throws IOException { + String uploadDir = "C:\\Users\\King\\Desktop"; // TODO : 경로 configuration 파일로 빼기 + String originalFileName = file.getOriginalFilename(); + String storedFileName = UUID.randomUUID().toString().replace("-", "") + "_" + originalFileName; + String path = Paths.get(uploadDir, storedFileName).toString(); + String contentType = file.getContentType(); + FileType fileType = convertMimeTypeToFileType(contentType); + + FileDto fileDto = FileDto.of(file, storedFileName, path, fileType); + File saveFile = fileDto.toEntity(); + + Files.copy(file.getInputStream(), Paths.get(path), StandardCopyOption.REPLACE_EXISTING); + return fileRepository.save(saveFile); + } + + private FileType convertMimeTypeToFileType(String mimeType) { + if (mimeType == null) { + throw new IllegalArgumentException("MIME 타입은 NULL이 될 수 없습니다."); + } + + return Arrays.stream(FileType.values()) + .filter(type -> type.getMimeType().equalsIgnoreCase(mimeType)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("지원하지 않는 파일 타입 : " + mimeType)); + } +} diff --git a/src/main/java/balancetalk/module/file/domain/File.java b/src/main/java/balancetalk/module/file/domain/File.java index 11f0b10ab..26cb11796 100644 --- a/src/main/java/balancetalk/module/file/domain/File.java +++ b/src/main/java/balancetalk/module/file/domain/File.java @@ -1,15 +1,7 @@ package balancetalk.module.file.domain; import balancetalk.module.Notice; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import jakarta.persistence.*; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; @@ -33,7 +25,7 @@ public class File { @Column(nullable = false, length = 50) private String uploadName; // 사용자가 업로드한 파일명 - @Size(max = 50) + @Size(max = 100) @Column(length = 50, unique = true) private String storedName; // 서버 내부에서 관리하는 파일명 diff --git a/src/main/java/balancetalk/module/file/domain/FileRepository.java b/src/main/java/balancetalk/module/file/domain/FileRepository.java new file mode 100644 index 000000000..ea2fab71c --- /dev/null +++ b/src/main/java/balancetalk/module/file/domain/FileRepository.java @@ -0,0 +1,6 @@ +package balancetalk.module.file.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FileRepository extends JpaRepository { +} diff --git a/src/main/java/balancetalk/module/file/dto/FileDto.java b/src/main/java/balancetalk/module/file/dto/FileDto.java index 63f4a8246..3cda51adb 100644 --- a/src/main/java/balancetalk/module/file/dto/FileDto.java +++ b/src/main/java/balancetalk/module/file/dto/FileDto.java @@ -4,13 +4,19 @@ import balancetalk.module.file.domain.FileType; import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; +import org.springframework.web.multipart.MultipartFile; + @Data @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class FileDto { + @Schema(description = "파일 id", example = "1") + private Long id; @Schema(description = "사용자가 업로드한 파일명", example = "사진1") private String uploadName; + @Schema(description = "서버에 저장되는 파일명", example = "d23d2dqwt1251asbds사진1") + private String storedName; @Schema(description = "업로드한 파일의 경로", example = "/...") private String path; @Schema(description = "업로드한 파일 확장자", example = "JPEG") @@ -35,4 +41,14 @@ public static FileDto fromEntity(File file) { .size(file.getSize()) .build(); } + + public static FileDto of(MultipartFile file, String storedFileName, String path, FileType fileType) { + return FileDto.builder() + .uploadName(file.getOriginalFilename()) + .storedName(storedFileName) + .path(path) + .type(fileType) + .size(file.getSize()) + .build(); + } } diff --git a/src/main/java/balancetalk/module/file/presentation/FileController.java b/src/main/java/balancetalk/module/file/presentation/FileController.java new file mode 100644 index 000000000..6ca698371 --- /dev/null +++ b/src/main/java/balancetalk/module/file/presentation/FileController.java @@ -0,0 +1,25 @@ +package balancetalk.module.file.presentation; + +import balancetalk.module.file.application.FileService; +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/files") +public class FileController { + private final FileService fileService; + + @ResponseStatus(HttpStatus.CREATED) + @PostMapping("/upload") + public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException { + fileService.uploadFile(file); + return "파일이 업로드되었습니다."; + } +} \ No newline at end of file