AWS S3를 이용한 이미지 처리(저장,조회,삭제,다운로드)

구본식·2023년 3월 17일
3
post-thumbnail

AWS S3의 버킷 생성 및 IAM 사용자 추가 등은 쉽게 검색을 통해 찾아볼 수 있으니, 필요시 찾아보시길 바랍니다.

1. gradle

//aws S3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

2. YML 파일 설정

YML 파일에 설정 정보를 분리해서 관리할 수 있는 방법을 새롭게 찾아 이 방식을 사용해보았다.

application-*.yml 이름으로 생성한 파일들을 application.yml 파일의 Spring.profiles.include 속성안에 * 값들을 넣어 어플리케이션 구동시 읽을 수 있도록 할 수 있다.

application.yml

# s3 연습
spring:
  profiles:
    include:
      - aws
      - credentials
      - redis

application-aws.yml

# AWS 설정 정보
cloud:
  aws:
    s3:
      bucket: s3-image-pratice
      folder:
        folderName1: user/
        folderName2: post/
    region:
      static: ap-northeast-2
    stack:
      auto: false

기능마다 S3 버킷의 다른 폴더에 이미지들을 저장하여 관리하기 위해서 folder 속성을 추가로 사용하였다.

application-credentials.yml

# S3 key 관리
cloud:
  aws:
    credentials:
      accessKey: [엑세스 키]
      secretKey: [시크릿 키]

3. S3Component

application.yml 기입한 AWS S3의 정보들을 사용하여 Component를 만들어 놓았다.

@Component
@Getter
public class S3Component {

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

    @Value("${cloud.aws.s3.folder.folderName1}")
    private String userFolder;

    @Value("${cloud.aws.s3.folder.folderName2}")
    private String postFolder;
}

4. FileService(인터페이스)

AWS S3를 이용한 방식을 말고도 다른 클라우드 서비스를 이용하여 파일 업로드 기능을 구현해야하는 상황도 있을 것이다.
이러한 확장성을 고려하여 파일 업로드와 관련된 기능을 인터페이스와 구현체로 분리시켜 놓았다.

public interface FileService {

    //파일 업로드
    String uploadFile(MultipartFile file, FileFolder fileFolder);

    //파일 삭제
    void deleteFile(String fileName);

    //파일 URL 조회
    String getFileUrl(String fileName);

    //파일 다운로드
    byte[] downloadFile(String fileName) throws FileNotFoundException;

    //폴더 조회
    String getFileFolder(FileFolder fileFolder);

}

5. S3Service(구현체)

5.1 uploadFile

@Component
@RequiredArgsConstructor
public class S3Service implements FileService{

    private final S3Component s3Component;
    private final AmazonS3 amazonS3;

    @Override
    public String uploadFile(MultipartFile file, FileFolder fileFolder) {

        //파일 이름 생성
        String fileName = getFileFolder(fileFolder) + createFileName(file.getOriginalFilename());

        //파일 변환
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentLength(file.getSize());
        objectMetadata.setContentType(file.getContentType());

        //파일 업로드
        try(InputStream inputStream = file.getInputStream()) {
            amazonS3.putObject(
                    new PutObjectRequest(s3Component.getBucket(), fileName, inputStream, objectMetadata).withCannedAcl(CannedAccessControlList.PublicReadWrite)
            );
        } catch (IOException e) {
            throw new IllegalArgumentException(String.format("파일 변환 중 에러가 발생하였습니다. (%s)", file.getOriginalFilename()));
        }

        return fileName;
    }

    //파일 이름 생성 로직
    private String createFileName(String originalFileName) {
        return UUID.randomUUID().toString().concat(getFileExtension(originalFileName));
    }

    //파일의 확장자명을 가져오는 로직
    private String getFileExtension(String fileName){
        try{
            return fileName.substring(fileName.lastIndexOf("."));
        }catch(StringIndexOutOfBoundsException e) {
            throw new IllegalArgumentException(String.format("잘못된 형식의 파일 (%s) 입니다.",fileName));
        }
    }
}

