[Project] S3 버킷에 저장한 이미지 불러오기

정동아·2023년 9월 7일

Celebee

목록 보기
4/12
post-thumbnail

main 프로젝트에서 프로필과 카드 (모임 글) 이미지를 유저가 직접 올리는게 아니라, 웹에서 제공하는 이미지중에서 선택하는 방식으로 진행하기로했다.
넷플릭스의 프로필 이미지 변경이 가장 대표적인 예시가 될 것 같다.

이거에대해 일단 이미지를 어떻게 불러오지..? 이미지는 db에 저장해야하나..? 이전에 회사다닐땐 어떻게 처리하셨었지...? 진짜 이런 오만가지 생각을하면서 관련 내용을 열심히 구글링했다.

역시 아마존..!
S3를 이용해서 서버에 이미지를 올리고 내리고 할 수 있다는 것을 찾았고, 이렇게 클라우드를 이용하는게 가장 많이 사용하는 방법이라는 것을 알았다.
서비스를 만들때 얼마나 많은 이미지가 올라갈지 모르기때문에 클라우드 서비스를 사용하는데, 그중가장 많이 사용하는 클라우드가 S3인 것같아서 선택했다.
(사용법도 이미 알고있기도 했던게 컸다!)

내가 구현할 이미지 처리 방식은 유저가 직접 올리는게 아니기때문에, 버킷을 하나 만들어주고 거기에 이미지를 구분해서 넣어줬다.

이렇게 이미지 URL을 얻을 수 있다.


해야하는 작업이
1) 프로필 이미지

  • 회원 가입 시 기본으로 프로필 설정
  • 마이페이지-내 정보 수정 시 프로필 관련 이미지를 모두 보내준다.
    유저가 이미지를 변경하면 변경된걸 저장해서 이후 멤버 정보를 호출할 때 변경된 이미지 URL을 전달한다.
  • 모임 주최자, 참석자 이미지를 관련 호출 시 함께 보내준다.

2) 보드 이미지 (모임 글)

  • 모임 글 작성 시 유저가 카테고리를 선택하면 -> 선택한 카테고리에 해당하는 이미지를 보여준다.
    글 작성 시 선택한 이미지를 저장해서 이후 보드 호출 시 같이 전달한다.

이런 작업을 해줘야했다.
유저가 직접 이미지를 올리고 그걸 저장하는 것에대한 블로그글은 많았는데, 이미 있는 이미지를 불러와서 프론트에 전달하고 또 특정 디렉토리에 있는 이미지 파일을 모두 전달하는 글은 찾기 어려웠다.

그래서 처음에는 아래와같이 작성했다.

applocation.yml

cloud:
  aws:
    credentials:
      accessKey: ${AWS_ACCESS_KEY}
      secretKey: ${AWS_SECRET_ACCESS_KEY}
    s3:
      bucket: celebeeimage
    region:
      static: ap-northeast-2
    stack:
      auto: 'false'
  • IAM 계정에 액세스키를 발급받아서 환경변수로 넣어줘야한다.

AwsS3Config

package com.party.image.config;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AwsS3Config {
    //accessKey, secretKey, region을 받아와서 AmazonS3 객체를 Bean으로 등록

    @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 AmazonS3Client amazonS3Client() {
        BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);

        return (AmazonS3Client) AmazonS3ClientBuilder.standard()
                .withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
                .build();
    }
}

AwsService

@Slf4j
@Service
@RequiredArgsConstructor
public class AwsService {
    private final AmazonS3Client amazonS3Client;
    @Value("${cloud.aws.s3.bucket}")
    private String bucketName;

    public String getThumbnailPath(String path) {
        return amazonS3Client.getUrl(bucketName, path).toString();
    }

