본문 바로가기
관리자

Project/Poppin

S3 이미지 업로드 구현, Profile 설정 및 불러오기, Swagger 이미지 업로드(multipart)

728x90
반응형

1. S3 이미지 업로드 구현

 

설정 파일 추가

@Configuration
open class AwsConfig {
    @Value("\${cloud.aws.credentials.accessKey}")
    private val accessKey: String? = null

    @Value("\${cloud.aws.credentials.secretKey}")
    private val secretKey: String? = null

    @Value("\${cloud.aws.region.static}")
    private val region: String? = null
    @Bean
    open fun amazonS3(): AmazonS3 {
        val awsCredentials: AWSCredentials = BasicAWSCredentials(accessKey, secretKey)
        return AmazonS3ClientBuilder.standard()
            .withRegion(region)
            .withCredentials(AWSStaticCredentialsProvider(awsCredentials))
            .build()
    }
}

 

 

S3 Service 구현

이미지 업로드 구현

  • S3 BucketEnum에 activeProfile 값을 넘겨주어서 어떤 환경(local | prod)에서 업로드 하느냐에 따라 S3 bucket 이름을 구분해주었다.
  • 파일명은 DB의 name 컬럼에 따로 저장하고 uuid.{extension}으로 저장한다. 불필요하게 URL Encoding된 이름이 S3의 key값으로 잡히는 것 + 중복 방지를 위함이다.
@Service
class S3Service(
    private val s3Client: AmazonS3
) {

    @Value("\${spring.config.activate.on-profile}")
    val activeProfile: String = ""
    @Throws(IOException::class)
    fun directUpload(uploadPath: String, file: MultipartFile): String {
        val bucketName = S3BucketEnum.getValue(activeProfile)
        val originalFilename = file.originalFilename ?: ""
        val fileExtension = originalFilename.substringAfterLast(".", "")
        val fileName = "$uploadPath/${UUID.randomUUID()}.$fileExtension"
        val objMeta = ObjectMetadata()

        val bytes = IOUtils.toByteArray(file.inputStream)
        objMeta.contentLength = bytes.size.toLong()

        val byteArrayIs = ByteArrayInputStream(bytes)

        s3Client.putObject(
            PutObjectRequest(bucketName, fileName, byteArrayIs, objMeta)
            .withCannedAcl(CannedAccessControlList.PublicRead))

        return s3Client.getUrl(bucketName, fileName).toString()
    }

}

 

BucketEnum

환경(Profile)에 따라 다른 S3 bucketName이 반환되도록 만들었다.

enum class S3BucketEnum(private val testValue: String, private val prodValue: String) {
    NAME("test", "prod");

    companion object {
        fun getValue(activeProfile: String): String {
            return when {
                activeProfile.contains("local") || activeProfile.contains("dev") -> NAME.testValue
                activeProfile.contains("prod") -> NAME.prodValue
                else -> throw IllegalStateException("Unexpected profile")
            }
        }
    }

}

 

 

application.yml 설정, 보안 처리

aws 연결을 위해서 아래와 같이 설정했다.

관리자(루트) 계정의 access_key 및 secret_key는 AWS 콘솔 우상단의 계정 이름 클릭 -> 보안 자격 증명 -> 액세스 키 쪽에서 만들고 다운로드 받을 수 있다. 해당 파일을 ~/.ssh에 보관했다.

cloud:
  aws:
    credentials:
      accessKey: ${CLOUD_ACCESS_KEY}
      secretKey: ${CLOUD_SECRET_KEY}
    s3:
      bucket: ${CLOUD_S3_PROD_BUCKET_NAME}
    region:
      static: {지역 이름}
      stack:
        auto: false

 

S3 버킷 정책 추가

S3 관련한 접근이 가능하도록 정책을 추가했다. 필요한 Action만 추가해야겠지만, 일단 모두(*) 추가해주었다.

{
    "Version": "2012-10-17",
    "Id": "{정책 편집기에서 만들어짐}",
    "Statement": [
        {
            "Sid": "{정책 편집기에서 만들어짐}",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::{버킷 이름}/*"
        }
    ]
}

 

 

2. Profile 설정 및 불러오기

 

설정하기

스프링부트 2.4.x 이상부터 아래와 같은 방식으로 작성한다. # Common에 적힌 방식은 어떤 group의 profile들을 사용할 것인지 정의하는 내용이다. group 이름이 local인데, 여기에 local 외에 다른 on-profile 값도 추가해주면 2가지에서 설정한 값들이 모두 적용된다.

 

spring.config.activate.on-profile식으로 작성하면된다.

---은 중요한 구분자이고, 이를 통해 각 환경을 구분한다.

# Common
spring:
  profiles:
    group:
      local: local
      prod: prod
      
---
# Local
spring:
  config:
    activate:
      on-profile: local
      

---
spring:
  config:
    activate:
      on-profile: prod

 

 

불러오기

실행 시(intellij)

Active profiles에 on-profile에 작성한 내용을 적으면 된다. 프로그램 실행 초기에 어떤 profile이 활성화 되었는지 알려준다.

 

 

불러올때

상기 기술한 것처럼 @Value로 불러온다. 이 값을 인자로 써서 profile 별로 다르게 처리해야할 분기문들을 처리하면 된다.

@Value("\${spring.config.activate.on-profile}")
lateinit var activeProfile: String

 

 

추가: Swagger multipart 이미지 업로드 설정

아래 코드처럼 controller를 작성했다.

consumes에 MULTIPART_FORM 임을 알린다.

@ModelAttribute로 Multipart를 받는다.

@PostMapping("/some/{some_id}/direct-upload", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
fun directUploadImage(
    @PathVariable("some_id") someId: Long,
    @ModelAttribute @Valid imageRequest: ImageRequest
): ApiResponse<ImageResponse> {
... 중략
}

 

class ImageRequest (
    val file: MultipartFile,
    val description: String?
){
}

 

file을 선택할 수 있다.

 

 

추가: s3 업로드 multipart 이미지 용량 servlet

multipart의 기본 업로드 용량 값이 1MB 정도로 설정되어있다. application.yml에서 servlet.multipart.maxFileSize, servlet.multipart.maxRequestSize를 조정해주면 된다.

spring:
  ... 중략
  servlet:
    multipart:
      maxFileSize: 10MB
      maxRequestSize: 10MB

 

728x90
반응형