diff --git a/src/main/java/com/pickyfy/pickyfy/PickyfyApplication.java b/src/main/java/com/pickyfy/pickyfy/PickyfyApplication.java index d6e6879..6f2c83c 100644 --- a/src/main/java/com/pickyfy/pickyfy/PickyfyApplication.java +++ b/src/main/java/com/pickyfy/pickyfy/PickyfyApplication.java @@ -3,9 +3,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableJpaAuditing +@EnableScheduling public class PickyfyApplication { public static void main(String[] args) { diff --git a/src/main/java/com/pickyfy/pickyfy/repository/PlaceImageRepository.java b/src/main/java/com/pickyfy/pickyfy/repository/PlaceImageRepository.java index 23b4dd0..48dbcea 100644 --- a/src/main/java/com/pickyfy/pickyfy/repository/PlaceImageRepository.java +++ b/src/main/java/com/pickyfy/pickyfy/repository/PlaceImageRepository.java @@ -13,4 +13,15 @@ public interface PlaceImageRepository extends JpaRepository { List findAllByPlaceId(@Param("placeId") Long placeId); List findALlByPlace(Place place); + + @Query(""" + SELECT url FROM ( + SELECT pi.url as url FROM PlaceImage pi + UNION + SELECT u.profileImage as url FROM User u WHERE u.profileImage IS NOT NULL + UNION + SELECT m.iconUrl as url FROM Magazine m WHERE m.iconUrl IS NOT NULL + ) urls + """) + List findAllImageUrls(); } diff --git a/src/main/java/com/pickyfy/pickyfy/service/ImageCleanupService.java b/src/main/java/com/pickyfy/pickyfy/service/ImageCleanupService.java new file mode 100644 index 0000000..e241249 --- /dev/null +++ b/src/main/java/com/pickyfy/pickyfy/service/ImageCleanupService.java @@ -0,0 +1,70 @@ +package com.pickyfy.pickyfy.service; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.ListObjectsV2Request; +import com.amazonaws.services.s3.model.ListObjectsV2Result; +import com.amazonaws.services.s3.model.S3ObjectSummary; +import com.pickyfy.pickyfy.repository.PlaceImageRepository; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +@RequiredArgsConstructor +public class ImageCleanupService { + + private final AmazonS3Client amazonS3Client; + private final PlaceImageRepository placeImageRepository; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + @Value("${cloud.aws.s3.path.image}") + private String imageFolder; + + @Scheduled(cron = "0 0 2 * * 1") // 매주 월요일 새벽 2시에 실행 + public void cleanupOrphanedImages() { + try { + // 1. S3의 모든 이미지 목록 가져오기 + ListObjectsV2Request listReq = new ListObjectsV2Request() + .withBucketName(bucket) + .withPrefix(imageFolder); + + List s3ImageUrls = new ArrayList<>(); + ListObjectsV2Result result; + + do { + result = amazonS3Client.listObjectsV2(listReq); + for (S3ObjectSummary obj : result.getObjectSummaries()) { + s3ImageUrls.add(amazonS3Client.getUrl(bucket, obj.getKey()).toString()); + } + listReq.setContinuationToken(result.getNextContinuationToken()); + } while (result.isTruncated()); + + // 2. DB에 저장된 모든 이미지 URL 가져오기 + List dbImageUrls = placeImageRepository.findAllImageUrls(); + + // 3. S3에는 있지만 DB에는 없는 이미지 찾기 + List orphanedImages = s3ImageUrls.stream() + .filter(s3Url -> !dbImageUrls.contains(s3Url)) + .toList(); + + // 4. 고아 이미지 삭제 + for (String imageUrl : orphanedImages) { + String fileName = imageUrl.substring(imageUrl.lastIndexOf("/") + 1); + amazonS3Client.deleteObject(bucket, imageFolder + fileName); + log.info("Deleted orphaned image: {}", imageUrl); + } + + log.info("Image cleanup completed. Deleted {} orphaned images", orphanedImages.size()); + + } catch (Exception e) { + log.error("Error during image cleanup", e); + } + } +}