feat: add scheduled task for cleaning up unused S3 images
This commit is contained in:
parent
6b2a77b391
commit
581f436c5f
@ -1,9 +1,8 @@
|
||||
package com.magamochi.mangamochi.client;
|
||||
|
||||
import io.github.resilience4j.retry.annotation.Retry;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import io.github.resilience4j.retry.annotation.Retry;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
package com.magamochi.mangamochi.client;
|
||||
|
||||
import com.magamochi.mangamochi.model.dto.MangaDexMangaDTO;
|
||||
import io.github.resilience4j.retry.annotation.Retry;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.github.resilience4j.retry.annotation.Retry;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
package com.magamochi.mangamochi.service;
|
||||
|
||||
import static java.util.Objects.nonNull;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import software.amazon.awssdk.core.sync.RequestBody;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.*;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@ -34,4 +39,43 @@ public class S3Service {
|
||||
|
||||
return s3Client.getObject(request);
|
||||
}
|
||||
|
||||
public void deleteObjects(Set<String> objectKeys) {
|
||||
if (CollectionUtils.isEmpty(objectKeys)) {
|
||||
throw new IllegalArgumentException("Object key list cannot be null or empty");
|
||||
}
|
||||
|
||||
var objectsToDelete =
|
||||
objectKeys.stream().map(key -> ObjectIdentifier.builder().key(key).build()).toList();
|
||||
|
||||
var deleteRequest =
|
||||
DeleteObjectsRequest.builder()
|
||||
.bucket(bucket)
|
||||
.delete(Delete.builder().objects(objectsToDelete).build())
|
||||
.build();
|
||||
|
||||
s3Client.deleteObjects(deleteRequest);
|
||||
}
|
||||
|
||||
public List<String> listAllObjectKeys() {
|
||||
var keys = new ArrayList<String>();
|
||||
String continuationToken = null;
|
||||
|
||||
do {
|
||||
var requestBuilder = ListObjectsV2Request.builder().bucket(bucket).maxKeys(1000);
|
||||
|
||||
if (nonNull(continuationToken)) {
|
||||
requestBuilder.continuationToken(continuationToken);
|
||||
}
|
||||
|
||||
var response = s3Client.listObjectsV2(requestBuilder.build());
|
||||
|
||||
response.contents().forEach(s3Object -> keys.add(s3Object.key()));
|
||||
|
||||
continuationToken = response.isTruncated() ? response.nextContinuationToken() : null;
|
||||
|
||||
} while (nonNull(continuationToken));
|
||||
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
package com.magamochi.mangamochi.task;
|
||||
|
||||
import com.magamochi.mangamochi.model.entity.Image;
|
||||
import com.magamochi.mangamochi.model.repository.*;
|
||||
import com.magamochi.mangamochi.service.S3Service;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Log4j2
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ImageCleanupTask {
|
||||
@Value("${image-service.clean-up-enabled}")
|
||||
private Boolean cleanUpEnabled;
|
||||
|
||||
private final S3Service s3Service;
|
||||
private final ImageRepository imageRepository;
|
||||
|
||||
@Scheduled(cron = "@weekly")
|
||||
public void cleanupImages() {
|
||||
if (!cleanUpEnabled) {
|
||||
log.info("S3 Image cleanup disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Getting unused S3 object keys to remove.");
|
||||
|
||||
var imageKeys = s3Service.listAllObjectKeys();
|
||||
|
||||
var existingImages =
|
||||
imageRepository.findAll().parallelStream()
|
||||
.map(Image::getFileKey)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
var keysToRemove =
|
||||
imageKeys.parallelStream()
|
||||
.filter(imageKey -> !existingImages.contains(imageKey))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
log.info("Removing {} objects from S3 storage", keysToRemove.size());
|
||||
|
||||
s3Service.deleteObjects(keysToRemove);
|
||||
|
||||
log.info("Image cleanup finished.");
|
||||
}
|
||||
}
|
||||
@ -67,3 +67,6 @@ resilience4j:
|
||||
seconds: 5
|
||||
retry-exceptions:
|
||||
- feign.FeignException
|
||||
|
||||
image-service:
|
||||
clean-up-enabled: ${IMAGE_SERVICE_CLEAN_UP_ENABLED:false}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user