Skip to content

Commit f835dae

Browse files
committed
feat: 파일 스토리지 AWS S3에서 minio로 마이그레이션
1 parent 4eb9080 commit f835dae

10 files changed

Lines changed: 132 additions & 138 deletions

File tree

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ dependencies {
4747

4848
implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
4949

50-
implementation("io.awspring.cloud:spring-cloud-starter-aws:2.4.4")
50+
implementation("io.minio:minio:8.5.7")
5151
implementation("javax.xml.bind:jaxb-api:2.3.1")
5252
implementation("org.apache.tika:tika-core:2.9.2")
5353
implementation("org.apache.tika:tika-parsers-standard-package:2.9.2")

src/main/kotlin/site/billilge/api/backend/domain/display/facade/DisplayPosterFacade.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import site.billilge.api.backend.domain.display.dto.response.DisplayPosterFindAl
88
import site.billilge.api.backend.domain.display.service.DisplayPosterService
99
import site.billilge.api.backend.global.exception.ApiException
1010
import site.billilge.api.backend.global.exception.GlobalErrorCode
11-
import site.billilge.api.backend.global.external.s3.S3Service
11+
import site.billilge.api.backend.global.external.FileStorageService
1212
import java.util.*
1313

1414
@Component
1515
class DisplayPosterFacade(
1616
private val displayPosterService: DisplayPosterService,
17-
private val s3Service: S3Service,
17+
private val fileStorageService: FileStorageService,
1818
) {
1919
fun getAllPosters(): DisplayPosterFindAllResponse {
2020
val posters = displayPosterService.getAllPosters()
@@ -24,14 +24,14 @@ class DisplayPosterFacade(
2424
}
2525

2626
fun addPoster(image: MultipartFile, request: DisplayPosterRequest) {
27-
val imageUrl = s3Service.uploadImageFile(image, "posters/${UUID.randomUUID()}")
27+
val imageUrl = fileStorageService.uploadImageFile(image, "posters/${UUID.randomUUID()}")
2828
?: throw ApiException(GlobalErrorCode.IMAGE_UPLOAD_FAILED)
2929
displayPosterService.addPoster(imageUrl, request.title)
3030
}
3131

3232
fun updatePoster(posterId: Long, image: MultipartFile?, request: DisplayPosterRequest) {
3333
val imageUrl = if (image != null && !image.isEmpty) {
34-
s3Service.uploadImageFile(image, "posters/${UUID.randomUUID()}")
34+
fileStorageService.uploadImageFile(image, "posters/${UUID.randomUUID()}")
3535
?: throw ApiException(GlobalErrorCode.IMAGE_UPLOAD_FAILED)
3636
} else {
3737
null

src/main/kotlin/site/billilge/api/backend/domain/item/service/ItemService.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ import site.billilge.api.backend.global.dto.PageableCondition
1515
import site.billilge.api.backend.global.dto.SearchCondition
1616
import site.billilge.api.backend.global.exception.ApiException
1717
import site.billilge.api.backend.global.exception.GlobalErrorCode
18-
import site.billilge.api.backend.global.external.s3.S3Service
18+
import site.billilge.api.backend.global.external.FileStorageService
1919

2020
@Service
2121
@Transactional(readOnly = true)
2222
class ItemService(
2323
private val itemRepository: ItemRepository,
24-
private val s3Service: S3Service,
24+
private val fileStorageService: FileStorageService,
2525
) {
2626
fun getAllItems(): List<Item> {
2727
return itemRepository.findAll()
@@ -46,7 +46,7 @@ class ItemService(
4646

4747
checkImageIsSvg(image)
4848

49-
val imageUrl = s3Service.uploadImageFile(image)
49+
val imageUrl = fileStorageService.uploadImageFile(image)
5050
?: throw ApiException(GlobalErrorCode.IMAGE_UPLOAD_FAILED)
5151

5252
val newItem = Item(
@@ -70,7 +70,7 @@ class ItemService(
7070
imageUrl = item.imageUrl
7171
} else {
7272
checkImageIsSvg(image)
73-
imageUrl = s3Service.uploadImageFile(image)
73+
imageUrl = fileStorageService.uploadImageFile(image)
7474
?: throw ApiException(GlobalErrorCode.IMAGE_UPLOAD_FAILED)
7575
}
7676

@@ -99,7 +99,7 @@ class ItemService(
9999
}
100100

101101
if (isEntityDeleted) {
102-
s3Service.deleteImageFile(imageUrl)
102+
fileStorageService.deleteImageFile(imageUrl)
103103
return true
104104
}
105105

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package site.billilge.api.backend.global.external
2+
3+
import org.springframework.web.multipart.MultipartFile
4+
import java.util.UUID
5+
6+
interface FileStorageService {
7+
fun uploadImageFile(imageFile: MultipartFile, newFileName: String = "items/${UUID.randomUUID()}"): String?
8+
9+
fun deleteImageFile(fileName: String)
10+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package site.billilge.api.backend.global.external.minio
2+
3+
import io.minio.MinioClient
4+
import org.springframework.beans.factory.annotation.Value
5+
import org.springframework.context.annotation.Bean
6+
import org.springframework.context.annotation.Configuration
7+
8+
@Configuration
9+
class MinioConfig(
10+
@Value("\${minio.endpoint}")
11+
private val endpoint: String,
12+
13+
@Value("\${minio.access-key}")
14+
private val accessKey: String,
15+
16+
@Value("\${minio.secret-key}")
17+
private val secretKey: String,
18+
) {
19+
20+
@Bean
21+
fun minioClient(): MinioClient {
22+
return MinioClient.builder()
23+
.endpoint(endpoint)
24+
.credentials(accessKey, secretKey)
25+
.build()
26+
}
27+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package site.billilge.api.backend.global.external.minio
2+
3+
import io.minio.MinioClient
4+
import io.minio.PutObjectArgs
5+
import io.minio.RemoveObjectArgs
6+
import io.minio.StatObjectArgs
7+
import io.minio.errors.ErrorResponseException
8+
import org.springframework.beans.factory.annotation.Value
9+
import org.springframework.stereotype.Service
10+
import org.springframework.web.multipart.MultipartFile
11+
import site.billilge.api.backend.global.exception.ApiException
12+
import site.billilge.api.backend.global.exception.GlobalErrorCode
13+
import site.billilge.api.backend.global.external.FileStorageService
14+
import java.io.IOException
15+
import java.util.*
16+
17+
@Service
18+
class MinioService(
19+
private val minioClient: MinioClient,
20+
@Value("\${minio.bucket}")
21+
private val bucket: String,
22+
@Value("\${minio.base-url}")
23+
private val baseUrl: String,
24+
) : FileStorageService {
25+
26+
override fun uploadImageFile(imageFile: MultipartFile, newFileName: String): String? {
27+
val originalName = imageFile.originalFilename ?: return null
28+
29+
val ext = originalName.substring(originalName.lastIndexOf("."))
30+
val changedName = newFileName + ext
31+
32+
try {
33+
minioClient.putObject(
34+
PutObjectArgs.builder()
35+
.bucket(bucket)
36+
.`object`(changedName)
37+
.stream(imageFile.inputStream, imageFile.size, -1)
38+
.contentType(imageFile.contentType)
39+
.build()
40+
)
41+
} catch (e: IOException) {
42+
throw ApiException(GlobalErrorCode.IMAGE_UPLOAD_FAILED, e)
43+
}
44+
45+
return "${baseUrl}/${bucket}/${changedName}"
46+
}
47+
48+
override fun deleteImageFile(fileName: String) {
49+
val imageKey = fileName.replace("${baseUrl}/${bucket}/", "")
50+
51+
try {
52+
minioClient.statObject(
53+
StatObjectArgs.builder()
54+
.bucket(bucket)
55+
.`object`(imageKey)
56+
.build()
57+
)
58+
} catch (e: ErrorResponseException) {
59+
throw ApiException(GlobalErrorCode.IMAGE_NOT_FOUND)
60+
}
61+
62+
try {
63+
minioClient.removeObject(
64+
RemoveObjectArgs.builder()
65+
.bucket(bucket)
66+
.`object`(imageKey)
67+
.build()
68+
)
69+
} catch (e: IOException) {
70+
throw ApiException(GlobalErrorCode.IMAGE_DELETE_FAILED, e)
71+
}
72+
}
73+
}

src/main/kotlin/site/billilge/api/backend/global/external/s3/S3Config.kt

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/main/kotlin/site/billilge/api/backend/global/external/s3/S3Service.kt

Lines changed: 0 additions & 61 deletions
This file was deleted.

src/main/resources/application-dev.yml

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,28 +42,17 @@ jwt:
4242
issuer: ${JWT_ISSUER}
4343

4444
login:
45-
admin-password: ${LOGIN_ADMIN_PASSWORD}
4645
redirect-url: ${LOGIN_REDIRECT_URL}
4746

4847
cors:
4948
allowed-origins: ${CORS_ALLOWED_ORIGINS}
5049

51-
exam-period:
52-
start-date: ${EXAM_PERIOD_START_DATE}
53-
end-date: ${EXAM_PERIOD_END_DATE}
54-
55-
cloud:
56-
aws:
57-
s3:
58-
bucket: ${S3_BUCKET}
59-
base-url: ${S3_BASE_URL}
60-
credentials:
61-
access-key: ${S3_ACCESS_KEY}
62-
secret-key: ${S3_SECRET_KEY}
63-
region:
64-
static: ap-northeast-2
65-
stack:
66-
auto: false
50+
minio:
51+
endpoint: ${MINIO_ENDPOINT}
52+
access-key: ${MINIO_ACCESS_KEY}
53+
secret-key: ${MINIO_SECRET_KEY}
54+
bucket: ${MINIO_BUCKET}
55+
base-url: ${MINIO_BASE_URL}
6756

6857
swagger:
6958
server:

src/main/resources/application-prod.yml

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,28 +41,17 @@ jwt:
4141
issuer: ${JWT_ISSUER}
4242

4343
login:
44-
admin-password: ${LOGIN_ADMIN_PASSWORD}
4544
redirect-url: ${LOGIN_REDIRECT_URL}
4645

4746
cors:
4847
allowed-origins: ${CORS_ALLOWED_ORIGINS}
4948

50-
exam-period:
51-
start-date: ${EXAM_PERIOD_START_DATE}
52-
end-date: ${EXAM_PERIOD_END_DATE}
53-
54-
cloud:
55-
aws:
56-
s3:
57-
bucket: ${S3_BUCKET}
58-
base-url: ${S3_BASE_URL}
59-
credentials:
60-
access-key: ${S3_ACCESS_KEY}
61-
secret-key: ${S3_SECRET_KEY}
62-
region:
63-
static: ap-northeast-2
64-
stack:
65-
auto: false
49+
minio:
50+
endpoint: ${MINIO_ENDPOINT}
51+
access-key: ${MINIO_ACCESS_KEY}
52+
secret-key: ${MINIO_SECRET_KEY}
53+
bucket: ${MINIO_BUCKET}
54+
base-url: ${MINIO_BASE_URL}
6655

6756
swagger:
6857
server:

0 commit comments

Comments
 (0)