Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

DOing

[AWS] S3를 Spring에서 사용하기 - 이미지 업로드 본문

AWS

[AWS] S3를 Spring에서 사용하기 - 이미지 업로드

mangdo 2021. 4. 22. 14:01

[ 버킷 정책 설정 ]

버킷 정책 수정한다.

퍼블릭 엑세스 차단 해제를 하고, 버킷 정책을 설정하는 방법을 사용할 것이다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::{버킷명}/*"
    }
  ]
}

S3 버킷 정책에 대한 AWS 공식문서

정책이 잘 적용되었는지 확인하려면, 파일을 업로드 해보면된다.

 

[ pom.xml ]

aws관련 의존성을 추가한다.

<!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk -->
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-s3</artifactId>
    <version>1.11.1001</version>
</dependency>

aws-java-sdk이 아닌 aws-java-sdk-s3를 받아야한다. 기능이 동작하지 않는 것은 아니지만 aws-java-sdk로 하면 모든 SDK를 가져오게 되어 너무 다운을 많이 받는다. 이때문에 패키징 시간이 크게 늘어났었다.

 

Maven에서 SDK 사용 관련한 AWS 공식문서

[ 파일 업로드 ]

기존의 코드에서는 로컬 컴퓨터 내에 이미지를 저장을 했다. (C:\\phoneMall\\upload)

그러나 이제는 AWS로 배포를 할 계획이였기때문에 로컬컴퓨터가 아닌 S3에 저장을 하는 코드로 변경이 필요하다.

기존의 코드를 최대한 건들지 않는 선에서 작업하였다.

 

<기존 코드 - RestController>

private void imageFolderSave(MultipartFile mainImage, List<ProductImageVO> imagelist, String imageType) {

  String uploadFolder = "c:\\phoneMall\\upload";

  // make folder
  String uploadFolderPath = getFolder();
  File uploadPath = new File(uploadFolder, uploadFolderPath);
  if(!uploadPath.exists()) {
  	uploadPath.mkdirs();
  }

  UUID uuid = UUID.randomUUID();
  String uploadImageName = uuid.toString()+"_"+mainImage.getOriginalFilename();
  try {
    // original image save
    File saveImage = new File(uploadPath, uploadImageName);
    mainImage.transferTo(saveImage);

  if(imageType.equals("mainImage")) {
    // thumbnail image create, save
    FileOutputStream thumbnail = new FileOutputStream(new File(uploadPath, "s_"+uploadImageName));
    Thumbnailator.createThumbnail(mainImage.getInputStream(), thumbnail, 400, 333);
    thumbnail.close();
  }

  // productImageVO create
  imagelist.add(new ProductImageVO(uuid.toString(), uploadFolderPath.toString().replace("\\", "/"), mainImage.getOriginalFilename(), imageType, null));

  }catch(Exception e){log.error(e.getMessage());}
}

<수정 코드 - RestController>

private void imageFolderSave(MultipartFile image, List<ProductImageVO> imagelist, String imageType) {

  // make folder
  String uploadFolderPath = getFolder();

  UUID uuid = UUID.randomUUID();
  String uploadImageName = uuid.toString()+"_"+image.getOriginalFilename();

  try {
    String s3Path = uploadFolderPath+"/"+uploadImageName;
    s3service.uploadFile(image, s3Path);

    if(imageType.equals("mainImage")) {

      String thumbs3Path = uploadFolderPath+"/s_"+uploadImageName;
      s3service.uploadThumbFile(image, thumbs3Path);
    }

    // productImageVO create for DB
    imagelist.add(new ProductImageVO(uuid.toString(), uploadFolderPath.toString().replace("\\", "/"), image.getOriginalFilename(), imageType, null));

  }catch(Exception e){log.error(e.getMessage());}
}

 

주요 흐름을 보자면 다음과 같다.

1. 원본이미지를 S3에 저장

   -> 저장시, 이미지 파일 이름 중복 예방

   1) UUID를 랜덤생성

   2) 년/월/일 폴더를 만들어 저장

2. 메인이미지라면 섬네일을 만들어서 S3에 저장

3. DB에 이미지 정보를 저장(uuid나 파일 위치, 이름, 이미지 타입 등등,,)


@Log4j
@Service
@RequiredArgsConstructor
@Getter
public class S3Service {
  final private AmazonS3 s3Client;

  String bucketName = "버킷명";
  String accessKey = "액세스 키";
  String secretKey = "액세스 시크릿 키";

  public S3Service() {
    AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);

    this.s3Client = AmazonS3ClientBuilder.standard()
                        .withCredentials(new AWSStaticCredentialsProvider(credentials))
                        .withRegion(Regions.AP_NORTHEAST_2)
                        .build();
  }

  // upload original image file
  public void uploadFile(MultipartFile image, String s3Path) {
    try {
      // set ObjectMatadata
      byte[] bytes = IOUtils.toByteArray(image.getInputStream());

      ObjectMetadata objectMetadata = new ObjectMetadata();
      objectMetadata.setContentLength(bytes.length);
      objectMetadata.setContentType(image.getContentType());

      // save in S3
      ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
      this.s3Client.putObject(this.bucketName, s3Path.replace(File.separatorChar, '/'), byteArrayInputStream, objectMetadata);
	  
      byteArrayInputStream.close();
      
      }catch(Exception e){log.error(e.getMessage());}
    }

    // upload thumbnail image file
    public void uploadThumbFile(MultipartFile image, String thumbs3Path) {
      try {
        // make thumbnail image for s3
        BufferedImage bufferImage = ImageIO.read(image.getInputStream());
        BufferedImage thumbnailImage = Thumbnails.of(bufferImage).size(400, 333).asBufferedImage();

        ByteArrayOutputStream thumbOutput = new ByteArrayOutputStream();
        String imageType = image.getContentType();
        ImageIO.write(thumbnailImage, imageType.substring(imageType.indexOf("/")+1), thumbOutput);

        // set metadata
        ObjectMetadata thumbObjectMetadata = new ObjectMetadata();
        byte[] thumbBytes = thumbOutput.toByteArray();
        thumbObjectMetadata.setContentLength(thumbBytes.length);
        thumbObjectMetadata.setContentType(image.getContentType());

        // save in s3
        InputStream thumbInput = new ByteArrayInputStream(thumbBytes);
        this.s3Client.putObject(this.bucketName, thumbs3Path.replace(File.separatorChar, '/'), thumbInput, thumbObjectMetadata);
	
        thumbInput.close();
        thumbOutput.close();
    }catch(Exception e){log.error(e.getMessage());}
  }
}

객체 업로드에 대한 AWS 공식문서

 

- BasicAWSCredentials("액세스키", "액세스 시크릿키");

  : 이때 액세스키가 깃허브에 노출되지않도록 유의해야 한다.

  : accessKey와 secretKey를 이용하여 자격증명 객체를 얻는다.

 

- AmazonS3ClientBuilder.standard().withCredentials().build();

  : 얻은 자격증명 객체를 이용해서 AmazonS3ClientBuilder로 S3 Client를 가져온다.

    (AmazonS3Client가 deprecated됨에 따라, AmazonS3ClientBuilder를 사용했다.)

  : 한번 빈으로 등록하고 다음에는 의존성 주입을 통해 가져다가 쓰면된다. 그러면 이제부터 내가 만든 S3에 접속할 수 있다.

 

- objectMetadata.setContentLength(bytes.length);

  : metadata로 contentLength를 설정하지 않으면 S3에 업로드는 되나, 다음과 같은 에러로그가 나온다.

  : [io-8443-exec-65] c.amazonaws.services.s3.AmazonS3Client   : No content length specified for stream data.

 

- objectMetadata.setContentType(image.getContentType());

  : metadata contentType을 설정하지않으면, S3의 contentType에 jpg, png이런식으로만 올라가게된다.

  : jpg, png으로만 되면 url으로 접속시에 무조건 다운로드가 되게 된다. 하지만 contentType에 image/jpg로 명시하게되면 바로 다운로드가 아닌 이미지를 브라우저로 조회만도 가능하다.

 

- BufferedImage thumbnailImage = Thumbnails.of(bufferImage).size(400, 333).asBufferedImage();

  : 사실 AWS S3를 사용하게되면 주로 람다를 이용해서 섬네일 이미지를 만든다. 하지만 그렇게 되면 기존 코드와 차이가 많이 생기다보니 Thumbnailator를 계속 사용하기로 하였다.

  : 기존 코드에서처럼 Thumbnailator.createThumbnail(bufferImage, 400, 333);를 사용하려했지만, 이렇게 되면 BufferedImage를 한번 더 거치게되서인지 섬네일 이미지 색상에 문제가 생겼다. 그래서 thumbnailator.Thumbnails를 사용하기로 했다.

 


 

 

 

참고 출처 :

 

victorydntmd.tistory.com/334

 

[SpringBoot] AWS S3 연동 (1) - 파일 업로드 기본 (AmazonS3ClientBuilder)

Springboot S3 업로드를 구현하는 시리즈입니다. [SpringBoot] AWS S3 연동 (1) - 파일 업로드 기본 (AmazonS3ClientBuilder) [SpringBoot] AWS S3 연동 (2) - 파일 조작 및 Cloud Front 전체 소스 코드는 여..

victorydntmd.tistory.com

docs.aws.amazon.com/ko_kr/sdk-for-java/v1/developer-guide/examples-s3-objects.html

 

Amazon S3 객체에 대한 작업 수행 - Java용 AWS SDK

를 copyObjectdeleteObject객체 삭제와 함께 사용하여 객체를 이동하거나 객체의 이름을 바꿀 수 있습니다. 먼저 객체를 새 이름으로 복사한 다음(동일한 버킷을 원본과 대상으로 모두 사용할 수 있음)

docs.aws.amazon.com

okky.kr/article/367729

 

OKKY | thumbnailator로 이미지 리사이징하기

한달 조금 넘는 시간동안 개인 프로젝트를 진행해봤습니다  글의 웹을 개발하며  이미지 리사이징 처리를 고민해본 경험과 thumbnailtor 로 처리한 코드를 올려봅니다. 원글 -  https://github.com/gomonk

okky.kr

www.oops4u.com/2445

 

No content length specified for stream data

s3 업로드시 발생하는 로그. Sep 30 03:29:32 ip-172-50-10-72 tomcat8: 2019-09-30 12:29:32.717 WARN 27104 --- [io-8443-exec-65] c.amazonaws.services.s3.AmazonS3Client  : No content length specified fo..

www.oops4u.com

www.joinc.co.kr/w/Site/C/Documents/CprogramingForLinuxEnv/Ch4_VarAndOper

 

리눅스 환경에서의 C 프로그래밍 4장 변수와 연산자

2 !, -, ++, --, +(단항), -(단항), *(포인터), &, sizeof 우->좌

www.joinc.co.kr

윈도우 C:\\와 리눅스/home/차이