[Lambda] Lambda와 S3를 사용한 이미지 리사이징 (3)- Spring 코드 구현

  • Spring Boot 애플리케이션에서 AWS SDK를 활용하여 S3에 이미지를 업로드하는 메서드와, CloudFront를 통해 각 S3 버킷의 섬네일 파일에 접근할 수 있는 URL을 생성하는 메서드를 생성하였다.
  • 생성된 URL은 소설의 상세 정보를 전송할 때 포함되어 클라이언트가 해당 URL을 통해 이미지를 다운로드하고 렌더링할 수 있도록 구성하였다.

1. 프로젝트 설정

build.gradle

  • AWS SDK를 사용하기 위해 의존성을 설정한다.
//AWS S3 SDK, versionCheck 2024-08-19
 implementation 'software.amazon.awssdk:s3:2.27.7'
//AWS Cloud Front SDK, versionCheck 2024-08-19
implementation 'software.amazon.awssdk:cloudfront:2.27.7'

application.properties

  • S3와 CloudFront 설정을 application.properties 파일에 추가한다,
  • 업로드 용량은 10MB로 제한하였다.
# AWS S3 config
aws.s3.accessKey=<KEY>
aws.s3.secretKey=<SECRETKEY>
aws.s3.thumbnail.bucket=<BUCKETNAME>

# AWS Cloud Front config
aws.cloudfront.thumbnail.domain=<URL>.cloudfront.net
aws.cloudfront.mini-thumbnail.domain=<URL>.cloudfront.net

# Upload file size setting
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

2. AWS S3 연결 설정

  • AWS S3와 상호작용할 수 있는 S3Client 객체를 Spring Bean으로 등록한다.
  • AWS accessKeysecretKey 는 위에서 설정한 값으로 propertiy binding 하여 값을 가져와 등록하였다.
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 AWSConfig {
    @Value("${aws.s3.accessKey}")
    private String accessKey;

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

    //AWS S3 접속 클라이언트 객체 이미지 업로드시 사용
    @Bean
    public S3Client s3Client() {
        return S3Client.builder()
                .region(Region.AP_NORTHEAST_2)//지역설정(한국 서울)
                .credentialsProvider(StaticCredentialsProvider
                        .create(AwsBasicCredentials.create(accessKey, secretKey)))//key설정
                .build();

    }

3. S3 관련 Service 계층 구현

1) 업로드 메서드

  • S3 버킷에 파일 업로드시, 파일명이 중복될 수 있으므로, 현재시간과 파일명 조합으로 고유한 파일명을 갖도록 구현하였다.
  • PutObjectRequest로 S3 버킷에 이미지 파일을 바이트 형태로 업로드하고, 성공 시 파일 이름을 반환(DB에 저장하기 위함) 실패하면 예외를 던지도록 하였다.
   @Override
    public String uploadFileToS3(MultipartFile file) {
        //현재시간과 파일이름으 조합으로 고유한 파일명 생성
        String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
        try {
            //S3에 Put 요청을 하기위한 Request 객체 생성
            PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                    .bucket(THUMBNAIL_BUKET_NAME)//S3에서 사용될 버킷 이름
                    .key(FOLDER_NAME + "/" + fileName)//버킷 안에 폴더 이름과 파일 명으로 엔드포인트 설정
                    .build();

            //업로드할 파일을 바이트로 변환하여 S3 버킷에 저장후, 반환된 응답 객체에 저장
            PutObjectResponse response = s3Client.putObject(putObjectRequest,
                    RequestBody.fromBytes(file.getBytes()));

            //업로드 결과 출력, 업로드 실패시 예외로 던져짐
            log.info("S3 파일 업로드 결과 ={}", response.toString());

            return fileName;
        } catch (Exception ex) {
            throw new ServiceMethodException("uploadFileToS3 메서드 에러, S3 파일 업로드 실패" + ex + ex.getMessage());
        }
    }

2) CloudFront URL 생성 메서드

  • 소설의 상세 정보를 보낼때 섬네일 생성용 메서드이다.
  • 이미지 종류(원본/축소)에 따라 CloudFront URL을 생성하여 클라이언트에서 이미지에 접근할 수 있도록 하였다.
