Spring Boot와 S3를 연동한 파일 업로드

Louie·2022년 6월 26일
7

프로젝트

목록 보기
6/6

Spring과 AWS의 S3를 사용해서 파일 업로드 기능을 구현해 보겠습니다.

스프링 클라우드 의존성 추가

Spring Cloud AWS를 사용하면 손쉽게 S3를 통한 파일 업로드 기능을 구현할 수 있습니다.

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

버킷에 접근하기 위한 사용자 등록

IAM의 사용자 탭으로 이동한 다음 오른쪽에 빨간색으로 표시되어 있는 사용자 추가 버튼을 클릭합니다.

image

액세스 유형은 액세스 키 - 프로그래밍 방식 액세스를 선택합니다.

image

S3와 관련된 모든 작업을 할 수 있도록 AmazonS3FullAccess 정책을 선택합니다.

image

따로 언급하지 않고 넘어갔던 태그 설정 부분은 원하는 대로 설정하면 됩니다.

마지막으로 사용자 추가에 성공했다면 액세스 키를 다운받아서 잘 보관해놓습니다.

image

application.yml 파일 설정

  • 해당 파일을 설정하기 전에 파일 업로드를 위한 S3 버킷을 생성해 줘야 합니다.
  • 버킷을 생성했다면 아래와 같이 application.yml 파일에 설정을 추가해 줍니다.
  • access key와 secret key는 바로 위에서 생성한 사용자의 키를 넣어줍니다.
  • 해당 정보는 공개되면 보안에 좋지 않기 때문에 환경 변수로 처리하는 걸 추천드립니다.
//  application.yml
cloud:
  aws:
    region:
      static: 버킷의 리전(ex: ap-northeast-2)
    s3:
      bucket: 버킷 이름(ex: issue-tracker-file-upload)
		credentials:
      access-key: access key
      secret-key: secret key
    stack:
      auto: false
		

실제 코드 구현

Entity

@NoArgsConstructor(access = PROTECTED)
@Entity
public class UploadFile {

    @Id @GeneratedValue(strategy = IDENTITY)
    private Long id;
    private String uploadFileName;
    private String storeFileUrl;

    public UploadFile(String uploadFileName, String storeFileUrl) {
        this.uploadFileName = uploadFileName;
        this.storeFileUrl = storeFileUrl;
    }
}
  • S3에 같은 이름의 파일을 업로드하게 된다면 먼저 업로드된 파일이 나중에 업로드한 파일로 덮어써지는 문제가 발생합니다.
  • 해당 문제를 해결하기 위해 저장소에는 실제 파일 이름이 아닌 중복되지 않는 별도의 파일 이름으로 저장해야 합니다.
  • 결국 저장소에 저장된 파일의 URL을 나타내는 storeFileUrl 필드를 추가하게 되었습니다.
  • uploadFileName 필드는 실제로 사용자가 업로드한 파일명을 나타냅니다.

Controller

@RequiredArgsConstructor
@RestController
public class S3Controller {

    private final S3Service s3Service;

    @PostMapping
    public void uploadFile(@RequestParam MultipartFile multipartFile)
        throws IOException {
        s3Service.saveUploadFile(multipartFile);
    }

}
  • MultipartFile 객체를 컨트롤러에서 파라미터로 받아올 수 있습니다.
  • 해당 객체는 HTTP Content-Type을 multipart/form-data 방식으로 요청한 여러 가지 형식의 파일들의 정보를 가지고 있습니다.

image

  • multipart/form-data 방식은 사진의 HTTP 메시지처럼 각 항목이 구분되어 있기 때문에 여러 파일의 정보를 저장할 수 있습니다.

Service

@RequiredArgsConstructor
@Service
public class S3Service {

    private final AmazonS3Client amazonS3Client;
    private final UploadFileRepository uploadFileRepository;

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

    @Transactional
    public void saveUploadFile(MultipartFile multipartFile) throws IOException {
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(multipartFile.getContentType());
        objectMetadata.setContentLength(multipartFile.getSize());

        String originalFilename = multipartFile.getOriginalFilename();
        int index = originalFilename.lastIndexOf(".");
        String ext = originalFilename.substring(index + 1);

        String storeFileName = UUID.randomUUID() + "." + ext;
        String key = "test/" + storeFileName;

        try (InputStream inputStream = multipartFile.getInputStream()) {
            amazonS3Client.putObject(new PutObjectRequest(bucket, key, inputStream, objectMetadata)
                .withCannedAcl(CannedAccessControlList.PublicRead));
        }

        String storeFileUrl = amazonS3Client.getUrl(bucket, key).toString();
        UploadFile uploadFile = new UploadFile(originalFilename, storeFileUrl);
        uploadFileRepository.save(uploadFile);
    }

}
  • Spring Boot Cloud AWS를 사용하면 AmazonS3Client와 같은 S3 관련 Bean들이 자동 생성됩니다.
  • ObjectMetadata 객체에 content-length를 지정하지 않으면 warning 로그가 발생합니다.
  • 변수 key에 저장할 디렉터리 경로 + 파일 이름을 입력해 주면 됩니다.
  • AmazonS3Client.getUrl() 메서드를 사용하면 파라미터의 입력한 bucket의 key에 저장된 객체의 Url을 얻어올 수 있습니다.
  • withCannedAcl 메서드를 통해 업로드한 파일을 모두가 읽을 수 있게 설정합니다.
  • UUID를 저장소에 저장하는 파일 이름(storeFileName)으로 사용해서 같은 이름의 파일을 업로드할 때 파일이 덮어써지는 문제를 방지했습니다.

파일 업로드 확인

postman을 통해 파일 업로드 요청을 보냅니다.

image

정상적으로 DB에 파일이 업로드된 것을 확인할 수 있습니다.

image

해당 기능을 구현하면서 발생했던 문제

SdkClientException: Failed to connect to service endpoint

FileSizeLimitExceededException

마무리

Spring Boot Cloud AWS 덕분에 파일 업로드 기능을 쉽게 구현할 수 있습니다.

궁금하신 부분이나 피드백은 편하게 댓글로 남겨주시면 감사하겠습니다 😀

참고 자료

SpringBoot & AWS S3 연동하기

AWS s3 upload source Tip

인프런 김영한 스프링 MVC2

profile
백엔드 개발자를 준비하고 있는 Louie입니다.

6개의 댓글

comment-user-thumbnail
2022년 6월 26일

루이 글 너무 잘 읽었습니다! 저도 해당 기능 구현하려고 하는데 루이 글이 많은 도움이 될 것 같아요! 미리 고맙습니다 ㅎㅎㅎㅎㅎㅎ

1개의 답글
comment-user-thumbnail
2022년 6월 26일

루이 글을 구현하기 전에 미리 봤더라면...😭 정리 정말 깔끔하게 잘했네요!
현재 저도 SdkClientException: Failed to connect to service endpoint 가 발생하고 있어요 ㅠㅠ
큰 문제는 아니라서 무시하고 진행하고 있었는데 끄는 방법이 있었네요. 앞으로 프로젝트 Run 할 때마다 4초씩 save!! 좋은 글 감사합니다 루이!!

1개의 답글
comment-user-thumbnail
2022년 7월 5일

루이의 S3 공략 따봉드립니다 잘읽고갑니닷 ㅎㅎ

1개의 답글