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 fe20b32..35caee3 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 @@ -6,6 +6,7 @@ import com.umc.naoman.domain.photo.dto.PhotoRequest.PhotoUploadRequest; import com.umc.naoman.domain.photo.dto.PhotoRequest.PreSignedUrlRequest; import com.umc.naoman.domain.photo.dto.PhotoRequest.UploadSamplePhotoRequest; +import com.umc.naoman.domain.photo.dto.PhotoResponse; import com.umc.naoman.domain.photo.dto.PhotoResponse.PagedPhotoInfo; import com.umc.naoman.domain.photo.dto.PhotoResponse.PhotoDeleteInfo; import com.umc.naoman.domain.photo.dto.PhotoResponse.PhotoDownloadUrlListInfo; @@ -85,7 +86,7 @@ public ResultResponse uploadPhotoList(@Valid @RequestBody Photo public ResultResponse getPhotoListByShareGroupAndProfile(@RequestParam Long shareGroupId, @RequestParam Long profileId, @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) - @Parameter(hidden = true) Pageable pageable, + @Parameter(hidden = true) Pageable pageable, @LoginMember Member member) { Page photoEsList = photoEsService.getPhotoEsListByShareGroupIdAndFaceTag(shareGroupId, profileId, member, pageable); return ResultResponse.of(RETRIEVE_PHOTO, photoConverter.toPagedPhotoInfo(photoEsList, member)); @@ -100,7 +101,7 @@ public ResultResponse getPhotoListByShareGroupAndProfile(@Reques }) public ResultResponse getAllPhotoListByShareGroup(@RequestParam Long shareGroupId, @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) - @Parameter(hidden = true) Pageable pageable, + @Parameter(hidden = true) Pageable pageable, @LoginMember Member member) { Page photoEsList = photoEsService.getAllPhotoEsListByShareGroupId(shareGroupId, member, pageable); return ResultResponse.of(RETRIEVE_PHOTO, photoConverter.toPagedPhotoInfo(photoEsList, member)); @@ -115,7 +116,7 @@ public ResultResponse getAllPhotoListByShareGroup(@RequestParam }) public ResultResponse getEtcPhotoListByShareGroup(@RequestParam Long shareGroupId, @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) - @Parameter(hidden = true) Pageable pageable, + @Parameter(hidden = true) Pageable pageable, @LoginMember Member member) { Page photoEsList = photoEsService.getEtcPhotoEsListByShareGroupId(shareGroupId, member, pageable); return ResultResponse.of(RETRIEVE_PHOTO, photoConverter.toPagedPhotoInfo(photoEsList, member)); @@ -130,6 +131,15 @@ public ResultResponse getPhotoDownloadUrlList(@Request return ResultResponse.of(DOWNLOAD_PHOTO, photoDownloadUrlList); } + @GetMapping("/download/all") + @Operation(summary = "특정 앨범 사진 전체 다운로드 API", description = "선택한 앨범에 속한 사진을 다운로드할 주소를 받는 API입니다. 해당 공유그룹에 속해있는 회원만 다운로드 요청할 수 있습니다.") + public ResultResponse getPhotoDownloadUrlListByProfile(@RequestParam Long shareGroupId, + @RequestParam Long profileId, + @LoginMember Member member) { + PhotoResponse.PhotoDownloadUrlListInfo photoEsDownloadUrlList = photoService.getPhotoEsDownloadUrlList(shareGroupId, profileId, member); + return ResultResponse.of(DOWNLOAD_PHOTO, photoEsDownloadUrlList); + } + @DeleteMapping @Operation(summary = "사진 삭제 API", description = "사진을 삭제하는 API입니다. 해당 공유그룹에 속해있는 회원만 삭제할 수 있습니다.") public ResultResponse deletePhotoList(@Valid @RequestBody PhotoDeletedRequest request, 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 058d415..4613f28 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 @@ -8,7 +8,6 @@ import com.umc.naoman.domain.photo.dto.PhotoResponse.PhotoUploadInfo; import com.umc.naoman.domain.photo.dto.PhotoResponse.PreSignedUrlInfo; import com.umc.naoman.domain.photo.dto.PhotoResponse.PreSignedUrlListInfo; -import com.umc.naoman.domain.photo.dto.PhotoResponse.SamplePhotoUploadInfo; import com.umc.naoman.domain.photo.elasticsearch.document.PhotoEs; import com.umc.naoman.domain.photo.entity.Photo; import com.umc.naoman.domain.shareGroup.entity.ShareGroup; @@ -116,13 +115,9 @@ public PhotoDeleteInfo toPhotoDeleteInfo(List photoList) { .build(); } - public PhotoDownloadUrlListInfo toPhotoDownloadUrlListInfo(List photoList) { - List photoDownloadUrlList = photoList.stream() - .map(Photo::getUrl) - .collect(Collectors.toList()); - + public PhotoDownloadUrlListInfo toPhotoDownloadUrlListInfo(List photoUrlList) { return PhotoDownloadUrlListInfo.builder() - .photoDownloadUrlList(photoDownloadUrlList) + .photoDownloadUrlList(photoUrlList) .build(); } } diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/PhotoEsClientRepository.java b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/PhotoEsClientRepository.java index e81fc2c..5d53020 100644 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/PhotoEsClientRepository.java +++ b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/PhotoEsClientRepository.java @@ -182,11 +182,11 @@ public void deletePhotoEsByRdsId(List rdsIdList, Long shareGroupId) { } //특정 회원의 얼굴이 태그된 사진 삭제 -> 해당 사진에서 감지된 얼굴벡터도 함께 삭제 return : 삭제된 사진의 rdsId - public List deletePhotoEsByFaceTag(Long memberId){ + public List deletePhotoEsByFaceTag(Long memberId) { SearchResponse response = null; List rdsIdList = new ArrayList<>(); List photoNameList = new ArrayList<>(); - try{ + try { response = elasticsearchClient.search(s -> s .index("photos_es") .from(0) diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/service/PhotoEsService.java b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/service/PhotoEsService.java index 6ba6fb6..bef8910 100644 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/service/PhotoEsService.java +++ b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/service/PhotoEsService.java @@ -7,6 +7,8 @@ public interface PhotoEsService { Page getPhotoEsListByShareGroupIdAndFaceTag(Long shareGroupId, Long profileId, Member member, Pageable pageable); + Page getAllPhotoEsListByShareGroupId(Long shareGroupId, Member member, Pageable pageable); + Page getEtcPhotoEsListByShareGroupId(Long shareGroupId, Member member, Pageable pageable); } 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 c3a2a08..2844e37 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,7 +12,9 @@ @Repository public interface PhotoRepository extends JpaRepository { List findByIdInAndShareGroupId(List photoIdList, Long shareGroupId); + List findByIdIn(List photoIdList); + @Modifying @Query("DELETE FROM Photo p WHERE p.id IN :photoIdList") void deleteAllByPhotoIdList(@Param("photoIdList") 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 befb019..94ac0cb 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 @@ -3,7 +3,9 @@ import com.umc.naoman.domain.member.entity.Member; import com.umc.naoman.domain.photo.dto.PhotoRequest; import com.umc.naoman.domain.photo.dto.PhotoRequest.UploadSamplePhotoRequest; +import com.umc.naoman.domain.photo.dto.PhotoResponse; import com.umc.naoman.domain.photo.dto.PhotoResponse.PhotoDownloadUrlListInfo; +import com.umc.naoman.domain.photo.dto.PhotoResponse.PhotoEsDownloadUrlListInfo; import com.umc.naoman.domain.photo.dto.PhotoResponse.PhotoUploadInfo; import com.umc.naoman.domain.photo.dto.PhotoResponse.PreSignedUrlInfo; import com.umc.naoman.domain.photo.dto.PhotoResponse.SamplePhotoUploadInfo; @@ -13,9 +15,15 @@ public interface PhotoService { List getPreSignedUrlList(PhotoRequest.PreSignedUrlRequest request, Member member); + SamplePhotoUploadInfo uploadSamplePhotoList(UploadSamplePhotoRequest request, Member member); + PhotoUploadInfo uploadPhotoList(PhotoRequest.PhotoUploadRequest request, Member member); + PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List photoIdList, Long shareGroupId, Member member); + + PhotoDownloadUrlListInfo getPhotoEsDownloadUrlList(Long shareGroupId, Long profileId, Member member); + List deletePhotoList(PhotoRequest.PhotoDeletedRequest request, Member member); Photo findPhoto(Long photoId); 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 f2277d3..99bfa00 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 @@ -11,10 +11,12 @@ import com.umc.naoman.domain.photo.dto.PhotoRequest; import com.umc.naoman.domain.photo.dto.PhotoRequest.PhotoUploadRequest; import com.umc.naoman.domain.photo.dto.PhotoRequest.UploadSamplePhotoRequest; +import com.umc.naoman.domain.photo.dto.PhotoResponse; import com.umc.naoman.domain.photo.dto.PhotoResponse.PhotoDownloadUrlListInfo; import com.umc.naoman.domain.photo.dto.PhotoResponse.PhotoUploadInfo; import com.umc.naoman.domain.photo.dto.PhotoResponse.PreSignedUrlInfo; import com.umc.naoman.domain.photo.dto.PhotoResponse.SamplePhotoUploadInfo; +import com.umc.naoman.domain.photo.elasticsearch.document.PhotoEs; import com.umc.naoman.domain.photo.elasticsearch.repository.PhotoEsClientRepository; import com.umc.naoman.domain.photo.entity.Photo; import com.umc.naoman.domain.photo.entity.SamplePhoto; @@ -26,10 +28,13 @@ import io.awspring.cloud.s3.S3Template; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; +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.net.URL; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; @@ -53,9 +58,9 @@ public class PhotoServiceImpl implements PhotoService { private final S3Template s3Template; @Value("${spring.cloud.aws.s3.bucket}") - private String bucketName; + private String BUCKET_NAME; @Value("${spring.cloud.aws.region.static}") - private String region; + private String REGION; public static final String RAW_PATH_PREFIX = "raw"; public static final String W200_PATH_PREFIX = "w200"; @@ -76,7 +81,7 @@ private PreSignedUrlInfo getPreSignedUrl(String originalFilename) { String photoName = fileName.split("/")[1]; String photoUrl = generateFileAccessUrl(fileName); - URL preSignedUrl = amazonS3.generatePresignedUrl(getGeneratePreSignedUrlRequest(bucketName, fileName)); + URL preSignedUrl = amazonS3.generatePresignedUrl(getGeneratePreSignedUrlRequest(BUCKET_NAME, fileName)); return photoConverter.toPreSignedUrlInfo(preSignedUrl.toString(), photoUrl, photoName); } @@ -112,7 +117,7 @@ private Date getPreSignedUrlExpiration() { // 원본 사진의 접근 URL 생성 private String generateFileAccessUrl(String fileName) { - return String.format("https://%s.s3.%s.amazonaws.com/%s", bucketName, region, fileName); + return String.format("https://%s.s3.%s.amazonaws.com/%s", BUCKET_NAME, REGION, fileName); } @Override @@ -133,7 +138,7 @@ public SamplePhotoUploadInfo uploadSamplePhotoList(UploadSamplePhotoRequest requ } private SamplePhoto checkAndSaveSamplePhotoInDB(String photoUrl, String photoName, Member member) { - if (!amazonS3.doesObjectExist(bucketName, RAW_PATH_PREFIX + "/" + photoName)) { + if (!amazonS3.doesObjectExist(BUCKET_NAME, RAW_PATH_PREFIX + "/" + photoName)) { throw new BusinessException(PHOTO_NOT_FOUND_S3); } @@ -165,6 +170,7 @@ public PhotoUploadInfo uploadPhotoList(PhotoUploadRequest request, Member member .filter(profile -> profile.getMember() != null) .map(profile -> profile.getMember().getId()) .collect(Collectors.toList()); + // 얼굴 인식 서비스 호출 faceDetectionService.detectFaceUploadPhoto(photoNameList, shareGroup.getId(), memberIdList); @@ -173,7 +179,7 @@ public PhotoUploadInfo uploadPhotoList(PhotoUploadRequest request, Member member // S3에 객체의 존재 여부 확인 및 DB에 사진을 저장하고 객체를 반환하는 메서드 private Photo checkAndSavePhotoInDB(String photoUrl, String photoName, ShareGroup shareGroup) { - if (!amazonS3.doesObjectExist(bucketName, RAW_PATH_PREFIX + "/" + photoName)) { + if (!amazonS3.doesObjectExist(BUCKET_NAME, RAW_PATH_PREFIX + "/" + photoName)) { throw new BusinessException(PHOTO_NOT_FOUND_S3); } @@ -197,7 +203,36 @@ public PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List photoIdList, throw new BusinessException(PHOTO_NOT_FOUND); } - return photoConverter.toPhotoDownloadUrlListInfo(photoList); + List photoUrlList = photoList.stream() + .map(Photo::getUrl) + .collect(Collectors.toList()); + + return photoConverter.toPhotoDownloadUrlListInfo(photoUrlList); + } + + @Override + public PhotoDownloadUrlListInfo getPhotoEsDownloadUrlList(Long shareGroupId, Long profileId, Member member) { + validateShareGroupAndProfile(shareGroupId, member); + Long memberId = shareGroupService.findProfile(profileId).getMember().getId(); + + List photoEsList = new ArrayList<>(); + Pageable pageable = Pageable.ofSize(5000); + boolean isLastPage = false; + + while (!isLastPage) { + Page photoEsPage = photoEsClientRepository.findPhotoEsByShareGroupIdAndFaceTag(shareGroupId, memberId, pageable); + photoEsList.addAll(photoEsPage.getContent()); + isLastPage = photoEsPage.isLast(); + + // 다음 페이지로 이동 + pageable = pageable.next(); + } + + List photUrlList = photoEsList.stream() + .map(PhotoEs::getUrl) + .collect(Collectors.toList()); + + return photoConverter.toPhotoDownloadUrlListInfo(photUrlList); } @Override @@ -230,9 +265,9 @@ public List deletePhotoList(PhotoRequest.PhotoDeletedRequest request, Mem private void deletePhoto(String photoName) { // S3에서 원본 및 변환된 이미지 삭제 - s3Template.deleteObject(bucketName, RAW_PATH_PREFIX + "/" + photoName); - s3Template.deleteObject(bucketName, W200_PATH_PREFIX + "/" + photoConverter.convertExtension(photoName)); - s3Template.deleteObject(bucketName, W400_PATH_PREFIX + "/" + photoConverter.convertExtension(photoName)); + s3Template.deleteObject(BUCKET_NAME, RAW_PATH_PREFIX + "/" + photoName); + s3Template.deleteObject(BUCKET_NAME, W200_PATH_PREFIX + "/" + photoConverter.convertExtension(photoName)); + s3Template.deleteObject(BUCKET_NAME, W400_PATH_PREFIX + "/" + photoConverter.convertExtension(photoName)); } // 해당 공유 그룹이 존재하는지 확인 & 멤버가 해당 공유 그룹에 속해있는지 확인