    //보드 이미지 모두 조회
    public List<String> getImagesAll (String directory ,String category){
        List<String> list = new ArrayList<>();

        for (int i = 1; i<4; i++){
            String str = String.valueOf(i)+ ".png";
            System.out.println(str);
            list.add(getThumbnailPath(directory + category + str));
            
                    return list;
    }

    //프로필 이미지 모두 조회
    public List<String> getProfileImageAll (String directory){
        List<String> list = new ArrayList<>();

        for (int i = 1; i<4; i++){
            String str = String.valueOf(i)+ ".png";
            System.out.println(str);
            list.add(getThumbnailPath(directory + str));
        }

        return list;

    }
}
  • 프론트에 빨리 API를 만들어서 줘야해서 일단 생각나는대로, 각 카테고리 별 사진이 3개 있으니까... 3개 가져와버려.. 이런생각으로 했다.
  • 보드와 프로필 이미지를 가져오는걸 따로해야하는 문제가 있다.
  • 디렉토리 내 파일 수가 3개보다 적거나 많으면 엑박 또는 불러오지 못하는 문제가 있다. -> 변경에 용이하지 않다.

ImageController

@RequiredArgsConstructor
@RestController
@RequestMapping
public class ImageController {
    private final AwsService awsService;
    private final MemberService memberService;

    //테스트 - URL 제대로 불러오는지 테스트하려고 작성함. 
    @GetMapping
    public String getTestImage(){
        String path = "board/Category_Culture";
        String imagePath = awsService.getThumbnailPath("1.png");
        Member member = new Member();
        member.setImageUrl(imagePath);
        System.out.println(imagePath);
        return imagePath;
    }

    //board 이미지 전달
    @GetMapping("/cards/{category}/images")
    public List<String> getBoardImages(@PathVariable("category") String category){

       List<String> boardImageList = awsService.getImagesAll("board/", category);

       return boardImageList;
    }

    //profile 이미지 전달
    @GetMapping("members/images")
    public List<String> getProfileImages (){
        List<String> profileImageList = awsService.getProfileImageAll("profile/");
        return profileImageList;
    }
}
  • 프론트에서 카테고리가 뭔지 알려주면 그걸로 조회해서 넘겨준다.
  • 프로필 이미지 요청이 오면 조회해서 넘겨준다.

회원가입 시 기본 프로필로 설정하는건, memberService 클래스에서 멤버 생성하는 로직에 1번 이미지로 추가해줬다.

일단 여기서 제일 문제가

  • 이미지를 조회하는 로직이 두번있다.
  • 디렉토리 내 파일 수에 변경이 생기면 대응을 못하고, 코드를 직접 바꿔줘야한다.

이런 문제가 있어서 또 열심히 구글링을했다.
분명..분명!!쉽게 조회하고 불러오는 기능이 있을텐데 하면서 이럴땐 공식 문서지 하면서 공식 깃헙을 봤다.
aws-doc-sdk-examples

여기서

//snippet-sourcedescription:[ListObjects.java demonstrates how to list objects within an Amazon S3 bucket.]
//snippet-keyword:[Java]
//snippet-sourcesyntax:[java]
//snippet-keyword:[Code Sample]
//snippet-keyword:[Amazon S3]
//snippet-keyword:[listObjectsV2]
//snippet-service:[s3]
//snippet-sourcetype:[full-example]
//snippet-sourcedate:[]
//snippet-sourceauthor:[soo-aws]
/*
   Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.

   This file is licensed under the Apache License, Version 2.0 (the "License").
   You may not use this file except in compliance with the License. A copy of
   the License is located at

    http://aws.amazon.com/apache2.0/

   This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied. See the License for the
   specific language governing permissions and limitations under the License.
*/
package aws.example.s3;

import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.ListObjectsV2Result;
import com.amazonaws.services.s3.model.S3ObjectSummary;

import java.util.List;

/**
 * List objects within an Amazon S3 bucket.
 * 
 * This code expects that you have AWS credentials set up per:
 * http://docs.aws.amazon.com/java-sdk/latest/developer-guide/setup-credentials.html
 */
public class ListObjects {
    public static void main(String[] args) {
        final String USAGE = "\n" +
                "To run this example, supply the name of a bucket to list!\n" +
                "\n" +
                "Ex: ListObjects <bucket-name>\n";

        if (args.length < 1) {
            System.out.println(USAGE);
            System.exit(1);
        }

        String bucket_name = args[0];

        System.out.format("Objects in S3 bucket %s:\n", bucket_name);
        final AmazonS3 s3 = AmazonS3ClientBuilder.standard().withRegion(Regions.DEFAULT_REGION).build();
        ListObjectsV2Result result = s3.listObjectsV2(bucket_name);
        List<S3ObjectSummary> objects = result.getObjectSummaries();
        for (S3ObjectSummary os : objects) {
            System.out.println("* " + os.getKey());
        }
    }
}

이걸 참고해서 디렉토리를 조회해서 for문을 통해 안에 있는걸 다 리스트에 넣는 방법을 알아냈다!

변경된 AwsService

@Slf4j
@Service
@RequiredArgsConstructor
public class AwsService {

