diff --git a/src/main/java/com/project/bumawiki/global/s3/implement/ImageCreator.java b/src/main/java/com/project/bumawiki/domain/file/implementation/FileCreator.java similarity index 66% rename from src/main/java/com/project/bumawiki/global/s3/implement/ImageCreator.java rename to src/main/java/com/project/bumawiki/domain/file/implementation/FileCreator.java index f47165b5..134e3004 100644 --- a/src/main/java/com/project/bumawiki/global/s3/implement/ImageCreator.java +++ b/src/main/java/com/project/bumawiki/domain/file/implementation/FileCreator.java @@ -1,23 +1,26 @@ -package com.project.bumawiki.global.s3.implement; +package com.project.bumawiki.domain.file.implementation; import java.io.IOException; import java.util.UUID; -import org.springframework.stereotype.Service; +import com.project.bumawiki.global.annotation.Implementation; +import com.project.bumawiki.global.error.exception.BumawikiException; + +import com.project.bumawiki.global.error.exception.ErrorCode; + import org.springframework.web.multipart.MultipartFile; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; -import com.project.bumawiki.global.config.s3.S3Bucket; -import com.project.bumawiki.global.s3.exception.S3SaveException; +import com.project.bumawiki.global.config.file.r2.R2Bucket; import lombok.RequiredArgsConstructor; -@Service +@Implementation @RequiredArgsConstructor -public class ImageCreator { - private final S3Bucket s3Bucket; +public class FileCreator { + private final R2Bucket r2Bucket; private final AmazonS3 amazonS3; public String create(MultipartFile multipartFile) { @@ -25,7 +28,7 @@ public String create(MultipartFile multipartFile) { try { PutObjectRequest request = new PutObjectRequest( - s3Bucket.getS3Bucket(), + r2Bucket.getS3Bucket(), fileName, multipartFile.getInputStream(), getMetadata(multipartFile) @@ -33,10 +36,10 @@ public String create(MultipartFile multipartFile) { amazonS3.putObject(request); } catch (IOException e) { - throw new S3SaveException(); + throw new BumawikiException(ErrorCode.S3_SAVE_EXCEPTION); } - return s3Bucket.getReadUrl() + fileName; + return r2Bucket.getReadUrl() + fileName; } private String createFileName(MultipartFile multipartFile) { diff --git a/src/main/java/com/project/bumawiki/domain/file/presentation/FileController.java b/src/main/java/com/project/bumawiki/domain/file/presentation/FileController.java new file mode 100644 index 00000000..7aada964 --- /dev/null +++ b/src/main/java/com/project/bumawiki/domain/file/presentation/FileController.java @@ -0,0 +1,51 @@ +package com.project.bumawiki.domain.file.presentation; + +import com.project.bumawiki.domain.file.presentation.dto.FileResponseDto; +import com.project.bumawiki.domain.file.presentation.dto.R2FileResponseDto; +import com.project.bumawiki.domain.file.service.CommandFileService; + +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.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; + +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/file") +public class FileController { + private final CommandFileService commandFileService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public R2FileResponseDto upload(@RequestPart("file") MultipartFile file) { + return new R2FileResponseDto( + commandFileService.uploadFile(file) + ); + } + + @GetMapping("/display/{docsName}/{fileName}") + public ResponseEntity displayImage( + @PathVariable String fileName, + @PathVariable String docsName, + HttpServletRequest request + ) { + FileResponseDto fileResponseDto = commandFileService.getFile(docsName, fileName, request); + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(fileResponseDto.contentType())) + .header("Content-Type", fileResponseDto.contentType()) + .body(fileResponseDto.resource()); + } + +} diff --git a/src/main/java/com/project/bumawiki/domain/file/presentation/dto/FileResponseDto.java b/src/main/java/com/project/bumawiki/domain/file/presentation/dto/FileResponseDto.java new file mode 100644 index 00000000..d62ce4c7 --- /dev/null +++ b/src/main/java/com/project/bumawiki/domain/file/presentation/dto/FileResponseDto.java @@ -0,0 +1,9 @@ +package com.project.bumawiki.domain.file.presentation.dto; + +import org.springframework.core.io.Resource; + +public record FileResponseDto( + Resource resource, + String contentType +) { +} diff --git a/src/main/java/com/project/bumawiki/domain/file/presentation/dto/R2FileResponseDto.java b/src/main/java/com/project/bumawiki/domain/file/presentation/dto/R2FileResponseDto.java new file mode 100644 index 00000000..f72ef976 --- /dev/null +++ b/src/main/java/com/project/bumawiki/domain/file/presentation/dto/R2FileResponseDto.java @@ -0,0 +1,7 @@ +package com.project.bumawiki.domain.file.presentation.dto; + + +public record R2FileResponseDto( + String url +) { +} diff --git a/src/main/java/com/project/bumawiki/domain/file/service/CommandFileService.java b/src/main/java/com/project/bumawiki/domain/file/service/CommandFileService.java new file mode 100644 index 00000000..94f4a546 --- /dev/null +++ b/src/main/java/com/project/bumawiki/domain/file/service/CommandFileService.java @@ -0,0 +1,68 @@ +package com.project.bumawiki.domain.file.service; + +import com.project.bumawiki.domain.file.implementation.FileCreator; +import com.project.bumawiki.domain.file.presentation.dto.FileResponseDto; +import com.project.bumawiki.global.config.file.local.FileProperties; +import com.project.bumawiki.global.error.exception.BumawikiException; + +import com.project.bumawiki.global.error.exception.ErrorCode; + +import jakarta.servlet.http.HttpServletRequest; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import lombok.RequiredArgsConstructor; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Service +@RequiredArgsConstructor +public class CommandFileService { + private final FileCreator fileCreator; + private final FileProperties fileProperties; + + public String uploadFile(MultipartFile file) { + return fileCreator.create(file); + } + + public FileResponseDto getFile(String docsName, String fileName, HttpServletRequest request) { + Resource resource = loadFileAsResource(docsName, fileName); + String contentType = getContentType(request, resource); + return new FileResponseDto(resource, contentType); + } + + private Resource loadFileAsResource(String docsName, String fileName) { + Path uploadPath = Paths.get(fileProperties.path(), docsName); + try { + Path filePath = uploadPath.resolve(fileName).normalize(); + Resource resource = new UrlResource(filePath.toUri()); + if (resource.exists()) { + return resource; + } else { + throw new BumawikiException(ErrorCode.IMAGE_NOT_FOUND_EXCEPTION); + } + } catch (MalformedURLException ex) { + throw new BumawikiException(ErrorCode.MALFORMED_URL); + } + } + + private String getContentType(HttpServletRequest request, Resource resource) { + 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/com/project/bumawiki/domain/image/ImageController.java b/src/main/java/com/project/bumawiki/domain/image/ImageController.java deleted file mode 100644 index 58391f33..00000000 --- a/src/main/java/com/project/bumawiki/domain/image/ImageController.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.project.bumawiki.domain.image; - -import java.io.IOException; - -import org.springframework.core.io.Resource; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.project.bumawiki.domain.image.service.ImageService; - -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -@RestController -@RequestMapping("/api/image") -public class ImageController { - private final ImageService imageService; - - @GetMapping("/display/{docsName}/{fileName}") - public ResponseEntity displayImage(@PathVariable String fileName, - @PathVariable String docsName, - HttpServletRequest request) { - // Load file as Resource - Resource resource = imageService.loadFileAsResource(docsName, fileName); - // Try to determine file's content type - String contentType = null; - try { - contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath()); - } catch (IOException ex) { - } - if (contentType == null) { - contentType = "application/octet-stream"; - } - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType(contentType)) - .header("Content-Type", contentType) - .body(resource); - } - -} diff --git a/src/main/java/com/project/bumawiki/domain/image/exception/NoImageException.java b/src/main/java/com/project/bumawiki/domain/image/exception/NoImageException.java deleted file mode 100644 index ebf1d88c..00000000 --- a/src/main/java/com/project/bumawiki/domain/image/exception/NoImageException.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.project.bumawiki.domain.image.exception; - -import com.project.bumawiki.global.error.exception.BumawikiException; -import com.project.bumawiki.global.error.exception.ErrorCode; - -public class NoImageException extends BumawikiException { - - public static final NoImageException EXCEPTION = new NoImageException(ErrorCode.NO_IMAGE); - - public NoImageException(ErrorCode errorCode) { - super(errorCode); - } -} diff --git a/src/main/java/com/project/bumawiki/domain/image/presentation/FileStorageProperties.java b/src/main/java/com/project/bumawiki/domain/image/presentation/FileStorageProperties.java deleted file mode 100644 index 5c0729e9..00000000 --- a/src/main/java/com/project/bumawiki/domain/image/presentation/FileStorageProperties.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.project.bumawiki.domain.image.presentation; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -@ConfigurationProperties(prefix = "image") -public class FileStorageProperties { - private final String path; -} diff --git a/src/main/java/com/project/bumawiki/domain/image/service/ImageService.java b/src/main/java/com/project/bumawiki/domain/image/service/ImageService.java deleted file mode 100644 index 4b0839fb..00000000 --- a/src/main/java/com/project/bumawiki/domain/image/service/ImageService.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.project.bumawiki.domain.image.service; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.security.SecureRandom; -import java.util.ArrayList; - -import org.springframework.core.io.Resource; -import org.springframework.core.io.UrlResource; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; -import org.springframework.web.multipart.MultipartFile; - -import com.project.bumawiki.domain.image.exception.NoImageException; - -@Service -public class ImageService { - private static final String uploadPath = "/home/insert/Desktop/image/"; - - private static String getRandomStr() { - int leftLimit = 97; // letter 'a' - int rightLimit = 122; // letter 'z' - int targetStringLength = 10; - SecureRandom random = new SecureRandom(); - String generatedString = random.ints(leftLimit, rightLimit + 1) - .limit(targetStringLength) - .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) - .toString(); - System.out.println("random : " + generatedString); - return generatedString; - } - - public String saveFile(MultipartFile file, String userName) throws IOException { - - String randomStr = getRandomStr(); - String fileName = randomStr + StringUtils.cleanPath(file.getOriginalFilename()); - - Path uploadPath = Paths.get(ImageService.uploadPath + userName); - if (!Files.exists(uploadPath)) { - Files.createDirectories(uploadPath); - } - - try (InputStream inputStream = file.getInputStream()) { - Path filePath = uploadPath.resolve(fileName); - Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING); - return fileName; - } catch (IOException ioe) { - throw new IOException("Could not save image file: " + fileName, ioe); - } - } - - public ArrayList getFileUrl(MultipartFile[] files, String docsName) throws IOException { - ArrayList imageUrl = new ArrayList<>(); - for (MultipartFile file : files) { - String fileName = saveFile(file, docsName); - imageUrl.add("<>"); - } - return imageUrl; - } - - public Resource loadFileAsResource(String docsName, String fileName) { - Path uploadPath = Paths.get(ImageService.uploadPath, docsName); - try { - Path filePath = uploadPath.resolve(fileName).normalize(); - Resource resource = new UrlResource(filePath.toUri()); - if (resource.exists()) { - return resource; - } else { - throw NoImageException.EXCEPTION; - } - } catch (MalformedURLException ex) { - System.out.println(docsName + "/" + fileName); - throw NoImageException.EXCEPTION; - } - } -} diff --git a/src/main/java/com/project/bumawiki/global/config/file/local/FileProperties.java b/src/main/java/com/project/bumawiki/global/config/file/local/FileProperties.java new file mode 100644 index 00000000..9aed86f7 --- /dev/null +++ b/src/main/java/com/project/bumawiki/global/config/file/local/FileProperties.java @@ -0,0 +1,9 @@ +package com.project.bumawiki.global.config.file.local; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "file") +public record FileProperties( + String path +) { +} diff --git a/src/main/java/com/project/bumawiki/global/config/s3/S3Bucket.java b/src/main/java/com/project/bumawiki/global/config/file/r2/R2Bucket.java similarity index 78% rename from src/main/java/com/project/bumawiki/global/config/s3/S3Bucket.java rename to src/main/java/com/project/bumawiki/global/config/file/r2/R2Bucket.java index 2cb8acbc..ace30586 100644 --- a/src/main/java/com/project/bumawiki/global/config/s3/S3Bucket.java +++ b/src/main/java/com/project/bumawiki/global/config/file/r2/R2Bucket.java @@ -1,4 +1,4 @@ -package com.project.bumawiki.global.config.s3; +package com.project.bumawiki.global.config.file.r2; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; @@ -7,7 +7,7 @@ @Getter @Configuration -public class S3Bucket { +public class R2Bucket { @Value("${aws.s3.bucket}") private String s3Bucket; diff --git a/src/main/java/com/project/bumawiki/global/config/s3/S3Config.java b/src/main/java/com/project/bumawiki/global/config/file/r2/R2Config.java similarity index 93% rename from src/main/java/com/project/bumawiki/global/config/s3/S3Config.java rename to src/main/java/com/project/bumawiki/global/config/file/r2/R2Config.java index 5d57f9ff..51b4dcb8 100644 --- a/src/main/java/com/project/bumawiki/global/config/s3/S3Config.java +++ b/src/main/java/com/project/bumawiki/global/config/file/r2/R2Config.java @@ -1,4 +1,4 @@ -package com.project.bumawiki.global.config.s3; +package com.project.bumawiki.global.config.file.r2; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -10,7 +10,7 @@ import com.amazonaws.services.s3.AmazonS3ClientBuilder; @Configuration -class S3Config { +class R2Config { @Value("${aws.s3.access-key}") private String accessKey; diff --git a/src/main/java/com/project/bumawiki/global/error/exception/ErrorCode.java b/src/main/java/com/project/bumawiki/global/error/exception/ErrorCode.java index 5095289e..c2dcc341 100644 --- a/src/main/java/com/project/bumawiki/global/error/exception/ErrorCode.java +++ b/src/main/java/com/project/bumawiki/global/error/exception/ErrorCode.java @@ -48,9 +48,9 @@ public enum ErrorCode { INTERNAL_SERVER_ERROR(500, "SERVER-500-1", "Internal Server Error"), //Image, - NO_IMAGE(400, "IMG-400-1", "이미지가 없습니다."), IMAGE_NOT_FOUND_EXCEPTION(404, "IMG-404-1", "이미지를 조회할 수 없습니다."), S3_SAVE_EXCEPTION(500, "IMG-500-1", "S3 Save Exception"), + MALFORMED_URL(500, "IMG-500-2", "URL 형식이 올바르지 않습니다."), //coin, MONEY_NOT_ENOUGH(400, "COIN-400-1", "돈이 충분하지 않습니다."), diff --git a/src/main/java/com/project/bumawiki/global/s3/controller/S3Controller.java b/src/main/java/com/project/bumawiki/global/s3/controller/S3Controller.java deleted file mode 100644 index 3bfce250..00000000 --- a/src/main/java/com/project/bumawiki/global/s3/controller/S3Controller.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.project.bumawiki.global.s3.controller; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import com.project.bumawiki.global.s3.controller.dto.ImageResponse; -import com.project.bumawiki.global.s3.service.CommandImageService; - -import lombok.RequiredArgsConstructor; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/s3") -public class S3Controller { - private final CommandImageService commandImageService; - - @PostMapping - @ResponseStatus(HttpStatus.CREATED) - public ImageResponse upload(@RequestPart("file") MultipartFile file) { - return ImageResponse.from( - commandImageService.uploadImage(file) - ); - } -} diff --git a/src/main/java/com/project/bumawiki/global/s3/controller/dto/ImageResponse.java b/src/main/java/com/project/bumawiki/global/s3/controller/dto/ImageResponse.java deleted file mode 100644 index 02259ad5..00000000 --- a/src/main/java/com/project/bumawiki/global/s3/controller/dto/ImageResponse.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.project.bumawiki.global.s3.controller.dto; - -import lombok.Getter; - -@Getter -public final class ImageResponse { - private final String url; - - public ImageResponse( - String url - ) { - this.url = url; - } - - public static ImageResponse from(String url) { - return new ImageResponse(url); - } - - public String url() { - return url; - } - -} diff --git a/src/main/java/com/project/bumawiki/global/s3/exception/ImageNotFoundException.java b/src/main/java/com/project/bumawiki/global/s3/exception/ImageNotFoundException.java deleted file mode 100644 index e2e56565..00000000 --- a/src/main/java/com/project/bumawiki/global/s3/exception/ImageNotFoundException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.project.bumawiki.global.s3.exception; - -import com.project.bumawiki.global.error.exception.BumawikiException; -import com.project.bumawiki.global.error.exception.ErrorCode; - -public class ImageNotFoundException extends BumawikiException { - public ImageNotFoundException() { - super(ErrorCode.IMAGE_NOT_FOUND_EXCEPTION); - } -} diff --git a/src/main/java/com/project/bumawiki/global/s3/exception/S3SaveException.java b/src/main/java/com/project/bumawiki/global/s3/exception/S3SaveException.java deleted file mode 100644 index c371fceb..00000000 --- a/src/main/java/com/project/bumawiki/global/s3/exception/S3SaveException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.project.bumawiki.global.s3.exception; - -import com.project.bumawiki.global.error.exception.BumawikiException; -import com.project.bumawiki.global.error.exception.ErrorCode; - -public class S3SaveException extends BumawikiException { - public S3SaveException() { - super(ErrorCode.S3_SAVE_EXCEPTION); - } -} diff --git a/src/main/java/com/project/bumawiki/global/s3/service/CommandImageService.java b/src/main/java/com/project/bumawiki/global/s3/service/CommandImageService.java deleted file mode 100644 index 258da517..00000000 --- a/src/main/java/com/project/bumawiki/global/s3/service/CommandImageService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.project.bumawiki.global.s3.service; - -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import com.project.bumawiki.global.s3.implement.ImageCreator; - -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class CommandImageService { - private final ImageCreator imageCreator; - - public String uploadImage(MultipartFile file) { - return imageCreator.create(file); - } -} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 18cc0597..4712deb6 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -48,7 +48,7 @@ bsm: secret-key: ${BSM_SECRET_KEY} redirect-url: ${BSM_REDIRECT_URL} -image: +file: path: ${SAVING_URL} #S3 diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index c43680c3..dbb63712 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -63,5 +63,5 @@ aws: end-point-url: ${S3_ENDPOINT_URL} read-url: ${S3_READ_URL} -image: +file: path: ${SAVING_URL}