[Spring/Kotlin] Amazon S3 를 사용해보자

트러블 슛돌이·2023년 7월 27일

서론

게시글에 이미지를 업로드 할 수 있는 기능이 필요했다
그래서 이미지를 저장할 수 있는 데이터베이스와 업로드한 이미지를 게시물에서 보여주기 위한 URL을 가져올 수 있어야했다

이를 구현하기 위해 두 가지 후보가 있었는데 첫 번째가 일반 서버를 이용하는 것이고 두 번째가 Amazon에서 서비스 하는 S3 버킷을 이용하는 것이었다
우리는 S3를 사용하기로 결정했다

AWS를 제대로 써본적이 없기 때문에 힘들 수 있겠다고 생각했지만 여러 블로그들을 참고하여 생각보다 수월하게 진행되었다

오늘 배운것

S3란?

S3(Simple Storage Service)는 AWS에서 제공하는 인터넷 스토리지 서비스이다

S3 사용의 장점

  1. 데이터를 안전하게 저장할 수 있다
  2. 비용이 저렴하다
  3. 보안성이 뛰어나다
  4. 속도가 빠르다

S3를 Spring Boot에서 사용하기

S3 버킷 생성 후 기본 설정을 해주면 이제 스프링 프로젝트를 수정해줘야한다

build.gradle에 의존성 추가

implementation("io.awspring.cloud:spring-cloud-starter-aws:2.3.1")

application-s3.properties 작성

Resource 패키지 우클릭 -> New -> Resource Bundle 클릭 후
이름을 application-s3로 하여 생성해준다

# AWS Account Credentials
cloud.aws.credentials.accessKey=
cloud.aws.credentials.secretKey=

# AWS S3 bucket Info
cloud.aws.s3.bucket=
cloud.aws.region.static=ap-northeast-2
cloud.aws.stack.auto=

그리고 위의 양식대로 파일을 수정해준다

S3config 작성

@Configuration
class S3Config {
    @Value("\${cloud.aws.credentials.accessKey}")
    private lateinit var accesskey: String

    @Value("\${cloud.aws.credentials.secretKey}")
    private lateinit var secretKey: String

    @Value("\${cloud.aws.region.static}")
    private lateinit var region:String

    @Bean
    fun amazonS3Client(): AmazonS3Client {
        val basicAWSCredentials: BasicAWSCredentials = BasicAWSCredentials(accesskey,secretKey)
        return AmazonS3ClientBuilder
            .standard()
            .withRegion(region)
            .withCredentials(AWSStaticCredentialsProvider(basicAWSCredentials))
            .build()
        as AmazonS3Client
    }


}

application-s3/properties에 작성한 AccessKey 등의 값들을 읽어와서 AmazonS3Client 객체를 생성할 때 의존성을 주입 해준다

서비스 작성

@Service
class AwsS3Service(
    private val amazonS3Client: AmazonS3Client,
    private val uploadUtils: UploadUtils) {

    @Value("\${cloud.aws.s3.bucket}")
    private lateinit var bucket:String

    //파일을 aws 보내고 url 받기
    fun uploadFiles(files:List<MultipartFile>): List<String> {
        var imageUrls = ArrayList<String>()
        for (file in files) {
            val originalFileName: String? = file.originalFilename

            if(uploadUtils.isNotImageFile(originalFileName as String))
                throw IllegalArgumentException("png, jpeg, jpg에 해당하는 파일만 업로드할 수 있습니다.");

            val objectMetadata = ObjectMetadata()
            objectMetadata.setContentLength(file.getSize())
            objectMetadata.setContentType(file.getContentType())

            try {
                val inputStream:InputStream = file.inputStream
                amazonS3Client.putObject(bucket,originalFileName, inputStream,objectMetadata);
                val uploadFileUrl = amazonS3Client.getUrl(bucket, originalFileName).toString();
                imageUrls.add(uploadFileUrl)
            }catch (e: IOException) {
                e.printStackTrace();
            }
        }
        return imageUrls
    }
    
    //URL을 DB에 저장 후 DTO로 반환하기
    @Transactional
    fun storeUrls(userId:Int, imageUrls: List<String>, files: List<MultipartFile>): ArrayList<UploadLogDTO> {
        val dtoList = ArrayList<UploadLogDTO>()

        for ((index, file) in files.withIndex()) {

            if(uploadUtils.isNotImageFile(file.originalFilename as String))
                throw IllegalArgumentException("png, jpeg, jpg에 해당하는 파일만 업로드할 수 있습니다.");

            val storeUrlDto = UploadLogDTO(
                user_id = userId,
                file_size = file.size.toInt(),
                upload_date = LocalDateTime.now(),
                url = imageUrls[index]
            )
            dtoList.add(storeUrlDto)
            /* TODO Mapper 이용해 db 저장 하기 */
        }

        return dtoList
    }

}

1개의 댓글

comment-user-thumbnail
2024년 10월 15일

uploadUtils 파일에 대한 내용은 어디에 있을까요 ?

답글 달기