본문 바로가기
Study/AWS

AWS(7)-S3 에 Spring Boot 프로젝트 업로드

by 왕방개 2024. 4. 18.

1.Spring Boot 프로젝트에서 파일 업로드 구현

1)Spring Boot Framework

=>Java를 이용해서 Application을 빠르고 쉽게 구현하기 위한 프레임워크

=>Java Application을 구현할 때는 특별한 경우가 아니면 Spring Framework 를 이용

=>이전에 Web Application의 경우는 Spring MVC Framework 를 많이 이용했는데 Micro Service 구현에서는 Spring Boot Frame Work를 이용

 

2)Spring Framework IDE

=>Eclipse 기반의 SpringToolSuite - SI 분야에서는 가끔 이용

=>Intelli J: 플랫폼 기업들에서 대부분 이용. Pycharm 과 개발 환경이 유사

웹 애플리케이션을 제작할 수 있는 버전은 유료

 

3)프로젝트 생성

=>Spring Web 과 lombok을 포함한 spring boot 프로젝트 생성

=>제공되지 않는 라이브러리를 추가: 프로젝트 수준의 build.gradle 파일의 dependicies 에 추가
implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.3.1'

=>오른쪽의 코끼리 모양의 아이콘을 클릭해서 reload를 수행

 

4)AWS S3 관련 설정을 src/main/resources 디렉토리의 application.properties 파일에 작성

# AWS Account Credentials (AWS 접근 키)
cloud.aws.credentials.accessKey=key값
cloud.aws.credentials.secretKey=key값

# AWS S3 bucket Info (S3 Bucket정보)
cloud.aws.s3.bucket=bucket이름
cloud.aws.region.static=ap-northeast-2
cloud.aws.stack.auto=false

# file upload max size (파일 업로드 크기 설정)
spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=20MB

 

5)업로드 하는 파일의 크기가 설정의 크기보다 클 때 동작하는 클래스를 생성

=>기본 패키지에 FileUploadFailedException 이라는 클래스로 생성

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.ErrorResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

@Component
@RestControllerAdvice
public class FileUploadFailedException{
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    protected ResponseEntity<ErrorResponse> handleMaxUploadSizeExceededException(
            MaxUploadSizeExceededException e) {

        ErrorResponse response = ErrorResponse.builder(e, HttpStatus.BAD_REQUEST, "용량 초과").build();
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }
}

 

=>IoC(Inversion of Control)


- 제어의 역전이나 역흐름
- 객체 지향 프로그래밍의 흐름은 클래스를 만들고 인스턴스를 만들어서 인스턴스를 이용해서 클래스에 정의 된 내용을 사용하는 것
- 클래스는 직접 만든 클래스 일 수 있고 프레임워크가 제공하는 클래스 일 수 도 있음
- 보통의 경우 프레임워크를 사용하면 프레임워크가 제공하는 클래스를 가지고 개발자가 인스턴스를 생성해서 사용하는 것이 일반적인 흐름인데 IoC가 적용된 프레임워크에서는 클래스를 개발자가 만들고 인스턴스를 프레임워크가 만들어서 수명 주기를 관리합니다.
이것 때문에 자바 개발자들은 인스턴스의 수명 주기를 직접 관리할 필요가 없고 스프링은 클래스를 싱글톤으로 만들어서 인스턴스를 1개만 사용할 수 있도록 해줍니다.
개발자는 싱글톤 패턴의 개념만 알면 되고 직접 작성할 필요가 없습니다.
- spring에서 IoC를 적용할려면 기본 설정을 변경하지 않는다면 기본 패키지에 있는 클래스 중에 @Component, @Controller, @Service, @Repository 등이 붙어야 합니다.

=>AoP(Aspect of Programming - 관점 지향 프로그래밍)
프로그래밍을 할 때 관점에 따라 분할해서 프로그래밍 해야 한다는 것
스프링에서는 어노테이션 이나 설정을 이용해서 이 작업을 수행해주고 이를 위한 클래스로 인터셉터 와 AoP를 제공

 

6)업로드할 파일 경로를 위한 클래스
=>파일 업로드를 구현할 때 동일한 파일 이름을 가진 경우가 발생할 수 있는데 s3는 파일 이름을 key 로 하고 파일을 value로 하는 key-value 시스템
s3에 파일을 저장할 때 파일 이름이 동일하면 업데이트가 됩니다.
=>파일 이름을 유일무이하게 만드는 방법
 - 파일 이름 과 확장자 사이에 코드를 추가하거나 기존 파일 이름을 무시하고 파일 이름을 생성
 추가하는 코드에는 id, 현재 시간, UUID 등이 있습니다.
 - 별도의 디렉토리를 생성하면서 디렉토리 단위로 구분하는 것
 s3에서는 디렉토리를 별도로 만들 필요없이 업로드 할 때 디렉토리명/파일명 의 형태로 만들면 됩니다.

 

=>기본 패키지에 CommonUtils 라는 클래스로 생성해서 작성

public class CommonUtils {
    private static final String FILE_EXTENSION_SEPARATOR = ".";
    private static final String CATEGORY_PREFIX = "/";
    private static final String TIME_SEPARATOR = "_";

