From 3078b21f0916b041a57d88045beafbe533d05b80 Mon Sep 17 00:00:00 2001 From: Donghoon Jeong <112836685+jjeongdong@users.noreply.github.com> Date: Sat, 3 Aug 2024 22:51:34 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat=20:=20=EC=82=AC=EC=A7=84=20=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=EB=A1=9C=EB=93=9C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../photo/controller/PhotoController.java | 44 ++++++++++++-- .../photo/converter/PhotoConverter.java | 11 ++++ .../domain/photo/dto/PhotoResponse.java | 19 ++++++ .../domain/photo/service/PhotoService.java | 7 +++ .../photo/service/PhotoServiceImpl.java | 58 ++++++++++++++++++- .../naoman/global/error/code/S3ErrorCode.java | 3 +- .../naoman/global/result/ResultResponse.java | 3 +- .../global/result/code/PhotoResultCode.java | 3 +- 8 files changed, 138 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java b/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java index 35ef319..00e05db 100644 --- a/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java +++ b/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java @@ -9,17 +9,21 @@ import com.umc.naoman.global.result.ResultResponse; import com.umc.naoman.global.security.annotation.LoginMember; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.core.io.ByteArrayResource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; 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.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -57,8 +61,8 @@ public ResultResponse uploadPhotoList(@Valid @Req @GetMapping("/all") @Operation(summary = "특정 공유그룹의 전체 사진 조회 API", description = "특정 공유 그룹의 전체 사진을 조회하는 API입니다.") - public ResultResponse getAllPhotoListByShareGroup(@RequestParam Long shareGroupId, @LoginMember Member member, - @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { + public ResultResponse getAllPhotoListByShareGroup(@RequestParam Long shareGroupId, @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, + @LoginMember Member member) { Page allPhotoListByShareGroup = photoService.getAllPhotoList(shareGroupId, member, pageable); return ResultResponse.of(RETRIEVE_PHOTO, photoConverter.toPhotoListInfo(allPhotoListByShareGroup)); } @@ -70,4 +74,36 @@ public ResultResponse deletePhotoList(@Valid @Req List photoList = photoService.deletePhotoList(request, member); return ResultResponse.of(DELETE_PHOTO, photoConverter.toPhotoDeleteInfo(photoList)); } + + @GetMapping("/download/{photoId}") + @Operation(summary = "사진 다운로드 API", description = "사진을 다운로드하는 API입니다. 해당 공유그룹에 속해있는 회원만 다운로드할 수 있습니다.") + public ResponseEntity downloadPhoto(@PathVariable Long photoId, @RequestParam Long shareGroupId, + @LoginMember Member member) { + // 다운로드할 사진의 이름을 가져옴 + String photoName = photoService.getPhotoName(photoId); + // 다운로드할 사진을 ByteArrayResource로 가져옴 + ByteArrayResource byteArrayResource = photoService.downloadPhoto(photoId, shareGroupId, member); + // 응답에 사용할 커스텀 헤더를 생성 + HttpHeaders headers = setHttpHeaders(byteArrayResource, photoName); + + return ResponseEntity.ok() + .headers(headers) + .body(byteArrayResource); + } + + private HttpHeaders setHttpHeaders(ByteArrayResource byteArrayResource, String photoName) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentLength(byteArrayResource.getByteArray().length); // 콘텐츠 길이를 설정 + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); // 콘텐츠 타입을 설정 + headers.setContentDisposition(ContentDisposition.attachment().filename(photoName).build()); // 콘텐츠 디스포지션 설정 + return headers; + } + + @GetMapping("/downloadMultiple") + @Operation(summary = "사진 다운로드 API", description = "여러장의 사진을 다운로드 요청하는 API입니다. 해당 공유그룹에 속해있는 회원만 다운로드 요청할 수 있습니다.") + public ResultResponse getPhotoDownloadUrlList(@RequestParam List photoIdList, @RequestParam Long shareGroupId, + @LoginMember Member member) { + PhotoResponse.PhotoDownloadUrlListInfo photoDownloadUrlList = photoService.getPhotoDownloadUrlList(photoIdList, shareGroupId, member); + return ResultResponse.of(DOWNLOAD_PHOTO, photoDownloadUrlList); + } } diff --git a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java index 1cdd149..3920ebb 100644 --- a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java +++ b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java @@ -3,7 +3,9 @@ import com.umc.naoman.domain.photo.dto.PhotoResponse; import com.umc.naoman.domain.photo.entity.Photo; import com.umc.naoman.domain.shareGroup.entity.ShareGroup; +import org.springframework.core.io.ByteArrayResource; import org.springframework.data.domain.Page; +import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import java.util.List; @@ -94,4 +96,13 @@ public PhotoResponse.PhotoDeleteInfo toPhotoDeleteInfo(List photoList) { .build(); } + public PhotoResponse.PhotoDownloadUrlListInfo toPhotoDownloadUrlListResponse(List photoIdList, Long shareGroupId) { + List photoDownloadUrlList = photoIdList.stream() + .map(photoId -> "http://localhost:8080/photos/download/" + photoId + "?shareGroupId=" + shareGroupId) + .collect(Collectors.toList()); + + return PhotoResponse.PhotoDownloadUrlListInfo.builder() + .photoDownloadUrlList(photoDownloadUrlList) + .build(); + } } diff --git a/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java b/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java index a9b87a5..ca1d9ed 100644 --- a/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java +++ b/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java @@ -4,6 +4,8 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpHeaders; import java.time.LocalDateTime; import java.util.List; @@ -68,4 +70,21 @@ public static class PhotoDeleteInfo { private List photoIdList; } + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class PhotoDownloadInfo { + private String photoName; + private ByteArrayResource byteArrayResource; + private HttpHeaders httpHeaders; + } + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class PhotoDownloadUrlListInfo { + private List photoDownloadUrlList; + } } diff --git a/src/main/java/com/umc/naoman/domain/photo/service/PhotoService.java b/src/main/java/com/umc/naoman/domain/photo/service/PhotoService.java index afd1185..9ca4a1c 100644 --- a/src/main/java/com/umc/naoman/domain/photo/service/PhotoService.java +++ b/src/main/java/com/umc/naoman/domain/photo/service/PhotoService.java @@ -4,6 +4,7 @@ import com.umc.naoman.domain.photo.dto.PhotoRequest; import com.umc.naoman.domain.photo.dto.PhotoResponse; import com.umc.naoman.domain.photo.entity.Photo; +import org.springframework.core.io.ByteArrayResource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -18,4 +19,10 @@ public interface PhotoService { Page getAllPhotoList(Long shareGroupId, Member member, Pageable pageable); List deletePhotoList(PhotoRequest.PhotoDeletedRequest request, Member member); + + ByteArrayResource downloadPhoto(Long photoId, Long shareGroupId, Member member); + + String getPhotoName(Long photoId); + + PhotoResponse.PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List photoIdList, Long shareGroupId, Member member); } diff --git a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java index 520c06d..196f3e2 100644 --- a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java +++ b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java @@ -3,10 +3,13 @@ import com.amazonaws.HttpMethod; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.Headers; +import com.amazonaws.services.s3.model.AmazonS3Exception; import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.S3Object; +import com.amazonaws.services.s3.model.S3ObjectInputStream; +import com.amazonaws.util.IOUtils; import com.umc.naoman.domain.member.entity.Member; import com.umc.naoman.domain.photo.converter.PhotoConverter; import com.umc.naoman.domain.photo.dto.PhotoRequest; @@ -17,14 +20,23 @@ import com.umc.naoman.domain.shareGroup.repository.ProfileRepository; import com.umc.naoman.domain.shareGroup.service.ShareGroupService; import com.umc.naoman.global.error.BusinessException; +import com.umc.naoman.global.error.code.S3ErrorCode; import io.awspring.cloud.s3.S3Template; +import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ByteArrayResource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; import java.net.URL; import java.util.Date; import java.util.List; @@ -37,6 +49,7 @@ @RequiredArgsConstructor public class PhotoServiceImpl implements PhotoService { + private static final Logger log = LoggerFactory.getLogger(PhotoServiceImpl.class); private final AmazonS3 amazonS3; private final S3Template s3Template; private final PhotoRepository photoRepository; @@ -112,7 +125,7 @@ private String generateFileAccessUrl(String fileName) { @Override @Transactional public PhotoResponse.PhotoUploadInfo uploadPhotoList(PhotoRequest.PhotoUploadRequest request, Member member) { - shareGroupService.findProfile(member.getId(), request.getShareGroupId()); + shareGroupService.findProfile(request.getShareGroupId(), member.getId()); ShareGroup shareGroup = shareGroupService.findShareGroup(request.getShareGroupId()); int uploadCount = 0; @@ -157,14 +170,15 @@ public Page getAllPhotoList(Long shareGroupId, Member member, Pageable pa @Transactional public List deletePhotoList(PhotoRequest.PhotoDeletedRequest request, Member member) { // 멤버가 해당 공유 그룹에 대한 권한이 있는지 확인 - shareGroupService.findProfile(member.getId(), request.getShareGroupId()); + shareGroupService.findProfile(request.getShareGroupId(), member.getId()); // 요청된 사진 ID 목록과 공유 그룹 ID를 기반으로 사진 목록 조회 List photoList = photoRepository.findByIdInAndShareGroupId(request.getPhotoIdList(), request.getShareGroupId()); // 사진 목록 크기 검증 if (photoList.size() != request.getPhotoIdList().size()) { - throw new BusinessException(PHOTO_NOT_FOUND); // 요청한 사진이 일부 또는 전부 없을 경우 예외 발생 + // 요청한 사진이 일부 또는 전부 없을 경우 예외 발생 + throw new BusinessException(PHOTO_NOT_FOUND); } // 각 사진에 대해 S3에서 객체 삭제 및 데이터베이스에서 삭제 @@ -185,4 +199,42 @@ private void deletePhoto(Photo photo) { photoRepository.delete(photo); } + @Override + @Transactional(readOnly = true) + public ByteArrayResource downloadPhoto(Long photoId, Long shareGroupId, Member member) { + // photoId에 해당하는 사진을 조회, 존재하지 않으면 예외를 던짐 + Photo photo = photoRepository.findById(photoId).orElseThrow(() -> new BusinessException(PHOTO_NOT_FOUND)); + + // S3에서 사진을 가져오기 위해 사진 이름을 키 값으로 사용 + String photoName = photo.getName(); + + // S3에서 해당 키 값을 가진 객체를 가져옴 + S3Object s3Object; + + try { + s3Object = amazonS3.getObject(bucketName + "/" + RAW_PATH_PREFIX, photoName); + } catch (AmazonS3Exception e) { + throw new BusinessException(PHOTO_NOT_FOUND); + } + + // S3 객체의 내용을 읽음 + try (S3ObjectInputStream s3ObjectInputStream = s3Object.getObjectContent()) { + return new ByteArrayResource(IOUtils.toByteArray(s3ObjectInputStream)); + } catch (IOException e) { + throw new BusinessException(FAILED_DOWNLOAD_PHOTO); + } + } + + @Override + @Transactional(readOnly = true) + public String getPhotoName(Long photoId) { + return photoRepository.findById(photoId).orElseThrow(() -> new BusinessException(PHOTO_NOT_FOUND)).getName(); + } + + @Override + @Transactional(readOnly = true) + public PhotoResponse.PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List photoIdList, Long shareGroupId, Member member) { + shareGroupService.findProfile(shareGroupId, member.getId()); + return photoConverter.toPhotoDownloadUrlListResponse(photoIdList, shareGroupId); + } } diff --git a/src/main/java/com/umc/naoman/global/error/code/S3ErrorCode.java b/src/main/java/com/umc/naoman/global/error/code/S3ErrorCode.java index c49b6bf..49c7157 100644 --- a/src/main/java/com/umc/naoman/global/error/code/S3ErrorCode.java +++ b/src/main/java/com/umc/naoman/global/error/code/S3ErrorCode.java @@ -12,7 +12,8 @@ public enum S3ErrorCode implements ErrorCode { UNAUTHORIZED_GET(403, "ES3000", "사진을 조회할 권한이 없습니다."), UNAUTHORIZED_DELETE(403, "ES3000", "사진을 삭제할 권한이 없습니다."), UNAUTHORIZED_UPLOAD(403, "ES3000", "사진을 업로드할 권한이 없습니다."), - PHOTO_NOT_FOUND(404, "ES3005", "요청한 사진이 존재하지 않습니다."), + PHOTO_NOT_FOUND(404, "ES3000", "요청한 사진이 존재하지 않습니다."), + FAILED_DOWNLOAD_PHOTO(500, "ES3000", "사진을 다운로드하는 도중 문제가 발생하였습니다.") ; diff --git a/src/main/java/com/umc/naoman/global/result/ResultResponse.java b/src/main/java/com/umc/naoman/global/result/ResultResponse.java index 1c91392..837f975 100644 --- a/src/main/java/com/umc/naoman/global/result/ResultResponse.java +++ b/src/main/java/com/umc/naoman/global/result/ResultResponse.java @@ -2,6 +2,7 @@ import lombok.Builder; import lombok.Getter; +import org.springframework.http.HttpHeaders; @Getter @Builder @@ -28,4 +29,4 @@ public static ResultResponse of(ResultCode resultCode) { .data(null) .build(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/umc/naoman/global/result/code/PhotoResultCode.java b/src/main/java/com/umc/naoman/global/result/code/PhotoResultCode.java index 027de1f..20a51ce 100644 --- a/src/main/java/com/umc/naoman/global/result/code/PhotoResultCode.java +++ b/src/main/java/com/umc/naoman/global/result/code/PhotoResultCode.java @@ -11,7 +11,8 @@ public enum PhotoResultCode implements ResultCode { CREATE_PRESIGNED_URL(200, "SP000", "성공적으로 Presigned URL을 요청하였습니다."), UPLOAD_PHOTO(200, "SP000", "성공적으로 이미지를 업로드하였습니다."), RETRIEVE_PHOTO(200, "SP000", "성공적으로 이미지를 조회하였습니다."), - DELETE_PHOTO(200, "SP000", "성공적으로 이미지를 삭제하였습니다.") + DELETE_PHOTO(200, "SP000", "성공적으로 이미지를 삭제하였습니다."), + DOWNLOAD_PHOTO(200, "SP000", "성공적으로 이미지를 디운로드하였습니다.") ; private final int status; From fe12b6ff757beea1f48bb4a19e7c2e59338889d0 Mon Sep 17 00:00:00 2001 From: Donghoon Jeong <112836685+jjeongdong@users.noreply.github.com> Date: Sat, 3 Aug 2024 22:54:50 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat=20:=20=EC=82=AC=EC=A7=84=20=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=EB=A1=9C=EB=93=9C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/umc/naoman/domain/photo/converter/PhotoConverter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java index 3920ebb..29ffc11 100644 --- a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java +++ b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java @@ -98,7 +98,8 @@ public PhotoResponse.PhotoDeleteInfo toPhotoDeleteInfo(List photoList) { public PhotoResponse.PhotoDownloadUrlListInfo toPhotoDownloadUrlListResponse(List photoIdList, Long shareGroupId) { List photoDownloadUrlList = photoIdList.stream() - .map(photoId -> "http://localhost:8080/photos/download/" + photoId + "?shareGroupId=" + shareGroupId) +// .map(photoId -> "http://localhost:8080/photos/download/" + photoId + "?shareGroupId=" + shareGroupId) + .map(photoId -> "https://naoman.site:8080/photos/download/" + photoId + "?shareGroupId=" + shareGroupId) .collect(Collectors.toList()); return PhotoResponse.PhotoDownloadUrlListInfo.builder() From 24799194cc489065c67c621fce89cee9467a2393 Mon Sep 17 00:00:00 2001 From: Donghoon Jeong <112836685+jjeongdong@users.noreply.github.com> Date: Sat, 3 Aug 2024 23:06:04 +0900 Subject: [PATCH 3/8] =?UTF-8?q?docs=20:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20import=EB=AC=B8=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc/naoman/domain/photo/converter/PhotoConverter.java | 2 -- .../umc/naoman/domain/photo/service/PhotoServiceImpl.java | 5 ----- 2 files changed, 7 deletions(-) diff --git a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java index 29ffc11..4934c3a 100644 --- a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java +++ b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java @@ -3,9 +3,7 @@ import com.umc.naoman.domain.photo.dto.PhotoResponse; import com.umc.naoman.domain.photo.entity.Photo; import com.umc.naoman.domain.shareGroup.entity.ShareGroup; -import org.springframework.core.io.ByteArrayResource; import org.springframework.data.domain.Page; -import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java index 196f3e2..98f23e6 100644 --- a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java +++ b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java @@ -20,9 +20,7 @@ import com.umc.naoman.domain.shareGroup.repository.ProfileRepository; import com.umc.naoman.domain.shareGroup.service.ShareGroupService; import com.umc.naoman.global.error.BusinessException; -import com.umc.naoman.global.error.code.S3ErrorCode; import io.awspring.cloud.s3.S3Template; -import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,9 +28,6 @@ import org.springframework.core.io.ByteArrayResource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.http.ContentDisposition; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; From 6e88aeae125a4f794177c8286451e637e6810942 Mon Sep 17 00:00:00 2001 From: Donghoon Jeong <112836685+jjeongdong@users.noreply.github.com> Date: Sun, 4 Aug 2024 22:29:44 +0900 Subject: [PATCH 4/8] =?UTF-8?q?refact=20:=20=EC=82=AC=EC=A7=84=20=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=EB=A1=9C=EB=93=9C=20API=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../photo/controller/PhotoController.java | 28 ++----------------- .../photo/converter/PhotoConverter.java | 7 ++--- .../domain/photo/dto/PhotoResponse.java | 10 ------- .../photo/repository/PhotoRepository.java | 1 + .../domain/photo/service/PhotoService.java | 4 --- .../photo/service/PhotoServiceImpl.java | 11 ++------ 6 files changed, 8 insertions(+), 53 deletions(-) diff --git a/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java b/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java index 00e05db..7fb48fd 100644 --- a/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java +++ b/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java @@ -75,32 +75,8 @@ public ResultResponse deletePhotoList(@Valid @Req return ResultResponse.of(DELETE_PHOTO, photoConverter.toPhotoDeleteInfo(photoList)); } - @GetMapping("/download/{photoId}") - @Operation(summary = "사진 다운로드 API", description = "사진을 다운로드하는 API입니다. 해당 공유그룹에 속해있는 회원만 다운로드할 수 있습니다.") - public ResponseEntity downloadPhoto(@PathVariable Long photoId, @RequestParam Long shareGroupId, - @LoginMember Member member) { - // 다운로드할 사진의 이름을 가져옴 - String photoName = photoService.getPhotoName(photoId); - // 다운로드할 사진을 ByteArrayResource로 가져옴 - ByteArrayResource byteArrayResource = photoService.downloadPhoto(photoId, shareGroupId, member); - // 응답에 사용할 커스텀 헤더를 생성 - HttpHeaders headers = setHttpHeaders(byteArrayResource, photoName); - - return ResponseEntity.ok() - .headers(headers) - .body(byteArrayResource); - } - - private HttpHeaders setHttpHeaders(ByteArrayResource byteArrayResource, String photoName) { - HttpHeaders headers = new HttpHeaders(); - headers.setContentLength(byteArrayResource.getByteArray().length); // 콘텐츠 길이를 설정 - headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); // 콘텐츠 타입을 설정 - headers.setContentDisposition(ContentDisposition.attachment().filename(photoName).build()); // 콘텐츠 디스포지션 설정 - return headers; - } - - @GetMapping("/downloadMultiple") - @Operation(summary = "사진 다운로드 API", description = "여러장의 사진을 다운로드 요청하는 API입니다. 해당 공유그룹에 속해있는 회원만 다운로드 요청할 수 있습니다.") + @GetMapping("/download") + @Operation(summary = "사진 다운로드 API", description = "여러장의 사진을 다운로드할 주소를 받는 API입니다. 해당 공유그룹에 속해있는 회원만 다운로드 요청할 수 있습니다.") public ResultResponse getPhotoDownloadUrlList(@RequestParam List photoIdList, @RequestParam Long shareGroupId, @LoginMember Member member) { PhotoResponse.PhotoDownloadUrlListInfo photoDownloadUrlList = photoService.getPhotoDownloadUrlList(photoIdList, shareGroupId, member); diff --git a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java index 4934c3a..10ab372 100644 --- a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java +++ b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java @@ -94,10 +94,9 @@ public PhotoResponse.PhotoDeleteInfo toPhotoDeleteInfo(List photoList) { .build(); } - public PhotoResponse.PhotoDownloadUrlListInfo toPhotoDownloadUrlListResponse(List photoIdList, Long shareGroupId) { - List photoDownloadUrlList = photoIdList.stream() -// .map(photoId -> "http://localhost:8080/photos/download/" + photoId + "?shareGroupId=" + shareGroupId) - .map(photoId -> "https://naoman.site:8080/photos/download/" + photoId + "?shareGroupId=" + shareGroupId) + public PhotoResponse.PhotoDownloadUrlListInfo toPhotoDownloadUrlListResponse(List photoList) { + List photoDownloadUrlList = photoList.stream() + .map(Photo::getUrl) .collect(Collectors.toList()); return PhotoResponse.PhotoDownloadUrlListInfo.builder() diff --git a/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java b/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java index ca1d9ed..35dad94 100644 --- a/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java +++ b/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java @@ -70,16 +70,6 @@ public static class PhotoDeleteInfo { private List photoIdList; } - @Getter - @Builder - @AllArgsConstructor - @NoArgsConstructor - public static class PhotoDownloadInfo { - private String photoName; - private ByteArrayResource byteArrayResource; - private HttpHeaders httpHeaders; - } - @Getter @Builder @AllArgsConstructor diff --git a/src/main/java/com/umc/naoman/domain/photo/repository/PhotoRepository.java b/src/main/java/com/umc/naoman/domain/photo/repository/PhotoRepository.java index 0ab8983..6c8f96c 100644 --- a/src/main/java/com/umc/naoman/domain/photo/repository/PhotoRepository.java +++ b/src/main/java/com/umc/naoman/domain/photo/repository/PhotoRepository.java @@ -12,4 +12,5 @@ public interface PhotoRepository extends JpaRepository { Page findAllByShareGroupId(Long shareGroupId, Pageable pageable); List findByIdInAndShareGroupId(List photoIdList, Long shareGroupId); + List findByIdIn(List photoIdList); } diff --git a/src/main/java/com/umc/naoman/domain/photo/service/PhotoService.java b/src/main/java/com/umc/naoman/domain/photo/service/PhotoService.java index 9ca4a1c..84c426a 100644 --- a/src/main/java/com/umc/naoman/domain/photo/service/PhotoService.java +++ b/src/main/java/com/umc/naoman/domain/photo/service/PhotoService.java @@ -20,9 +20,5 @@ public interface PhotoService { List deletePhotoList(PhotoRequest.PhotoDeletedRequest request, Member member); - ByteArrayResource downloadPhoto(Long photoId, Long shareGroupId, Member member); - - String getPhotoName(Long photoId); - PhotoResponse.PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List photoIdList, Long shareGroupId, Member member); } diff --git a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java index 98f23e6..edbef3b 100644 --- a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java +++ b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java @@ -44,13 +44,11 @@ @RequiredArgsConstructor public class PhotoServiceImpl implements PhotoService { - private static final Logger log = LoggerFactory.getLogger(PhotoServiceImpl.class); private final AmazonS3 amazonS3; private final S3Template s3Template; private final PhotoRepository photoRepository; private final ShareGroupService shareGroupService; private final PhotoConverter photoConverter; - private final ProfileRepository profileRepository; @Value("${spring.cloud.aws.s3.bucket}") private String bucketName; @@ -220,16 +218,11 @@ public ByteArrayResource downloadPhoto(Long photoId, Long shareGroupId, Member m } } - @Override - @Transactional(readOnly = true) - public String getPhotoName(Long photoId) { - return photoRepository.findById(photoId).orElseThrow(() -> new BusinessException(PHOTO_NOT_FOUND)).getName(); - } - @Override @Transactional(readOnly = true) public PhotoResponse.PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List photoIdList, Long shareGroupId, Member member) { shareGroupService.findProfile(shareGroupId, member.getId()); - return photoConverter.toPhotoDownloadUrlListResponse(photoIdList, shareGroupId); + List photoList = photoRepository.findByIdIn(photoIdList); + return photoConverter.toPhotoDownloadUrlListResponse(photoList); } } From b85dddbe36dbfc340016aebf840c86c305108068 Mon Sep 17 00:00:00 2001 From: Donghoon Jeong <112836685+jjeongdong@users.noreply.github.com> Date: Sun, 4 Aug 2024 22:30:47 +0900 Subject: [PATCH 5/8] =?UTF-8?q?refact=20:=20=EC=82=AC=EC=A7=84=20=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=EB=A1=9C=EB=93=9C=20API=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../photo/service/PhotoServiceImpl.java | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java index edbef3b..5b75965 100644 --- a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java +++ b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java @@ -49,7 +49,7 @@ public class PhotoServiceImpl implements PhotoService { private final PhotoRepository photoRepository; private final ShareGroupService shareGroupService; private final PhotoConverter photoConverter; - +: @Value("${spring.cloud.aws.s3.bucket}") private String bucketName; @@ -192,32 +192,6 @@ private void deletePhoto(Photo photo) { photoRepository.delete(photo); } - @Override - @Transactional(readOnly = true) - public ByteArrayResource downloadPhoto(Long photoId, Long shareGroupId, Member member) { - // photoId에 해당하는 사진을 조회, 존재하지 않으면 예외를 던짐 - Photo photo = photoRepository.findById(photoId).orElseThrow(() -> new BusinessException(PHOTO_NOT_FOUND)); - - // S3에서 사진을 가져오기 위해 사진 이름을 키 값으로 사용 - String photoName = photo.getName(); - - // S3에서 해당 키 값을 가진 객체를 가져옴 - S3Object s3Object; - - try { - s3Object = amazonS3.getObject(bucketName + "/" + RAW_PATH_PREFIX, photoName); - } catch (AmazonS3Exception e) { - throw new BusinessException(PHOTO_NOT_FOUND); - } - - // S3 객체의 내용을 읽음 - try (S3ObjectInputStream s3ObjectInputStream = s3Object.getObjectContent()) { - return new ByteArrayResource(IOUtils.toByteArray(s3ObjectInputStream)); - } catch (IOException e) { - throw new BusinessException(FAILED_DOWNLOAD_PHOTO); - } - } - @Override @Transactional(readOnly = true) public PhotoResponse.PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List photoIdList, Long shareGroupId, Member member) { From 41bb1f42a3cade1dc945ab648d8717b75b2663fc Mon Sep 17 00:00:00 2001 From: Donghoon Jeong <112836685+jjeongdong@users.noreply.github.com> Date: Sun, 4 Aug 2024 22:33:53 +0900 Subject: [PATCH 6/8] =?UTF-8?q?refact=20:=20=EC=82=AC=EC=A7=84=20=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=EB=A1=9C=EB=93=9C=20API=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc/naoman/domain/photo/service/PhotoServiceImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java index 5b75965..76d5d91 100644 --- a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java +++ b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java @@ -197,6 +197,12 @@ private void deletePhoto(Photo photo) { public PhotoResponse.PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List photoIdList, Long shareGroupId, Member member) { shareGroupService.findProfile(shareGroupId, member.getId()); List photoList = photoRepository.findByIdIn(photoIdList); + + if (photoList.size() != photoIdList.size()) { + // 요청한 사진이 일부 또는 전부 없을 경우 예외 발생 + throw new BusinessException(PHOTO_NOT_FOUND); + } + return photoConverter.toPhotoDownloadUrlListResponse(photoList); } } From 9a000de9b012b6c80b163ade7773b53f17164a31 Mon Sep 17 00:00:00 2001 From: Donghoon Jeong <112836685+jjeongdong@users.noreply.github.com> Date: Sun, 4 Aug 2024 22:44:37 +0900 Subject: [PATCH 7/8] =?UTF-8?q?refact=20:=20=EC=82=AC=EC=A7=84=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20API=20=EC=9A=94=EC=B2=AD=EC=8B=9C=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/photo/service/PhotoServiceImpl.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java index 76d5d91..e9328e3 100644 --- a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java +++ b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java @@ -63,7 +63,7 @@ public class PhotoServiceImpl implements PhotoService { @Override @Transactional public List getPreSignedUrlList(PhotoRequest.PreSignedUrlRequest request, Member member) { - shareGroupService.findProfile(request.getShareGroupId(), member.getId()); + validateShareGroupAndProfile(request.getShareGroupId(), member); return request.getPhotoNameList().stream() .map(this::getPreSignedUrl) @@ -118,8 +118,8 @@ private String generateFileAccessUrl(String fileName) { @Override @Transactional public PhotoResponse.PhotoUploadInfo uploadPhotoList(PhotoRequest.PhotoUploadRequest request, Member member) { - shareGroupService.findProfile(request.getShareGroupId(), member.getId()); ShareGroup shareGroup = shareGroupService.findShareGroup(request.getShareGroupId()); + shareGroupService.findProfile(request.getShareGroupId(), member.getId()); int uploadCount = 0; for (String photoUrl : request.getPhotoUrlList()) { @@ -162,8 +162,7 @@ public Page getAllPhotoList(Long shareGroupId, Member member, Pageable pa @Override @Transactional public List deletePhotoList(PhotoRequest.PhotoDeletedRequest request, Member member) { - // 멤버가 해당 공유 그룹에 대한 권한이 있는지 확인 - shareGroupService.findProfile(request.getShareGroupId(), member.getId()); + validateShareGroupAndProfile(request.getShareGroupId(), member); // 요청된 사진 ID 목록과 공유 그룹 ID를 기반으로 사진 목록 조회 List photoList = photoRepository.findByIdInAndShareGroupId(request.getPhotoIdList(), request.getShareGroupId()); @@ -195,7 +194,7 @@ private void deletePhoto(Photo photo) { @Override @Transactional(readOnly = true) public PhotoResponse.PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List photoIdList, Long shareGroupId, Member member) { - shareGroupService.findProfile(shareGroupId, member.getId()); + validateShareGroupAndProfile(shareGroupId, member); List photoList = photoRepository.findByIdIn(photoIdList); if (photoList.size() != photoIdList.size()) { @@ -205,4 +204,11 @@ public PhotoResponse.PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List return photoConverter.toPhotoDownloadUrlListResponse(photoList); } + + private void validateShareGroupAndProfile(Long shareGroupId, Member member) { + // 해당 공유 그룹이 존재하는지 확인 + shareGroupService.findShareGroup(shareGroupId); + // 멤버가 해당 공유 그룹에 속해있는지 확인 + shareGroupService.findProfile(shareGroupId, member.getId()); + } } From f7ebea336813ace89709816c9fb2235127a67fc5 Mon Sep 17 00:00:00 2001 From: Donghoon Jeong <112836685+jjeongdong@users.noreply.github.com> Date: Sun, 4 Aug 2024 22:47:23 +0900 Subject: [PATCH 8/8] =?UTF-8?q?refact=20:=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/photo/converter/PhotoConverter.java | 2 +- .../domain/photo/service/PhotoServiceImpl.java | 12 ++---------- .../com/umc/naoman/global/result/ResultResponse.java | 1 - 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java index 10ab372..78f7411 100644 --- a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java +++ b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java @@ -94,7 +94,7 @@ public PhotoResponse.PhotoDeleteInfo toPhotoDeleteInfo(List photoList) { .build(); } - public PhotoResponse.PhotoDownloadUrlListInfo toPhotoDownloadUrlListResponse(List photoList) { + public PhotoResponse.PhotoDownloadUrlListInfo toPhotoDownloadUrlListInfo(List photoList) { List photoDownloadUrlList = photoList.stream() .map(Photo::getUrl) .collect(Collectors.toList()); diff --git a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java index e9328e3..a7edde8 100644 --- a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java +++ b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java @@ -3,13 +3,10 @@ import com.amazonaws.HttpMethod; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.Headers; -import com.amazonaws.services.s3.model.AmazonS3Exception; import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.S3Object; -import com.amazonaws.services.s3.model.S3ObjectInputStream; -import com.amazonaws.util.IOUtils; import com.umc.naoman.domain.member.entity.Member; import com.umc.naoman.domain.photo.converter.PhotoConverter; import com.umc.naoman.domain.photo.dto.PhotoRequest; @@ -17,21 +14,16 @@ import com.umc.naoman.domain.photo.entity.Photo; import com.umc.naoman.domain.photo.repository.PhotoRepository; import com.umc.naoman.domain.shareGroup.entity.ShareGroup; -import com.umc.naoman.domain.shareGroup.repository.ProfileRepository; import com.umc.naoman.domain.shareGroup.service.ShareGroupService; import com.umc.naoman.global.error.BusinessException; import io.awspring.cloud.s3.S3Template; import lombok.RequiredArgsConstructor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.ByteArrayResource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.io.IOException; import java.net.URL; import java.util.Date; import java.util.List; @@ -49,7 +41,7 @@ public class PhotoServiceImpl implements PhotoService { private final PhotoRepository photoRepository; private final ShareGroupService shareGroupService; private final PhotoConverter photoConverter; -: + @Value("${spring.cloud.aws.s3.bucket}") private String bucketName; @@ -202,7 +194,7 @@ public PhotoResponse.PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List throw new BusinessException(PHOTO_NOT_FOUND); } - return photoConverter.toPhotoDownloadUrlListResponse(photoList); + return photoConverter.toPhotoDownloadUrlListInfo(photoList); } private void validateShareGroupAndProfile(Long shareGroupId, Member member) { diff --git a/src/main/java/com/umc/naoman/global/result/ResultResponse.java b/src/main/java/com/umc/naoman/global/result/ResultResponse.java index 837f975..6d0ccbc 100644 --- a/src/main/java/com/umc/naoman/global/result/ResultResponse.java +++ b/src/main/java/com/umc/naoman/global/result/ResultResponse.java @@ -2,7 +2,6 @@ import lombok.Builder; import lombok.Getter; -import org.springframework.http.HttpHeaders; @Getter @Builder