728x90
반응형
1. 서론
서비스를 개발하다 보면 이미지를 저장하고 제공해야 하는 상황이 자주 발생합니다. Relogging 프로젝트에서도 사용자 프로필 이미지, 플로깅 모임 배너 이미지, 뉴스 기사 썸네일 등 다양한 이미지 리소스를 다루어야 했습니다.
이미지 호스팅을 구현하는 방식은 크게 두 가지가 있습니다:
- 애플리케이션 서버에서 직접 이미지를 저장하고 제공하는 방식
- 구현이 비교적 간단하고 직관적
- 하지만 서버 리소스(디스크 공간, 네트워크 대역폭) 부담이 큼
- 확장성과 가용성 측면에서 한계가 있음
- 클라우드 스토리지 서비스(AWS S3 등)를 활용하는 방식
- 초기 설정과 구현이 다소 복잡할 수 있음
- 하지만 서버 부하를 줄일 수 있고 확장성이 뛰어남
- CDN과 연계하여 더 빠른 이미지 제공 가능
- 비용 효율적인 운영 가능
이 글에서는 Relogging 프로젝트에서 AWS S3를 활용하여 이미지 호스팅을 구현한 방법을 상세히 살펴보겠습니다. Spring Boot와 Kotlin을 기반으로 한 구현 방법과 실제 프로젝트에서 겪은 문제들, 그리고 해결 방법을 공유하고자 합니다.
이어서 AWS S3 설정부터 실제 구현까지 단계별로 알아보도록 하겠습니다.
2. 구현
2-1. AWS S3 버킷 생성
2024.11.08 - [백엔드] - [백엔드] AWS S3 버킷 생성
2-2. AWS S3 권한 엑세스 키 발급
2024.11.09 - [백엔드] - [백엔드] AWS S3 엑세스 키 발급 방법
2-3. yml 환경설정
아래의 설명을 참고해서 구현하는 프로젝트의 설정을 해주면 된다.
- bucket: S3 버킷 이름을 'relogging'으로 지정
- path: 각 도메인별 S3 내부 이미지 저장 경로 설정
- credentials: AWS 접근을 위한 인증 정보
- ${AWS_XXX_KEY} 이 부분에 아까 발급 받은 AWS 엑세스 공개키와 비밀키를 설정하면 된다.
Key 부분은 중요한 정보니까 환경 변수 또는 env 파일로 분리하자.
2024.10.20 - [백엔드] - [Java, Kotlin Spring] 인텔리제이 application.yml 환경변수 설정하기
2-4. 코드
Config
@Configuration
class AmazonConfig(
@Value("\${cloud.aws.credentials.accessKey}")
private val accessKey: String,
@Value("\${cloud.aws.credentials.secretKey}")
private val secretKey: String,
@Value("\${cloud.aws.region.static}")
private val region: String,
) {
@Bean
fun amazonS3Client(): AmazonS3Client {
val basicAWSCredentials = BasicAWSCredentials(accessKey, secretKey)
return AmazonS3ClientBuilder
.standard()
.withRegion(region)
.withCredentials(AWSStaticCredentialsProvider(basicAWSCredentials))
.build()
as AmazonS3Client
}
}
- @Configuration: Spring의 설정 클래스임을 나타냄
- AWS 접근에 필요한 인증 정보(accessKey, secretKey)와 리전을 프로퍼티에서 주입
- amazonS3Client 빈을 생성하여 AWS S3 서비스와 통신
Service
@Service
class AmazonS3Service(
private val amazonS3Client: AmazonS3Client,
@Value("\${cloud.aws.s3.bucket}")
private val bucket: String,
) {
fun uploadFile(
file: MultipartFile,
uploadDir: String,
): String {
val originalFileName: String? = file.originalFilename
if (!isImageFile(originalFileName as String)) {
throw IllegalArgumentException("png, jpeg, jpg에 해당하는 파일만 업로드할 수 있습니다.")
}
val objectMetadata = ObjectMetadata()
objectMetadata.contentLength = file.size
objectMetadata.contentType = file.contentType
val inputStream: InputStream = file.inputStream
val fileName = uploadDir + "/" + UUID.randomUUID().toString() + "_" + file.originalFilename
try {
amazonS3Client.putObject(bucket, fileName, inputStream, objectMetadata)
amazonS3Client.getUrl(bucket, originalFileName).toString()
} catch (e: IOException) {
throw GlobalException(GlobalErrorCode.BAD_REQUEST)
}
return amazonS3Client.getUrl(bucket, fileName).toString()
}
private fun isImageFile(fileName: String): Boolean {
val allowedExtensions = listOf("png", "jpg", "jpeg")
val extension = fileName.substringAfterLast(".", "")
return extension.lowercase() in allowedExtensions
}
}
- 파일 유효성 검사 (이미지 파일만 허용)
- 메타데이터 설정 (파일 크기, 컨텐츠 타입)
- UUID를 사용한 고유한 파일명 생성
- S3에 파일 업로드 후 URL 반환
728x90
반응형