@Override
 public String generateCloudFrontUrl(String fileName, String thumbnailType) {
     try {
         //작은 썸네일 요청 URL 생성(랭킹 등 이미지가 작아도 되는경우)
         if (thumbnailType.equals("mini")) {
             return String.format("https://%s/%s/%s", MINI_THUMBNAIL_DOMAIN_NAME, FOLDER_NAME, "mini-"+fileName); //생성된 URL 반환
         }
         //원본 썸네일 요청 URL, Novel 상세페이지 등에 이용
         return String.format("https://%s/%s/%s", THUMBNAIL_DOMAIN_NAME, FOLDER_NAME, fileName); //생성된 URL 반환

         //cloudfront 도메인으로 S3 이미지 접근, URL은 https://{cloudfront도메인이름}/{폴더이름}/{파일이름}
     } catch (Exception ex) {
         throw new ServiceMethodException("generateCloudFrontUrl 메서드 에러 cloudfront URL 생성실패" + ex + ex.getMessage());
        }
 }

전체코드

import com.ham.netnovel.common.exception.ServiceMethodException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.sync.RequestBody;

import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;

@Service
@Slf4j
public class S3ServiceImpl implements S3Service {

    private final S3Client s3Client;
    private final String FOLDER_NAME = "thumbnail";//S3 버킷에서 접근할 폴더 이름

    @Value("${aws.s3.thumbnail.bucket}")
    private String THUMBNAIL_BUKET_NAME;//S3 버킷이름
    @Value("${aws.cloudfront.thumbnail.domain}")
    private String THUMBNAIL_DOMAIN_NAME;//원본 사이즈 섬네일cloud front 가상 도메인 이름
    @Value("${aws.cloudfront.mini-thumbnail.domain}")
    private String MINI_THUMBNAIL_DOMAIN_NAME;//작은 사이즈 섬네일 cloud front 가상 도메인 이름

    public S3ServiceImpl(S3Client s3Client) {
        this.s3Client = s3Client;
    }
    @Override
    public String uploadFileToS3(MultipartFile file) {
        //현재시간과 파일이름으 조합으로 고유한 파일명 생성
        String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
        try {
            //S3에 Put 요청을 하기위한 Request 객체 생성
            PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                    .bucket(THUMBNAIL_BUKET_NAME)//S3에서 사용될 버킷 이름
                    .key(FOLDER_NAME + "/" + fileName)//버킷 안에 폴더 이름과 파일 명으로 엔드포인트 설정
                    .build();

            //업로드할 파일을 바이트로 변환하여 S3 버킷에 저장후, 반환된 응답 객체에 저장
            PutObjectResponse response = s3Client.putObject(putObjectRequest,
                    RequestBody.fromBytes(file.getBytes()));

            //업로드 결과 출력, 업로드 실패시 예외로 던져짐
            log.info("S3 파일 업로드 결과 ={}", response.toString());

            return fileName;
        } catch (Exception ex) {
            throw new ServiceMethodException("uploadFileToS3 메서드 에러, S3 파일 업로드 실패" + ex + ex.getMessage());
        }
    }

    @Override
    public String generateCloudFrontUrl(String fileName, String thumbnailType) {
        try {
            //작은 썸네일 요청 URL 생성(랭킹 등 이미지가 작아도 되는경우)
            if (thumbnailType.equals("mini")) {
                return String.format("https://%s/%s/%s", MINI_THUMBNAIL_DOMAIN_NAME, FOLDER_NAME, "mini-"+fileName); //생성된 URL 반환
            }
            //원본 썸네일 요청 URL, Novel 상세페이지 등에 이용
            return String.format("https://%s/%s/%s", THUMBNAIL_DOMAIN_NAME, FOLDER_NAME, fileName); //생성된 URL 반환

            //cloudfront 도메인으로 S3 이미지 접근, URL은 https://{cloudfront도메인이름}/{폴더이름}/{파일이름}
        } catch (Exception ex) {
            throw new ServiceMethodException("generateCloudFrontUrl 메서드 에러 cloudfront URL 생성실패" + ex + ex.getMessage());
        }
    }
}