    public static String buildFileName(String category, String originalFileName) {
        int fileExtensionIndex = originalFileName.lastIndexOf(FILE_EXTENSION_SEPARATOR);
        String fileExtension = originalFileName.substring(fileExtensionIndex);
        String fileName = originalFileName.substring(0, fileExtensionIndex);
        String now = String.valueOf(System.currentTimeMillis());

        return category + CATEGORY_PREFIX + fileName + TIME_SEPARATOR + now + fileExtension;
    }
}

 

7)사용자의 요청을 처리하는 Service 클래스를 생성

=>데이터베이스 작업 그리고 Common Concern(비지니스 로직이 필요없는 코드)을 제외한 비지니스 로직 과 관련된 내용을 작성하는 클래스를 Service 라고 하는데 Service 는 Controller 안에서 호출되고 데이터 작업이 있는 경우 Repository 클래스의 인스턴스를 이용합니다.

=>기본 패키지 안에 AwsS3Service 로 생성하고 작성

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;

@Slf4j
@RequiredArgsConstructor
@Service
public class AwsS3Service {
    private AmazonS3 amazonS3Client;

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

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

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

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

    @PostConstruct
    public void setS3Client() {
        AWSCredentials credentials = new BasicAWSCredentials(this.accessKey, this.secretKey);

        amazonS3Client = AmazonS3ClientBuilder.standard()
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .withRegion(this.region)
                .build();
    }
    
    public String uploadFileV1(String category, MultipartFile multipartFile) {
        boolean result = validateFileExists(multipartFile);
        if(result == false){
            return null;
        }
        String fileName = CommonUtils.buildFileName(category, multipartFile.getOriginalFilename());
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(multipartFile.getContentType());
        try (InputStream inputStream = multipartFile.getInputStream()) {
            amazonS3Client.putObject(new PutObjectRequest(bucketName, fileName, inputStream, objectMetadata)
                    .withCannedAcl(CannedAccessControlList.PublicRead));
        } catch (IOException e) {
            return null;
        }
        return amazonS3Client.getUrl(bucketName, fileName).toString();
    }
    
    private boolean validateFileExists(MultipartFile multipartFile) {
        boolean result = true;
        if (multipartFile.isEmpty()) {
            result = false;
        }
        return result;
    }
}


=>DI(Dependency Injection - 의존성 주입)
- 인스턴스 내부에서 사용하는 인스턴스를 직접 생성하지 않고 외부에서 생성해서 주입받아서 사용하는 패턴
- 실제 구현할 때는 생성자를 이용하거나 setter를 이용해서 주입을 받습니다.
- 내부에서 직접 만들면 여러 곳에서 사용하는 인스턴스 인 경우 관리가 어려워집니다.

 

8)Controller 클래스

=>Controller
- 사용자의 요청을 받아서 필요한 비지니스 로직(Service)을 호출하고 그 결과를 사용자에게 전송하는 클래스
- 뷰를 전달하기도 하고 데이터를 전달하기도 합니다.

=>기본 패키지에 S3FileController 클래스로 생성하고 작성

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequiredArgsConstructor
public class S3FileController {

    private final AwsS3Service awsS3Service;
    @PostMapping("/upload")
    public String uploadFile(
            @RequestParam("category") String category,
            @RequestPart(value = "file") MultipartFile multipartFile) {
        return awsS3Service.uploadFileV1(category, multipartFile);
    }
}

 

9)실행
=>main 메서드를 가진 클래스를 선택하고 마우스 오른쪽을 눌러서 [Run Application] 메뉴를 클릭
=>포트 설정을 하지 않으면 8080 번 포트로 실행
포트를 변경하고자 하면 application.properties 파일에 server.port=포트번호 를 설정하면 됩니다.


10)테스트
=>API Server 이므로 브라우저에서 테스트하기 어려움
=>GET 방식의 경우는 브라우저에 입력하면 되지만 그 이외의 방식은 브라우저에 URL 창에 직접 입력이 안됩니다.
=>POSTMAN 등을 이용해서 테스트


2.Client 에서 파일 업로드

=>서버에서 파일을 업로드하는 방식은 클라이언트가 파일을 서버에게 전송하고 서버가 다시 S3에 파일을 전송해서 저장하는 방식

=>클라이언트(웹 브라우저 도는 모바일 앱)에서 파일을 업로드하고 파일명을 서버로 전송해서 관리하는 방법을 사용할 수 있는데 이렇게 되면 파일에 대한 트래픽은 한 번만 발생해서 앞의 방식보다 트래픽은 줄일 수 있는데 클라이언트 애플리케이션에서 작업을 수행하게 되면 코드가 클라이언트에게 노출될 수 있다는 점을 주의해야 합니다.
이런 경우 웹에서는 코드 난독화 등을 적절히 이용해야 합니다.
iOS의 경우는 이런 문제가 발생하지 않음
iOS는 역 어셈블이 거의 불가능
Android는 역 어셈블이 가능하기 때문에 이런 부분에 주의

 

 

'Study > AWS' 카테고리의 다른 글

AWS Container Service(ECR,ECS)  (0) 2024.05.03
Server Application 배포-EC2 활용  (0) 2024.05.02
AWS(6)-S3에 파일 upload(Django, Spring Boot, react)  (2) 2024.04.17
AWS(5) - DataBase  (0) 2024.04.16
AWS(4)  (0) 2024.04.15