기타/개발일기

Spring Boot S3에 이미지 업로드

hu6r1s 2024. 3. 13. 19:16

Spring Boot 이미지 업로드

처음에 이미지 업로드는 서버에 임의로 지정해놓은 디렉터리에 저장되도록 구현했다.

private PostImage getPostImage(MultipartFile file, Post post) throws IOException {
    if (file != null) {
      String originalFilename = file.getOriginalFilename();
      String saveFileName = createSaveFileName(originalFilename);
      file.transferTo(new File(getFullPath(saveFileName))); 		// 여기서 임의의 위치로 이미지 저장
      String filePath = uploadPath + saveFileName;
      String contentType = file.getContentType();
      PostImage image = PostImage.builder()
        .fileName(originalFilename)
        .saveFileName(saveFileName)
        .contentType(contentType)
        .filePath(filePath)
        .post(post)
        .build();
      return image;
    }
    return PostImage.builder()
      .fileName(null)
      .saveFileName(null)
      .contentType(null)
      .filePath(null)
      .post(post)
      .build();
  }
  private String createSaveFileName(String originalFilename) {
    String ext = extractExt(originalFilename);
    String uuid = UUID.randomUUID().toString();
    return uuid + "." + ext;
  }
  private String extractExt(String originalFilename) {
    int pos = originalFilename.lastIndexOf(".");
    return originalFilename.substring(pos + 1);
  }
  private String getFullPath(String filename) {
    return uploadPath + filename;
  }

서버에 이미지 데이터를 저장할 수도 있지만 S3를 이용해 이미지를 따로 저장하는 것이 더 효율적이므로 S3를 사용해보고 싶었다.

S3 설정

S3를 설정하려고 하니 거의 모든 블로그가 옛날에 사용하던 Dependency로 이제 더이상 업데이트도 되지 않고 취약점이 발견된 것 같다. 그래서 최신 버전으로 사용해보려고 방법을 찾아보다 dependency 주입부터 난관에 부딪혔다.

Dependency

implementation 'io.awspring.cloud:spring-cloud-aws-s3:3.1.0'

이것을 통해서 S3를 사용할 수 있게 된다.

cloud:
  aws:
    s3:
      bucket: 
    credentials:
      access-key: 
      secret-key: 
    region:
      static: ap-northeast-2
      auto: false
    stack:
      auto: false

S3에서 만든 버킷의 이름, 액세스 키 시크릿 키를 모두 기입해주면 된다.

버킷 생성 방법에 대해서는 따로 언급하지 않겠다.

S3Config

package com.nbcampif.ifstagram.global.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;

@Configuration
public class S3Config {

  @Value("${cloud.aws.credentials.access-key}")
  private String accessKey;

  @Value("${cloud.aws.credentials.secret-key}")
  private String secretKey;

  @Value("${cloud.aws.region.static}")
  private String region;

  @Bean
  public S3Client amazonS3() {
    AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKey, secretKey);
    return S3Client.builder()
      .region(Region.of(region))
      .credentialsProvider(StaticCredentialsProvider.create(credentials))
      .build();
  }
}

 

Config를 구성하는 것도 뭔가 많이 바껴서 찾아보고 하느라 시간이 오래 걸렸다.

이렇게 액세스 키와 시크릿 키를 통해 접근을 허용해주면 된다.

개발 구현

private PostImage getPostImage(MultipartFile file, Post post) {
    try {
      String originalFilename = file.getOriginalFilename();
      String extension = StringUtils.getFilenameExtension(originalFilename);
      String saveFileName = createSaveFileName(originalFilename);
      PutObjectRequest putObjectRequest = PutObjectRequest.builder()
        .bucket(bucket)
        .key(uploadPath + saveFileName)
        .contentType(extension)
        .build();
      PutObjectResponse response = s3Client
        .putObject(putObjectRequest, RequestBody.fromBytes(file.getBytes()));

      String filePath = uploadPath + saveFileName;
      String contentType = file.getContentType();

      if(response.sdkHttpResponse().statusText().orElse("FAIL").equals("OK")){
        PostImage image = PostImage.builder()
          .fileName(originalFilename)
          .saveFileName(saveFileName)
          .contentType(contentType)
          .filePath(filePath)
          .post(post)
          .build();
        return image;
      }else{
        throw new IllegalStateException("AWS에 파일을 올리는데 실패했습니다.");
      }
    } catch(IOException ie){
      throw new RuntimeException(ie.getMessage());
    } catch (S3Exception ae){
      throw new RuntimeException(ae.getMessage());
    } catch (IllegalStateException se){
      throw new RuntimeException(se.getMessage());
    }
  }

 

PutObjectRequest를 사용하여 이미지를 S3로 업로드하고 S3에 업로드가 성공하면 데이터베이스에 이미지 정보를 저장하도록 하였다.

public String getImage(Long id) throws MalformedURLException {
    PostImage image = postImageRepository.findById(id).orElse(null);

    if (image == null || image.getFilePath() == null) {
       return null;
    }

    GetUrlRequest request = GetUrlRequest
      .builder()
      .bucket(bucket)
      .key(image.getFilePath())
      .build();

    return s3Client.utilities().getUrl(request).toExternalForm();
  }

 

이미지를 가져오는 것을 이미지 주소를 넘겨주면 프론트에서 이미지 태크에 넣어주면서 이미지를 볼 수 있도록 하였다.

이전에는 GetUrl이라는 메서드가 있었는데 지금은 utilities메서드 안에 있다.

GetUrlRequest를 사용하여 이미지를 가져오고 전체 url를 보내주면 된다.