Skip to content

Commit

Permalink
Merge pull request #89 from Na-o-man/feat/#83/photo-all-download-api
Browse files Browse the repository at this point in the history
[FEAT] 특정 앨범의 사진 전체 다운로드 API 구현
  • Loading branch information
bflykky authored Aug 12, 2024
2 parents 829d006 + 863c812 commit a1290c7
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -85,7 +86,7 @@ public ResultResponse<PhotoUploadInfo> uploadPhotoList(@Valid @RequestBody Photo
public ResultResponse<PagedPhotoInfo> 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<PhotoEs> photoEsList = photoEsService.getPhotoEsListByShareGroupIdAndFaceTag(shareGroupId, profileId, member, pageable);
return ResultResponse.of(RETRIEVE_PHOTO, photoConverter.toPagedPhotoInfo(photoEsList, member));
Expand All @@ -100,7 +101,7 @@ public ResultResponse<PagedPhotoInfo> getPhotoListByShareGroupAndProfile(@Reques
})
public ResultResponse<PagedPhotoInfo> 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<PhotoEs> photoEsList = photoEsService.getAllPhotoEsListByShareGroupId(shareGroupId, member, pageable);
return ResultResponse.of(RETRIEVE_PHOTO, photoConverter.toPagedPhotoInfo(photoEsList, member));
Expand All @@ -115,7 +116,7 @@ public ResultResponse<PagedPhotoInfo> getAllPhotoListByShareGroup(@RequestParam
})
public ResultResponse<PagedPhotoInfo> 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<PhotoEs> photoEsList = photoEsService.getEtcPhotoEsListByShareGroupId(shareGroupId, member, pageable);
return ResultResponse.of(RETRIEVE_PHOTO, photoConverter.toPagedPhotoInfo(photoEsList, member));
Expand All @@ -130,6 +131,15 @@ public ResultResponse<PhotoDownloadUrlListInfo> getPhotoDownloadUrlList(@Request
return ResultResponse.of(DOWNLOAD_PHOTO, photoDownloadUrlList);
}

@GetMapping("/download/all")
@Operation(summary = "특정 앨범 사진 전체 다운로드 API", description = "선택한 앨범에 속한 사진을 다운로드할 주소를 받는 API입니다. 해당 공유그룹에 속해있는 회원만 다운로드 요청할 수 있습니다.")
public ResultResponse<PhotoDownloadUrlListInfo> 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<PhotoDeleteInfo> deletePhotoList(@Valid @RequestBody PhotoDeletedRequest request,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -116,13 +115,9 @@ public PhotoDeleteInfo toPhotoDeleteInfo(List<Photo> photoList) {
.build();
}

public PhotoDownloadUrlListInfo toPhotoDownloadUrlListInfo(List<Photo> photoList) {
List<String> photoDownloadUrlList = photoList.stream()
.map(Photo::getUrl)
.collect(Collectors.toList());

public PhotoDownloadUrlListInfo toPhotoDownloadUrlListInfo(List<String> photoUrlList) {
return PhotoDownloadUrlListInfo.builder()
.photoDownloadUrlList(photoDownloadUrlList)
.photoDownloadUrlList(photoUrlList)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,11 @@ public void deletePhotoEsByRdsId(List<Long> rdsIdList, Long shareGroupId) {
}

//특정 회원의 얼굴이 태그된 사진 삭제 -> 해당 사진에서 감지된 얼굴벡터도 함께 삭제 return : 삭제된 사진의 rdsId
public List<Long> deletePhotoEsByFaceTag(Long memberId){
public List<Long> deletePhotoEsByFaceTag(Long memberId) {
SearchResponse<PhotoEs> response = null;
List<Long> rdsIdList = new ArrayList<>();
List<String> photoNameList = new ArrayList<>();
try{
try {
response = elasticsearchClient.search(s -> s
.index("photos_es")
.from(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

public interface PhotoEsService {
Page<PhotoEs> getPhotoEsListByShareGroupIdAndFaceTag(Long shareGroupId, Long profileId, Member member, Pageable pageable);

Page<PhotoEs> getAllPhotoEsListByShareGroupId(Long shareGroupId, Member member, Pageable pageable);

Page<PhotoEs> getEtcPhotoEsListByShareGroupId(Long shareGroupId, Member member, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
@Repository
public interface PhotoRepository extends JpaRepository<Photo, Long> {
List<Photo> findByIdInAndShareGroupId(List<Long> photoIdList, Long shareGroupId);

List<Photo> findByIdIn(List<Long> photoIdList);

@Modifying
@Query("DELETE FROM Photo p WHERE p.id IN :photoIdList")
void deleteAllByPhotoIdList(@Param("photoIdList") List<Long> photoIdList);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13,9 +15,15 @@

public interface PhotoService {
List<PreSignedUrlInfo> getPreSignedUrlList(PhotoRequest.PreSignedUrlRequest request, Member member);

SamplePhotoUploadInfo uploadSamplePhotoList(UploadSamplePhotoRequest request, Member member);

PhotoUploadInfo uploadPhotoList(PhotoRequest.PhotoUploadRequest request, Member member);

PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List<Long> photoIdList, Long shareGroupId, Member member);

PhotoDownloadUrlListInfo getPhotoEsDownloadUrlList(Long shareGroupId, Long profileId, Member member);

List<Photo> deletePhotoList(PhotoRequest.PhotoDeletedRequest request, Member member);

Photo findPhoto(Long photoId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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";
Expand All @@ -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);
}

Expand Down Expand Up @@ -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
Expand All @@ -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);
}

Expand Down Expand Up @@ -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);

Expand All @@ -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);
}

Expand All @@ -197,7 +203,36 @@ public PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List<Long> photoIdList,
throw new BusinessException(PHOTO_NOT_FOUND);
}

return photoConverter.toPhotoDownloadUrlListInfo(photoList);
List<String> 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<PhotoEs> photoEsList = new ArrayList<>();
Pageable pageable = Pageable.ofSize(5000);
boolean isLastPage = false;

while (!isLastPage) {
Page<PhotoEs> photoEsPage = photoEsClientRepository.findPhotoEsByShareGroupIdAndFaceTag(shareGroupId, memberId, pageable);
photoEsList.addAll(photoEsPage.getContent());
isLastPage = photoEsPage.isLast();

// 다음 페이지로 이동
pageable = pageable.next();
}

List<String> photUrlList = photoEsList.stream()
.map(PhotoEs::getUrl)
.collect(Collectors.toList());

return photoConverter.toPhotoDownloadUrlListInfo(photUrlList);
}

@Override
Expand Down Expand Up @@ -230,9 +265,9 @@ public List<Photo> 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));
}

// 해당 공유 그룹이 존재하는지 확인 & 멤버가 해당 공유 그룹에 속해있는지 확인
Expand Down

0 comments on commit a1290c7

Please sign in to comment.