    private final AmazonS3Client amazonS3Client;

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

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

    public String getThumbnailPath(String path) {
        return amazonS3Client.getUrl(bucketName, path).toString();
    }


    //S3폴더 내 파일 리스트 전달
    public List<String> getFileList(String directory){
        List<String> fileList = new ArrayList<>();

        ListObjectsV2Request listObjectsV2Request = new ListObjectsV2Request()
                .withBucketName(bucketName)
                .withPrefix(directory+"/");  //폴더 경로 지정

        ListObjectsV2Result result = amazonS3Client.listObjectsV2(listObjectsV2Request);
        List<S3ObjectSummary> objectSummaries = result.getObjectSummaries();

        for (S3ObjectSummary objectSummary : objectSummaries) {
            String key = objectSummary.getKey();
            if (!key.equals(directory + "/")) {
                fileList.add("https://"+bucketName+".s3."+region+".amazonaws.com/" + key);
            }
        }

        return fileList;
    }
}

공식 예제를 참고해서

      ListObjectsV2Result result = amazonS3Client.listObjectsV2(listObjectsRequest);
        List<S3ObjectSummary> objectSummaries = result.getObjectSummaries();

        for (S3ObjectSummary objectSummary : objectSummaries) {
            fileList.add(objectSummary.getKey());
        }

        return fileList;
    }

이렇게 리스트에 넣어줬는데 응답이

[
    "board/",
    "board/1.png",
    "board/2.png",
    "board/3.png",
    "board/4.png",
 
]

이런식으로 와서, 그냥 리스트에 넣은걸 바로 클라에 전달하려고 (이미지 URL을 바로 전달하려고)

        ListObjectsV2Result result = amazonS3Client.listObjectsV2(listObjectsV2Request);
        List<S3ObjectSummary> objectSummaries = result.getObjectSummaries();

        for (S3ObjectSummary objectSummary : objectSummaries) {
            String key = objectSummary.getKey();
            if (!key.equals(directory + "/")) {  // "board/", 안나오게하기위해
                fileList.add("https://"+bucketName+".s3."+region+".amazonaws.com/" + key);
            } //URL 바로 내보내주려고
        }

        return fileList;
    }
}

이렇게 수정했고, 이거에 맞춰서
변경된 ImageController

@RequiredArgsConstructor
@RestController
@RequestMapping
public class ImageController {

    private final AwsService awsService;
    private final MemberService memberService;

    //board 이미지 전달
    @GetMapping("/cards/{category}/images")
    public List<String> getBoardImages(@PathVariable("category") String category){

       List<String> boardImageList = awsService.getFileList("board/"+category); //보드는 카테고리 디렉토리가 하나 더있어서 

       return boardImageList;
    }

    //profile 이미지 전달
    @GetMapping("members/images")
    public List<String> getProfileImages (){
        List<String> profileImageList = awsService.getFileList("profile");
        return profileImageList;
    }
}

이렇게 comtroller도 수정해줬다.

이렇게 원하는대로 이미지 URL이 리스트로 프론트에 전달되었고, 디렉토리내 파일을 모두 불러오는거라 1개든 4개든 10개든 따로 코드를 수정해줄 필요없이 대응 가능해졌다.

이미지 호출이 그렇게 많을거같진 않을 것으로 예상하지만..! 그래도 한번 해보고싶어서 cloudfront로 적용해보려한다. 추가로, 나중에 유저가 직접 이미지를 올리고 그 이미지를 저장해서 URL을 받아오는 작업도 해보고싶다.

0개의 댓글