티스토리 뷰

공연 동행 구인 서비스를 개발하며, 

동행 구인글 생성 중, 이미지 첨부가 가능하여

AWS의 S3 클라우드 스토리지를 활용하여 이미지를 관리하고 있습니다🖼️

 

동행 구인글 생성 및 수정 시에, 사용되고 있기도 하고

추후에 다른 기능에서도 이미지 업로드가 추가될 수 있기에

 

이미지 저장 시 사용되는 공통 로직들을 유틸 클래스를 만들어 추출하였습니다!

 


이미지 관련 공통 기능들

putObject

단일 이미지 파일을 S3에 업로드하고 이미지 URL을 반환합니다.

 

putObjects

여러 이미지 파일을 받아 putObject 메서드를 이용하여 각 이미지를 업로드하고

S3에 업로드 하고 이미지 URL 리스트를 반환합니다.
업로드되는 이미지가 없는 경우 기본 이미지 URL을 반환합니다.


deleteObject

주어진 이미지 URL에서 이미지의 파일명을 추출하여 해당하는 이미지를 S3에서 삭제합니다.

 

deleteObjects

여러 이미지 URL을 받아 deleteObject 메서드를 이용하여 각 이미지를 삭제합니다.

기본 이미지 URL은 삭제하지 않습니다.


validateFileExtension

이미지 파일의 확장자를 검증합니다.

 

createObjectMetadataWith

업로드되는 이미지 파일의 메타데이터를 생성합니다.

 

@Slf4j
@RequiredArgsConstructor
@Component
public class S3ImageUtil {

    private final AmazonS3 amazonS3;

    private final List<String> allowedExtensions = Arrays.asList("jpg", "png", "jpeg");

    @Value("${cloud.aws.s3.default-image-url}")
    private String defaultImageUrl;

    @Value("${cloud.aws.s3.bucket}/")
    private String bucket;

    public String putObject(MultipartFile multipartFile, ImageType imageType) {
        String originalFilename = multipartFile.getOriginalFilename();
        validateFileExtension(originalFilename);
        String s3Filename = UUID.randomUUID() + "-" + originalFilename;

        try {
            amazonS3.putObject(bucket + imageType.getName(), s3Filename,
                    multipartFile.getInputStream(), createObjectMetadataWith(multipartFile));
        } catch (AmazonS3Exception e) {
            log.error("Amazon S3 error while uploading file: " + e.getMessage());
            throw new FailFileUploadException(GlobalErrorCode.FAIL_FILE_UPLOAD);
        } catch (SdkClientException e) {
            log.error("AWS SDK client error while uploading file: " + e.getMessage());
            throw new FailFileUploadException(GlobalErrorCode.FAIL_FILE_UPLOAD);
        } catch (IOException e) {
            log.error("IO error while uploading file: " + e.getMessage());
            throw new FailFileUploadException(GlobalErrorCode.FAIL_FILE_UPLOAD);
        }

        return amazonS3.getUrl(bucket + imageType.getName(), s3Filename).toString();
    }

    public List<String> putObjects(List<MultipartFile> multipartFiles, ImageType imageType) {
        List<String> imageUrls = new ArrayList<>();
        if (multipartFiles.isEmpty() || multipartFiles.get(0).isEmpty()) {
            imageUrls.add(defaultImageUrl);
        } else {
            multipartFiles.forEach(
                    multipartFile -> imageUrls.add(putObject(multipartFile, imageType)));
        }

        return imageUrls;
    }

    public void deleteObject(String originImageUrl, ImageType imageType) {
        String filename = originImageUrl.substring(originImageUrl.lastIndexOf(".com/") + 1);
        amazonS3.deleteObject(bucket + imageType.getName(), filename);
    }

    public void deleteObjects(List<String> originImageUrls, ImageType imageType) {
        if (originImageUrls.size() != 1 || !originImageUrls.get(0).equals(defaultImageUrl)) {
            originImageUrls.forEach(originImageUrl -> deleteObject(originImageUrl, imageType));
        }
    }

    private void validateFileExtension(String originalFilename) {
        String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1)
                .toLowerCase();
        if (!allowedExtensions.contains(fileExtension)) {
            throw new InvalidFileExtensionException(GlobalErrorCode.INVALID_FILE_EXTENSION);
        }
    }

    private ObjectMetadata createObjectMetadataWith(MultipartFile multipartFile) {
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(multipartFile.getContentType());
        objectMetadata.setContentLength(multipartFile.getSize());

        return objectMetadata;
    }
}

 

 


S3 CORS 에러

프론트 측에서, 갑자기 이미지 CORS 에러가 발생한다고 연락이 왔습니다😲

생각해보니, S3 이미지 url로 접근하려면 스프링부트를 거치는게 아니다 보니, 

스프링부트에서 설정한 CORS랑은 관련이 없겠구나 싶었고

그럼 S3에 CORS 설정이 있나..?하고 찾아보니 설정이 가능했습니다👀

 

아래와 같이 프론트 url 두개를 추가해두었고, GET과 HEAD에 대해서만 접근을 허용했습니다.