스프링부트/Code Tip

Amazon S3를 통해 이미지 업로드할 때

삼록이 2025. 9. 6. 21:09
public String saveImgAndReturn(MultipartFile image){
       //이미지 파일인지 검증
       String contentType = image.getContentType();
        if(contentType == null || contentType.startsWith("image/")){
            throw new IllegalArgumentException("Only Image files can be uploaded");
        }

     	
        String fileName = "jobPost/" + LocalDate.now() + "/" + UUID.randomUUID();
        try {
            PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                    .key(fileName)
                    .bucket(bucket)
                    .contentType(image.getContentType())
                    .build();
            s3Client.putObject(putObjectRequest,RequestBody.fromInputStream(image.getInputStream(), image.getSize()));


            String url = s3Client.utilities().getUrl(a->a.bucket(bucket).key(fileName)).toString();

            //이미지나 첨부파일 테이블에 저장로직 
            //Attachment attachment = Attachment.builder.~~~.builde();
            // attachmentRepository.save(attachment);
            
            return url;
        } catch (IOException e){
            throw new RuntimeException("image does not saved");
        }

 

 

 

1. 이 파일이 이미지인지 아닌지 검증하려면 MIME 타입을 검증해야한다.

String contentType = image.getContentType();
if(contentType == null || !contentType.startsWith("image/")){
    throw new IllegalArgumentException("Only Image files can be uploaded");
}

 

*MIME (Multipurpost Internet Mail Extensions) 타입이란?

원래는 이메일에서 첨부파일 구분용으로 시작되었지만, 현재는 HTTP통신에서 첨부되는 파일의 종류를 나타내는 표준이다.

이 MIME타입은 HTTP 요청/응답의 헤더에 Contet-Type 키값에 대한 Value로 들어온다.

 

ex.

  • Content - Type : application/json ->JSON 데이터
  • Content - Type : image/jpg(혹은 png,jpeg,gif) ->이미지
  • Content - Type : text/plain ->일반 텍스트
  • Content - Type : application/pdf  ->PDF 파일

 

다시 본론으로 돌아와서 말하자면,

사용자가 파일을 업로드하면 브라우저가 자동으로 인식하여 해당 파일에 알맞는 Content-Type을 보내준다.

사실 파일의 확장자로도 검증을 할 수 있으나, 사용자가 파일명을 .jpg로 바꾸면 원래 .exe였던 악의적인 파일도 이미지 확장자가 되니까 확장자로만 검증하는 것은 신뢰할 수 없는 방법이다.

그래서 해당 파일에 MIME타입으로 검증하는 코드를 심어놓은 것이다.

 

 

 

 

2.S3 버킷에 폴더별로 저장하려면 prefix를 사용하면 된다.

String fileName = "jobPost/" + LocalDate.now() + "/" + UUID.randomUUID();
try {
    PutObjectRequest putObjectRequest = PutObjectRequest.builder()
            .key(fileName)
            .bucket(bucket)
            .contentType(image.getContentType())
            .build();
    s3Client.putObject(putObjectRequest,RequestBody.fromInputStream(image.getInputStream(), image.getSize()));

 

s3에 저장하는 key값으로 fileName 문자열을 사용했고,

그 문자열은 jobPost/ 와 LocalDate.now()/로  prefix(접두어?)를 사용했다.

그러면 S3 버킷에는 아래처럼 jobPost 폴더가 생기고 또 그안에는 날짜별로 폴더에 저장될 수 있다.

 

 

 

3.S3에 파일을 전송할 때는 스트리밍 방식을 사용하는 것이 효율적이다.

s3Client.putObject(putObjectRequest,RequestBody.fromInputStream(image.getInputStream(), image.getSize()));

 

파일을 읽어 S3에 전송하는 방식에는 위 코드처럼 RequestBody.fromInputStream()방식도 있고, 
또 RequestBody.fromBytes(image.getBytes()) 방식도 있다.

 

첫번째 방법은 InputStream을 통해 파일 데이터를 조금씩 읽어 네트워크로 바로 전송하는 방식이다.

이 방식은 메모리 효율적이라 대용량 파일에도 안전하다.

 

두번째 방법은 파일 전체를 byte[]로 메모리에 로드 하여 S3에 한 방에 업로드한다.

이 경우에는 파일 크기 만큼 메모리를 즉시 점유한다.

예를 들어, 100MB 파일 업로드 시, 서버 메모리에 그대로 100MB가 그대로 올라간다.

그렇다면 수많은 유저가 동시에 이 메서드를 호출할 경우 OutOfMemory 에러가 날 리스크가 존재하는 것이다.

따라서, 실 서비스에서 안전한 방식인 InputStream방식을 택하는 것을 권한다