diff --git a/backend/src/main/java/com/worlabel/domain/image/controller/ImageController.java b/backend/src/main/java/com/worlabel/domain/image/controller/ImageController.java index 6830e09..ad51b40 100644 --- a/backend/src/main/java/com/worlabel/domain/image/controller/ImageController.java +++ b/backend/src/main/java/com/worlabel/domain/image/controller/ImageController.java @@ -33,9 +33,9 @@ public class ImageController { @SwaggerApiError({ErrorCode.BAD_REQUEST, ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR}) public void uploadImage( @CurrentUser final Integer memberId, - @PathVariable("folder_id") final Integer folderId, @PathVariable("project_id") final Integer projectId, - @Parameter(name = "폴더에 추가 할 이미지 리스트", description = "MultiPartFile을 imageList로 추가해준다.", example = "") @RequestBody final List imageList) { + @PathVariable("folder_id") final Integer folderId, + @Parameter(name = "폴더에 추가 할 이미지 리스트", description = "MultiPartFile을 imageList로 추가해준다.", example = "") @RequestPart final List imageList) { imageService.uploadImageList(imageList, folderId, projectId, memberId); } @@ -45,8 +45,9 @@ public class ImageController { @SwaggerApiError({ErrorCode.BAD_REQUEST, ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR}) public void uploadFolder( @Parameter(name = "폴더", description = "MultiPartFile을 폴더나 zip으로 추가해준다.", example = "") @RequestPart final MultipartFile folderZip, - @PathVariable("project_id") Integer projectId) throws IOException { - imageService.uploadFolderWithImages(folderZip, projectId); + @PathVariable("project_id") final Integer projectId, + @PathVariable("folder_id") final Integer folderId) throws IOException { + imageService.uploadFolderWithImages(folderZip, projectId, folderId); } @GetMapping("/{image_id}") diff --git a/backend/src/main/java/com/worlabel/domain/image/entity/Image.java b/backend/src/main/java/com/worlabel/domain/image/entity/Image.java index b75614c..f8beb21 100644 --- a/backend/src/main/java/com/worlabel/domain/image/entity/Image.java +++ b/backend/src/main/java/com/worlabel/domain/image/entity/Image.java @@ -40,12 +40,6 @@ public class Image extends BaseEntity { @Column(name = "image_extenstion", nullable = false, length = 10) private String extension; - /** - * 이미지 순서 - */ - @Column(name = "image_order", nullable = false) - private Integer order; - /** * 이미지 레이블링 상태 */ @@ -61,16 +55,15 @@ public class Image extends BaseEntity { @JsonIgnore private Folder folder; - private Image(final String imageTitle, final String imageKey, final String extension, final Integer order, final Folder folder) { + private Image(final String imageTitle, final String imageKey, final String extension, final Folder folder) { this.title = imageTitle; this.imageKey = imageKey; this.extension = extension; - this.order = order; this.folder = folder; } - public static Image of(final String imageTitle, final String imageKey,final String extension, final Integer order, final Folder folder) { - return new Image(imageTitle, imageKey, extension, order, folder); + public static Image of(final String imageTitle, final String imageKey, final String extension, final Folder folder) { + return new Image(imageTitle, imageKey, extension, folder); } public void moveFolder(final Folder moveFolder) { @@ -81,11 +74,11 @@ public class Image extends BaseEntity { this.status = labelStatus; } - public String getImagePath(){ + public String getImagePath() { return this.imageKey + "." + this.extension; } - public String getDataPath(){ + public String getDataPath() { return this.imageKey + ".json"; } } diff --git a/backend/src/main/java/com/worlabel/domain/image/repository/ImageRepository.java b/backend/src/main/java/com/worlabel/domain/image/repository/ImageRepository.java index b00baae..b3f5a07 100644 --- a/backend/src/main/java/com/worlabel/domain/image/repository/ImageRepository.java +++ b/backend/src/main/java/com/worlabel/domain/image/repository/ImageRepository.java @@ -10,15 +10,19 @@ import java.util.Optional; public interface ImageRepository extends JpaRepository { - // todo N + 1 발생할듯 @Query("select i from Image i " + - "where i.folder.project.id = :projectId") + "join fetch i.folder f " + + "join fetch f.project p " + + "where p.id = :projectId") List findImagesByProjectId(@Param("projectId") Integer projectId); + Optional findByIdAndFolderIdAndFolderProjectId(Long imageId, Integer folderId, Integer projectId); @Query("SELECT i FROM Image i " + + "JOIN FETCH i.folder f " + + "JOIN FETCH f.project p " + "WHERE i.id = :imageId " + - "AND i.folder.project.id = :projectId ") + "AND p.id = :projectId") Optional findByIdAndProjectId(@Param("imageId") Long imageId, @Param("projectId") Integer projectId); } diff --git a/backend/src/main/java/com/worlabel/domain/image/service/ImageService.java b/backend/src/main/java/com/worlabel/domain/image/service/ImageService.java index 17ccb13..fd4c07d 100644 --- a/backend/src/main/java/com/worlabel/domain/image/service/ImageService.java +++ b/backend/src/main/java/com/worlabel/domain/image/service/ImageService.java @@ -40,19 +40,20 @@ public class ImageService { private final FolderRepository folderRepository; private final ProjectRepository projectRepository; - private static int orderCount = 0; - /** * 이미지 리스트 업로드 */ @CheckPrivilege(value = PrivilegeType.EDITOR) public void uploadImageList(final List imageList, final Integer folderId, final Integer projectId, final Integer memberId) { - Folder folder = getFolder(folderId); - for (int order = 0; order < imageList.size(); order++) { - MultipartFile file = imageList.get(order); + Folder folder = null; + if (folderId != 0) { + folder = getFolder(folderId); + } + + for (MultipartFile file : imageList) { String extension = getExtension(file); String imageKey = s3UploadService.upload(file, extension, projectId); - Image image = Image.of(file.getOriginalFilename(), imageKey, extension, order, folder); + Image image = Image.of(file.getOriginalFilename(), imageKey, extension, folder); imageRepository.save(image); } } @@ -116,15 +117,15 @@ public class ImageService { save(imageId, labelRequest.getData()); } - public void uploadFolderWithImages(MultipartFile folderOrZip, Integer projectId) throws IOException { - orderCount = 0; - + public void uploadFolderWithImages(MultipartFile folderOrZip, Integer projectId, Integer folderId) throws IOException { // 프로젝트 정보 가져오기 Project project = projectRepository.findById(projectId) .orElseThrow(() -> new CustomException(ErrorCode.PROJECT_NOT_FOUND)); - // 부모 폴더가 최상위인지 확인 Folder parentFolder = null; + if (folderId != 0) { + parentFolder = getFolder(folderId); + } // 파일이 zip 파일인지 확인 String originalFilename = folderOrZip.getOriginalFilename(); @@ -166,7 +167,7 @@ public class ImageService { // InputStream으로 S3 업로드 String imageKey = s3UploadService.uploadFromInputStream(inputStream, extension, project.getId(), file.getName()); - Image image = Image.of(file.getName(), imageKey, extension, orderCount++, currentFolder); + Image image = Image.of(file.getName(), imageKey, extension, currentFolder); imageRepository.save(image); } catch (IOException e) { throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE); diff --git a/backend/src/main/java/com/worlabel/global/service/S3UploadService.java b/backend/src/main/java/com/worlabel/global/service/S3UploadService.java index dffbee7..b652d5c 100644 --- a/backend/src/main/java/com/worlabel/global/service/S3UploadService.java +++ b/backend/src/main/java/com/worlabel/global/service/S3UploadService.java @@ -12,6 +12,8 @@ import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; import java.io.*; import java.net.MalformedURLException; import java.net.URL; @@ -131,6 +133,12 @@ public class S3UploadService { PutObjectRequest putRequest = new PutObjectRequest(bucket, s3FileName, byteArrayInputStream, metadata); amazonS3.putObject(putRequest); // S3 파일 업로드 + + BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(bytes)); + int imageWidth = bufferedImage.getWidth(); + int imageHeight = bufferedImage.getHeight(); + + uploadJsonWithDimensions(s3Key, imageWidth, imageHeight); } catch (Exception e) { log.error("", e); throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE); @@ -174,9 +182,11 @@ public class S3UploadService { public String uploadFromInputStream(InputStream inputStream, String extension, Integer projectId, String fileName) { try { + // UUID로 S3 파일 이름 생성 String s3Key = getS3FileName(projectId); String s3FileName = s3Key + "." + extension; + // 이미지 파일 업로드 byte[] bytes = IOUtils.toByteArray(inputStream); ObjectMetadata metadata = new ObjectMetadata(); @@ -185,12 +195,44 @@ public class S3UploadService { try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) { PutObjectRequest putRequest = new PutObjectRequest(bucket, s3FileName, byteArrayInputStream, metadata); - amazonS3.putObject(putRequest); + amazonS3.putObject(putRequest); // 이미지 업로드 } + // 이미지 높이와 너비 추출 + BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(bytes)); + int imageWidth = bufferedImage.getWidth(); + int imageHeight = bufferedImage.getHeight(); + + // 너비와 높이를 포함한 JSON 업로드 + uploadJsonWithDimensions(s3Key, imageWidth, imageHeight); // JSON 파일 업로드 메서드 호출 + return url + "/" + s3Key; } catch (IOException e) { throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE); } } + + // 이미지의 너비와 높이를 포함하는 JSON 파일 업로드 + private void uploadJsonWithDimensions(String s3Key, int width, int height) { + try { + // JSON 파일 이름 생성 (s3Key + ".json") + String jsonFileName = s3Key + ".json"; + + // 이미지의 너비와 높이를 포함한 JSON 데이터 생성 + String jsonContent = "{\n\"imageHeight\": " + height + ",\n\"imageWidth\": " + width + "\n}"; + + byte[] jsonBytes = jsonContent.getBytes(StandardCharsets.UTF_8); + + ObjectMetadata jsonMetadata = new ObjectMetadata(); + jsonMetadata.setContentType("application/json"); + jsonMetadata.setContentLength(jsonBytes.length); + + try (ByteArrayInputStream jsonInputStream = new ByteArrayInputStream(jsonBytes)) { + PutObjectRequest jsonPutRequest = new PutObjectRequest(bucket, jsonFileName, jsonInputStream, jsonMetadata); + amazonS3.putObject(jsonPutRequest); // JSON 파일 업로드 + } + } catch (Exception e) { + throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE); + } + } }