FileFolder을 Enum으로 구성하여 버킷내 폴더를 기능에 따라 선택할 수 있게 하였다.

AWS S3가 제공해주는 putObject 메소드를 살펴보게 되면, 2가지 방식이 존재한다.

  1. AWS S3에 이미지를 업로드 하기 위해, 전달받은 이미지(바이너리 파일)을 서버내에
    임시 파일(new File)로 만들었다가 임시 파일을 업로드 하는 방식.
  2. 이러한 과정을 거치지 않고, MultipartFileInputStream(파일의 Byte)ObjectMetaData(파일의 정보)를 사용해서 파일을 업로드 하는 방식.

임시 파일을 만드는 것은 불필요한 과정이라고 생각이 들어 2번 방식을 사용하였다.

5.2 deleteFile

@Component
@RequiredArgsConstructor
public class S3Service implements FileService{

    private final S3Component s3Component;
    private final AmazonS3 amazonS3;

    @Override
    public void deleteFile(String fileName) {
        amazonS3.deleteObject(new DeleteObjectRequest(s3Component.getBucket(), fileName));
    }
}

5.3 getFileUrl

@Component
@RequiredArgsConstructor
public class S3Service implements FileService{

    private final S3Component s3Component;
    private final AmazonS3 amazonS3;

    @Override
    public String getFileUrl(String fileName) {
        return amazonS3.getUrl(s3Component.getBucket(), fileName).toString();
    }
 }

AWS S3에 저장된 파일 이름에 해당하는 URL(경로)를 알아내는 함수이다.

5.4 downloadFile

@Component
@RequiredArgsConstructor
public class S3Service implements FileService{

    private final S3Component s3Component;
    private final AmazonS3 amazonS3;

    @Override
    public byte[] downloadFile(String fileName) throws FileNotFoundException {

        //파일 유무 확인
        validateFileExists(fileName);

        S3Object s3Object = amazonS3.getObject(s3Component.getBucket(), fileName);
        S3ObjectInputStream s3ObjectContent = s3Object.getObjectContent();

        try {
            return IOUtils.toByteArray(s3ObjectContent);
        }catch (IOException e ){
            throw new FileDownloadFailedException();
        }
    }

    private void validateFileExists(String fileName) throws FileNotFoundException {
        if(!amazonS3.doesObjectExist(s3Component.getBucket(), fileName))
            throw new FileNotFoundException();
    }
}

다운로드 받을 파일이 S3에 존재하는지 검증을 먼저 하도록 하였다.

다운로드 받을 파일이 있다면, amazonaws Utils 함수를 사용해서 파일(이미지)의 InputStream을 읽어 byte배열로 바꾸어 주었다.

5.5 getFileFolder

@Component
@RequiredArgsConstructor
public class S3Service implements FileService{

    private final S3Component s3Component;
    private final AmazonS3 amazonS3;

    @Override
    public String getFileFolder(FileFolder fileFolder) {

        String folder = "";
        if(fileFolder == FileFolder.USER_IMAGES) {
            folder = s3Component.getUserFolder();

        }else if(fileFolder ==FileFolder.POST_IMAGES){
            folder = s3Component.getPostFolder();
        }
        return folder;
    }
}

기능에 따라 다른 폴더에 파일(이미지)를 저장하기 위해서, 저장될 폴더를 선택하는 함수이다.

이번 포스터에서는 AWS S3를 이용한 파일(이미지) 처리에 기능 구현에 집중하였다.

해당 기능을 테스트 하기 위해서 간단히 Article, Image 엔티티와 서비스, 레포지토리를 구현하여 컨트롤러를 통해 테스트 해보았다.

해당 포스터에는 없지만 아래 github 링크를 통해 완전한 코드를 볼 수 있다.
https://github.com/BonSik-Koo/Funtion-pratice/tree/master/src/main/java/project/AMS/awsS3

profile
백엔드 개발자를 꿈꾸며 기록중💻

1개의 댓글

comment-user-thumbnail
2023년 6월 8일

잘 보고 갑니다.. 도움 많이 될 것 같아요

답글 달기