diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/domain/File.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/domain/File.java deleted file mode 100644 index 93d5436..0000000 --- a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/domain/File.java +++ /dev/null @@ -1,18 +0,0 @@ -package schoolzone.schoolzone_backend_v2.domain.file.domain; - -import jakarta.persistence.*; -import lombok.*; - -@Entity -@Getter -@NoArgsConstructor -@Builder -@AllArgsConstructor -public class File { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false) - private String path; -} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/exception/FileNotFoundException.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/exception/FileNotFoundException.java deleted file mode 100644 index 1bf7df9..0000000 --- a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/exception/FileNotFoundException.java +++ /dev/null @@ -1,11 +0,0 @@ -package schoolzone.schoolzone_backend_v2.domain.file.exception; - -import schoolzone.schoolzone_backend_v2.global.error.exception.ErrorCode; -import schoolzone.schoolzone_backend_v2.global.error.exception.SchoolzoneException; - -public class FileNotFoundException extends SchoolzoneException { - public static final SchoolzoneException EXCEPTION = new FileNotFoundException(); - public FileNotFoundException() { - super(ErrorCode.FILE_NOT_FOUND); - } -} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/implementation/FileCreator.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/implementation/FileCreator.java new file mode 100644 index 0000000..07ecfd7 --- /dev/null +++ b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/implementation/FileCreator.java @@ -0,0 +1,33 @@ +package schoolzone.schoolzone_backend_v2.domain.file.implementation; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.multipart.MultipartFile; +import schoolzone.schoolzone_backend_v2.global.annotation.Implementation; +import schoolzone.schoolzone_backend_v2.global.config.file.FileProperties; +import schoolzone.schoolzone_backend_v2.global.error.exception.ErrorCode; +import schoolzone.schoolzone_backend_v2.global.error.exception.SchoolzoneException; + +import java.io.IOException; +import java.nio.file.*; +import java.util.UUID; + +@Implementation +@RequiredArgsConstructor +public class FileCreator { + private final FileProperties fileProperties; + + public String upload(MultipartFile file) { + return fileProperties.serverUrl() + save(file); + } + + private String save(MultipartFile file) { + String filename = UUID.randomUUID() + file.getOriginalFilename(); + Path path = fileProperties.path().resolve(filename); + try { + Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new SchoolzoneException(ErrorCode.FILE_SAVE_ERROR); + } + return path.toString(); + } +} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/implementation/FileReader.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/implementation/FileReader.java new file mode 100644 index 0000000..24bc624 --- /dev/null +++ b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/implementation/FileReader.java @@ -0,0 +1,33 @@ +package schoolzone.schoolzone_backend_v2.domain.file.implementation; + +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import schoolzone.schoolzone_backend_v2.global.annotation.Implementation; +import schoolzone.schoolzone_backend_v2.global.config.file.FileProperties; +import schoolzone.schoolzone_backend_v2.global.error.exception.ErrorCode; +import schoolzone.schoolzone_backend_v2.global.error.exception.SchoolzoneException; + +import java.net.MalformedURLException; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Implementation +@RequiredArgsConstructor +public class FileReader { + private final FileProperties fileProperties; + + public Resource loadFileAsResource(String fileName) { + Path path = Paths.get(fileProperties.path() + fileName).normalize(); + try { + Resource resource = new UrlResource(path.toUri()); + if (!resource.exists()) { + return resource; + } else { + throw new SchoolzoneException(ErrorCode.FILE_NOT_FOUND); + } + } catch (MalformedURLException e) { + throw new SchoolzoneException(ErrorCode.INVALID_URL_FORMAT); + } + } +} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/presentation/FileController.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/presentation/FileController.java index 70e3056..86da610 100644 --- a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/presentation/FileController.java +++ b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/presentation/FileController.java @@ -3,30 +3,35 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import schoolzone.schoolzone_backend_v2.domain.file.presentation.dto.response.FileResponse; -import schoolzone.schoolzone_backend_v2.domain.file.service.GetFileService; -import schoolzone.schoolzone_backend_v2.domain.file.service.PostFileService; - -import java.io.IOException; +import schoolzone.schoolzone_backend_v2.domain.file.presentation.dto.response.FileResponseDto; +import schoolzone.schoolzone_backend_v2.domain.file.presentation.dto.response.FileUrlResponseDto; +import schoolzone.schoolzone_backend_v2.domain.file.service.CommandFileService; +import schoolzone.schoolzone_backend_v2.domain.file.service.QueryFileService; @RestController @RequiredArgsConstructor @RequestMapping("/file") public class FileController { - private final PostFileService postFileService; - private final GetFileService getFileService; + private final CommandFileService commandFileService; + private final QueryFileService queryFileService; @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity postFile(@RequestPart(value = "file") MultipartFile file) { - return postFileService.execute(file); + @ResponseStatus(HttpStatus.CREATED) + public FileUrlResponseDto postFile(@RequestPart(value = "file") MultipartFile file) { + return FileUrlResponseDto.from(commandFileService.create(file)); } - @GetMapping - public ResponseEntity getFile(@RequestParam Long fileId, HttpServletRequest httpServletRequest) throws IOException { - return getFileService.execute(fileId, httpServletRequest); + @GetMapping("/{fileName}") + public ResponseEntity getFile(@PathVariable String fileName, HttpServletRequest request) { + FileResponseDto dto = queryFileService.getFile(fileName, request); + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(dto.contentType())) + .header("Content-Type", dto.contentType()) + .body(dto.resource()); } } diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/presentation/dto/response/FileResponse.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/presentation/dto/response/FileResponse.java deleted file mode 100644 index 7145d38..0000000 --- a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/presentation/dto/response/FileResponse.java +++ /dev/null @@ -1,5 +0,0 @@ -package schoolzone.schoolzone_backend_v2.domain.file.presentation.dto.response; - -public record FileResponse( - String url -) {} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/presentation/dto/response/FileResponseDto.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/presentation/dto/response/FileResponseDto.java new file mode 100644 index 0000000..d8c3f1c --- /dev/null +++ b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/presentation/dto/response/FileResponseDto.java @@ -0,0 +1,9 @@ +package schoolzone.schoolzone_backend_v2.domain.file.presentation.dto.response; + +import org.springframework.core.io.Resource; + +public record FileResponseDto( + Resource resource, + String contentType +) { +} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/presentation/dto/response/FileUrlResponseDto.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/presentation/dto/response/FileUrlResponseDto.java new file mode 100644 index 0000000..f26fc62 --- /dev/null +++ b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/presentation/dto/response/FileUrlResponseDto.java @@ -0,0 +1,9 @@ +package schoolzone.schoolzone_backend_v2.domain.file.presentation.dto.response; + +public record FileUrlResponseDto( + String url +) { + public static FileUrlResponseDto from(String url) { + return new FileUrlResponseDto(url); + } +} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/repository/FileRepository.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/repository/FileRepository.java deleted file mode 100644 index 4d80375..0000000 --- a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/repository/FileRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package schoolzone.schoolzone_backend_v2.domain.file.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import schoolzone.schoolzone_backend_v2.domain.file.domain.File; - -@Repository -public interface FileRepository extends JpaRepository { -} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/service/CommandFileService.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/service/CommandFileService.java new file mode 100644 index 0000000..3a1ddcd --- /dev/null +++ b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/service/CommandFileService.java @@ -0,0 +1,18 @@ +package schoolzone.schoolzone_backend_v2.domain.file.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import schoolzone.schoolzone_backend_v2.domain.file.implementation.FileCreator; + +@Service +@Transactional +@RequiredArgsConstructor +public class CommandFileService { + private final FileCreator fileCreator; + + public String create(MultipartFile file) { + return fileCreator.upload(file); + } +} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/service/GetFileService.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/service/GetFileService.java deleted file mode 100644 index 0ae0465..0000000 --- a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/service/GetFileService.java +++ /dev/null @@ -1,38 +0,0 @@ -package schoolzone.schoolzone_backend_v2.domain.file.service; - -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; -import org.springframework.core.io.Resource; -import org.springframework.core.io.UrlResource; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import schoolzone.schoolzone_backend_v2.domain.file.domain.File; -import schoolzone.schoolzone_backend_v2.domain.file.exception.FileNotFoundException; -import schoolzone.schoolzone_backend_v2.domain.file.repository.FileRepository; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; - -@Service -@RequiredArgsConstructor -public class GetFileService { - - private final FileRepository fileRepository; - - public ResponseEntity execute(Long fileId, HttpServletRequest httpServletRequest) throws IOException { - File file = fileRepository.findById(fileId).orElseThrow( - () -> FileNotFoundException.EXCEPTION - ); - - Path path = Paths.get(file.getPath()); - Resource resource = new UrlResource(path.toUri()); - String contentType = httpServletRequest.getServletContext().getMimeType(resource.getFile().getAbsolutePath()); - - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType(contentType)) - .header(contentType) - .body(resource); - } -} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/service/PostFileService.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/service/PostFileService.java deleted file mode 100644 index 01627df..0000000 --- a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/service/PostFileService.java +++ /dev/null @@ -1,30 +0,0 @@ -package schoolzone.schoolzone_backend_v2.domain.file.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; -import schoolzone.schoolzone_backend_v2.domain.file.domain.File; -import schoolzone.schoolzone_backend_v2.domain.file.presentation.dto.response.FileResponse; -import schoolzone.schoolzone_backend_v2.domain.file.repository.FileRepository; -import schoolzone.schoolzone_backend_v2.domain.file.util.FileSaveUtil; -import schoolzone.schoolzone_backend_v2.global.config.properties.ServerProperties; - -@Service -@RequiredArgsConstructor -public class PostFileService { - private final FileRepository fileRepository; - private final FileSaveUtil fileSaveUtil; - private final ServerProperties serverProperties; - - public ResponseEntity execute(MultipartFile file) { - String path = fileSaveUtil.save(file); - - File saveFile = File.builder() - .path(path) - .build(); - fileRepository.save(saveFile); - - return ResponseEntity.ok(new FileResponse(serverProperties.getUrl() + "/file?id=" + saveFile.getId())); - } -} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/service/QueryFileService.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/service/QueryFileService.java new file mode 100644 index 0000000..aef223c --- /dev/null +++ b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/service/QueryFileService.java @@ -0,0 +1,36 @@ +package schoolzone.schoolzone_backend_v2.domain.file.service; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; +import schoolzone.schoolzone_backend_v2.domain.file.implementation.FileReader; +import schoolzone.schoolzone_backend_v2.domain.file.presentation.dto.response.FileResponseDto; + +import java.io.IOException; + +@Service +@RequiredArgsConstructor +public class QueryFileService { + private final FileReader fileReader; + + public FileResponseDto getFile(String fileName, HttpServletRequest request) { + Resource resource = fileReader.loadFileAsResource(fileName); + String contentType = getContentType(resource, request); + return new FileResponseDto(resource, contentType); + } + + private String getContentType(Resource resource, HttpServletRequest request) { + String contentType = "application/octet-stream"; + try { + String mimeType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath()); + if (mimeType != null) { + contentType = mimeType; + } + } + catch (IOException ignored) { + } + return contentType; + } + +} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/util/FileSaveUtil.java b/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/util/FileSaveUtil.java deleted file mode 100644 index e88644a..0000000 --- a/src/main/java/schoolzone/schoolzone_backend_v2/domain/file/util/FileSaveUtil.java +++ /dev/null @@ -1,34 +0,0 @@ -package schoolzone.schoolzone_backend_v2.domain.file.util; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; -import schoolzone.schoolzone_backend_v2.global.config.properties.ServerProperties; -import schoolzone.schoolzone_backend_v2.global.error.exception.ErrorCode; -import schoolzone.schoolzone_backend_v2.global.error.exception.SchoolzoneException; - -import java.io.IOException; -import java.nio.file.*; -import java.util.UUID; - -@Component -@RequiredArgsConstructor -public class FileSaveUtil { - private final ServerProperties serverProperties; - - public String save(MultipartFile file) { - try { - String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename(); - Path path = serverProperties.getPath().resolve( - Paths.get(fileName) - .normalize()); - if (!Files.exists(serverProperties.getPath())) { - Files.createDirectories(serverProperties.getPath()); - } - Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING); - return String.format("%s/%s", serverProperties.getPath(), fileName); - } catch (IOException e) { - throw new SchoolzoneException(ErrorCode.FILE_SAVE_ERROR); - } - } -} \ No newline at end of file diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/global/config/file/FileProperties.java b/src/main/java/schoolzone/schoolzone_backend_v2/global/config/file/FileProperties.java new file mode 100644 index 0000000..45448bb --- /dev/null +++ b/src/main/java/schoolzone/schoolzone_backend_v2/global/config/file/FileProperties.java @@ -0,0 +1,21 @@ +package schoolzone.schoolzone_backend_v2.global.config.file; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +import java.nio.file.Path; +import java.nio.file.Paths; + +@ConfigurationProperties(prefix = "file") +public record FileProperties( + Path path, + String serverUrl +) { + @ConstructorBinding + public FileProperties(String path, String serverUrl) { + this( + Paths.get(path), + serverUrl + ); + } +} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/global/config/properties/ServerProperties.java b/src/main/java/schoolzone/schoolzone_backend_v2/global/config/properties/ServerProperties.java deleted file mode 100644 index 58e0df7..0000000 --- a/src/main/java/schoolzone/schoolzone_backend_v2/global/config/properties/ServerProperties.java +++ /dev/null @@ -1,19 +0,0 @@ -package schoolzone.schoolzone_backend_v2.global.config.properties; - -import lombok.Getter; -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.nio.file.Path; -import java.nio.file.Paths; - -@Getter -@ConfigurationProperties(prefix = "server") -public class ServerProperties { - private final Path path; - private final String url; - - public ServerProperties(String path, String url) { - this.path = Paths.get(path); - this.url = url; - } -} diff --git a/src/main/java/schoolzone/schoolzone_backend_v2/global/error/exception/ErrorCode.java b/src/main/java/schoolzone/schoolzone_backend_v2/global/error/exception/ErrorCode.java index fb1764e..f2c578a 100644 --- a/src/main/java/schoolzone/schoolzone_backend_v2/global/error/exception/ErrorCode.java +++ b/src/main/java/schoolzone/schoolzone_backend_v2/global/error/exception/ErrorCode.java @@ -35,7 +35,8 @@ public enum ErrorCode { // file FILE_SAVE_ERROR(500, "FILE-500-1", "파일 저장 중 오류가 발생했습니다."), - FILE_NOT_FOUND(404, "FILE-404-1", "파일을 찾을 수 없습니다."); + FILE_NOT_FOUND(404, "FILE-404-1", "파일을 찾을 수 없습니다."), + INVALID_URL_FORMAT(400, "FILE-400-1", "유효하지 않은 URL 형식입니다"); private final int status; private final String code;