본문 바로가기
서버 공부

[스프링] S3를 이용한 이미지 업로드

by 모선효 2024. 6. 13.

 

사진 업로드를 할 수 있는 외주 작업을 맡게 되어 S3를 이용한 이미지 업로드 로직을 작성했습니다.

더보기

요구사항

 

- 게시물에 이미지는 필수값이 아니다.

- 하나의 게시물에 올릴 수 있는 파일 수는 최대 3개이다.

 

위 요구사항에 맞춰 먼저, S3를 사용하기 위한 코드를 작성해보도록 하겠습니다.

 

1. AWS S3에 bucket을 생성

 

2. bucket, accessKey, secretKey 정보를 application.yml에 추가

cloud:
  aws:
    s3:
      bucket: 버킷이름
    stack.auto: false
    region.static: ap-northeast-2
    credentials:
      accessKey: 본인 키 입력
      secretKey: 본인 키 입력

 

3. S3Config 파일 작성

package com.example.pet.file;

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 S3Config {
    @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();
    }
}

 

4. S3Service 파일에 파일 업로드 로직 작성

단일 파일 업로드

@Service
@RequiredArgsConstructor
@Slf4j
public class S3Service {

    private final AmazonS3Client amazonS3Client;

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

    @Value("${cloud.aws.region.static}")
    private String region;
	
    // 단일 파일 업로드 로직
    public String uploadFile(MultipartFile file) throws IOException {
        try {
            String URL = "https://" + bucket + ".s3." + region + ".amazonaws.com/";
            String folderDir = bucket;
            String savedName = createSaveName(file.getOriginalFilename());
            String fileUrl = URL + savedName;

            ObjectMetadata metaData = new ObjectMetadata();
            metaData.setContentType(file.getContentType());
            metaData.setContentLength(file.getSize());

            amazonS3Client.putObject(folderDir, savedName, file.getInputStream(), metaData);
            return fileUrl;
        } catch (IOException e) {
            throw new BusinessException(FILE_UPLOAD_FAILED);
        }
    }
    
    // UUID를 사용하여 unique한 파일명 만들기
    private String createSaveName(String originalName) {
        String ext = extractExt(originalName);
        String uuid = UUID.randomUUID().toString();
        return uuid + "." + ext;
    }
    
    // 파일 확장명 추출
    private String extractExt(String originalName) {
        int pos = originalName.lastIndexOf(".");
        return originalName.substring(pos + 1);
    }
}

 

하나의 파일을 업로드 하는 로직입니다.

저는 파일 확장명을 UUID로 만들어진 파일명 뒤에 붙여 파일명을 만들어줬고, 버킷 폴더 안에 파일을 저장하도록 했습니다.

 

여러 파일 업로드

public FileDto uploadFiles(List<MultipartFile> files) {
	
    // 파일 개수 검증 (의뢰자의 요구사항)
    validateFileSize(files);
    
    // 이미지를 올리지 않았을 경우 빈 리스트 return
    if (files == null) {
        return FileDto.toDto(new ArrayList<>());
    }
    
    // 여러 파일 업로드
    List<String> urls = files.stream().map(file -> {
    	try {
            return uploadFile(file);
        } catch (IOException e) {
            throw new BusinessException(FILE_UPLOAD_FAILED);
        }
    }).collect(Collectors.toList());
    
    return FileDto.toDto(urls);
}

private void validateFileSize(List<MultipartFile> files) {
    if (files != null && files.size() > 3) {
        throw new BusinessException(TOO_MANY_FILES);
    }
}

 

파일이 3장을 초과하는지 먼저 검증하였으며, 이미지를 올리지 않았을 경우 빈 리스트를 반환했습니다.

그리고 map을 사용하여 List 에 들어있는 파일들을 uploadFile에 적용하였습니다.

더보기

☝️ MultipartFile

 

여기서 사용한 MultipartFile은 여러 파일을 쉽게 처리할 수 있게 해주는 것입니다. 파일 내용은 메모리나 디스크에 일시적으로 저장되며, 요청이 끝나면 지워집니다. 해당 파일은 DB나 세션으로 저장할 수 있습니다.

 

5. Controller 작성

이미지를 포함하여 게시물을 작성하는 방법에는 2가지 방법이 있습니다.

 

1) 이미지 업로드 API와 글쓰기 API 따로 생성하기

2024.06.13 - [서버 공부] - [스프링] 글 작성 API : 이미지 업로드 API와 글쓰기 API 따로 생성하기

 

[스프링] 글 작성 API : 이미지 업로드 API와 글쓰기 API 따로 생성하기

보통 한 페이지에 하나의 API를 요청하는 게 좋지만, 그럴 경우 form-data로 List과 JSON 데이터를 한 번에 받아야 합니다.  플러터 같은 경우, form-data에 두 가지 타입의 데이터를 받는 게 어렵다고 하

hy5sun.tistory.com

 

2) 글쓰기 API 하나로 진행하기\

2024.06.25 - [서버 공부] - [스프링] 이미지와 함께 게시물 작성하기

 

[스프링] 이미지와 함께 게시물 작성하기

이번에는 이미지 업로드와 json 타입의 Request Dto 데이터 값을 동시에 받아 글을 작성하는 API 코드를 소개하겠습니다. S3를 사용하여 이미지를 업로드하는 코드는 이전 게시물에 올려뒀습니다! 먼

hy5sun.tistory.com

 

글이 길어질 것 같아, 위 2가지 방법은 따로 글을 올리겠습니다!

 

글 읽어주셔서 감사합니다.

아직 부족한 부분이 많으니, 피드백 주시면 정말 감사하겠습니다!