Cloud/AWS

[Spring boot] AWS S3 를 이용한 파일 업로드 - 2 (S3-부트 연결)

Sean-creative 2023. 10. 2. 17:44

2023.09.26 - [분류 전체보기] - [Spring boot] AWS S3 를 이용한 파일 업로드 - 1 (S3 생성 + 설정)

 

[Spring boot] AWS S3 를 이용한 파일 업로드 - 1 (S3 생성 + 설정)

프로젝트에서 파일을 로컬로 저장해서 사용하고 있었는데 치명적인 문제점이 있었다. 만약 사진을 업로드 하게 되면 파일이 제대로 뜨지 않았다. 사진은 정적파일이기 때문이라는 문제도 있었

sean-lets-go.tistory.com

저번 글에서는 S3를 사용하게된 이유와 S3 생성 +설정을 해보았습니다.

요번 글에서는 S3와 스프링 부트(프로젝트)와 연결해보려고 합니다. 

 

 

스프링 연동하기

1)  build.gradle에 의존성 추가

//S3 연동
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

2)  application.properties 에 설정 정보 추가

cloud.aws.credentials.accessKey=<발급받은 accessKey>
cloud.aws.credentials.secretKey=<발급받은 secretKey>
cloud.aws.s3.bucket=<버킷이름>
cloud.aws.region.static=ap-northeast-2
cloud.aws.stack.auto=false

spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

accessKey 와 같은 보안 설정 정보는 해킹 위험이 있으므로 별도의 설정 파일로 분리 후 git ignore 해주어야 합니다.

설정 파일에 있는 정보는 스프링이 지원하는 @Value 어노테이션을 사용하여 불러올 수 있습니다.

 

cloud.aws.stack.auto=false

EC2에서 Spring Cloud 프로젝트를 실행시키면 기본으로 CloudFormation 구성을 시작하기 때문에 설정한 CloudFormation이 없으면 프로젝트 실행이 되지 않습니다. 해당 기능을 사용하지 않도록 false로 설정.

 

cloud.aws.region.static:ap-northeast-2

지역을 한국으로 고정

 

multipart

기본적으로 default의 값은 1MB 이다. 사진을 업로드할 때에 최대 10MB의 원본을 가지고 있으므로 10MB로 설정

3)  스프링 설정 추가

S3Config.java

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

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

    @Value("${cloud.aws.region.static}")
    private String region;
    @Bean
    public AmazonS3 amazonS3Client(){
        AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);

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

application.properties에서 설정한 값을 Value로 주입해서 사용합니다,

 

4)  AmazonS3Controller 생성

@RestController
@RequiredArgsConstructor
@RequestMapping("/s3")
public class AmazonS3Controller {

    private final AwsS3Service awsS3Service;

    /**
     * Amazon S3에 파일 업로드
     * @return 성공 시 200 Success와 함께 업로드 된 파일의 파일명 리스트 반환
     */
    @ApiOperation(value = "Amazon S3에 파일 업로드", notes = "Amazon S3에 파일 업로드 ")
    @PostMapping("/file")
    public ResponseEntity<List<String>> uploadFile(@ApiParam(value="파일들(여러 파일 업로드 가능)", required = true) @RequestPart List<MultipartFile> multipartFile) {
        return ApiResponse.success(awsS3Service.uploadImage(multipartFile));
    }

    /**
     * Amazon S3에 업로드 된 파일을 삭제
     * @return 성공 시 200 Success
     */
    @ApiOperation(value = "Amazon S3에 업로드 된 파일을 삭제", notes = "Amazon S3에 업로드된 파일 삭제")
    @DeleteMapping("/file")
    public ResponseEntity<Void> deleteFile(@ApiParam(value="파일 하나 삭제", required = true) @RequestParam String fileName) {
        awsS3Service.deleteImage(fileName);
        return ApiResponse.success(null);
    }
}

 

5)  AmazonS3Controller 생성

@Service
@RequiredArgsConstructor
public class AwsS3Service {

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

    private final AmazonS3 amazonS3;

    public List<String> uploadFile(List<MultipartFile> multipartFile) {
        List<String> fileNameList = new ArrayList<>();

        // forEach 구문을 통해 multipartFile로 넘어온 파일들 하나씩 fileNameList에 추가
        multipartFile.forEach(file -> {
            String fileName = createFileName(file.getOriginalFilename());
            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.setContentLength(file.getSize());
            objectMetadata.setContentType(file.getContentType());

            try(InputStream inputStream = file.getInputStream()) {
                amazonS3.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata)
                        .withCannedAcl(CannedAccessControlList.PublicRead));
            } catch(IOException e) {
                throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드에 실패했습니다.");
            }

            fileNameList.add(fileName);
        });

        return fileNameList;
    }

    public void deleteFile(String fileName) {
        amazonS3.deleteObject(new DeleteObjectRequest(bucket, fileName));
    }

    private String createFileName(String fileName) { // 먼저 파일 업로드 시, 파일명을 난수화하기 위해 random으로 돌립니다.
        return UUID.randomUUID().toString().concat(getFileExtension(fileName));
    }

    private String getFileExtension(String fileName) { // file 형식이 잘못된 경우를 확인하기 위해 만들어진 로직이며, 파일 타입과 상관없이 업로드할 수 있게 하기 위해 .의 존재 유무만 판단하였습니다.
        try {
            return fileName.substring(fileName.lastIndexOf("."));
        } catch (StringIndexOutOfBoundsException e) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "잘못된 형식의 파일(" + fileName + ") 입니다.");
        }
    }
}

 

6) View에서 Test  

 

프로젝트에서 S3를 구현한 부분입니다. 

 

AWS S3 콘솔에서 파일이 들어온 것을 확인할 수 있습니다.

S3를 사용하지 않고 로컬저장을 했을 때는  application이 재실행 되어야 새로운 사진이 등장했는데,

지금은 S3를 이용하여 사진이 바로 등장하는것을 볼 수 있습니다!

 

view 파일에 대해서 궁금한게 있으시면 댓글주세요!!