Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ client/captures
.cxx
local.properties
client/app/release/
client/app/google-services.json

HELP.md
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
server/.env

### STS ###
.apt_generated
Expand Down
7 changes: 6 additions & 1 deletion server/.envsample
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
DB_URL=
DB_NAME=
DB_USERNAME=
DB_PASSWORD=
DB_PASSWORD=

NAVER_S3_ACCESS_KEY=
NAVER_S3_SECRET_KEY=
NAVER_S3_BUCKET=
4 changes: 4 additions & 0 deletions server/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ dependencies {
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

// Naver, Storage
implementation("software.amazon.awssdk:s3:2.25.60")
implementation("software.amazon.awssdk:auth:2.25.60")
}

kotlin {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.andone.memorip.common.config

import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.S3Configuration
import java.net.URI

@Configuration
class StorageConfig {

@Bean
fun s3Client(
@Value("\${naver.s3.access-key}") accessKey: String,
@Value("\${naver.s3.secret-key}") secretKey: String,
@Value("\${naver.s3.endpoint}") endpoint: String,
@Value("\${naver.s3.region}") region: String
): S3Client {
val credentials = AwsBasicCredentials.create(accessKey, secretKey)

return S3Client.builder()
.endpointOverride(URI.create(endpoint))
.region(Region.of(region))
.credentialsProvider(StaticCredentialsProvider.create(credentials))
.serviceConfiguration(
S3Configuration.builder()
.pathStyleAccessEnabled(true)
.build()
)
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.andone.memorip.infra.storage

import com.andone.memorip.common.response.ApiResult
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestPart
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile

@RestController
@RequestMapping("/api/s3")
class StorageController(
private val service: StorageService
) {

@GetMapping("/health")
fun health(): String {
service.check()
return "OK"
}

@PostMapping("/upload/place")
fun uploadPlaceImage(
@RequestPart file: MultipartFile
): ApiResult<String> {
return ApiResult.success(
data = service.upload(file, dir = "place")
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.andone.memorip.infra.storage

import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import software.amazon.awssdk.services.s3.S3Client
import org.springframework.web.multipart.MultipartFile
import software.amazon.awssdk.core.sync.RequestBody
import software.amazon.awssdk.services.s3.model.PutObjectRequest
import java.util.UUID

@Service
class StorageService(
private val s3Client: S3Client,
@Value("\${naver.s3.bucket}") private val bucket: String
) {
fun check() {
s3Client.headBucket { it.bucket(bucket) }
}

fun upload(
file: MultipartFile,
dir: String
): String {

require(value = !file.isEmpty) { "빈 파일입니다" }

val extension = file.originalFilename
?.substringAfterLast('.', "")
?.lowercase()
?: throw IllegalArgumentException("파일 확장자가 없습니다")

val key = "$dir/${UUID.randomUUID()}.$extension"

s3Client.putObject(
PutObjectRequest.builder()
.bucket(bucket)
.key(key)
.contentType(file.contentType)
.contentLength(file.size)
.build(),
RequestBody.fromInputStream(file.inputStream, file.size)
)

return s3Client.utilities()
.getUrl { it.bucket(bucket).key(key) }
.toExternalForm()
}
}
12 changes: 12 additions & 0 deletions server/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,15 @@ spring:
# Jackson (JSON null 필드 제외)
jackson:
default-property-inclusion: non_null



# Naver/Object Storage
naver:
s3:
access-key: ${NAVER_S3_ACCESS_KEY}
secret-key: ${NAVER_S3_SECRET_KEY}
bucket: ${NAVER_S3_BUCKET}
endpoint: https://kr.object.ncloudstorage.com
region: kr-standard