feat: S3以外にもメディアを保存できるように

This commit is contained in:
usbharu 2023-12-06 14:17:19 +09:00
parent 4943b8e94e
commit a8da0f5366
4 changed files with 84 additions and 21 deletions

View File

@ -10,7 +10,7 @@ import java.net.URI
@Configuration @Configuration
class AwsConfig { class AwsConfig {
@Bean @Bean
fun s3Client(awsConfig: StorageConfig): S3Client { fun s3Client(awsConfig: S3StorageConfig): S3Client {
return S3Client.builder() return S3Client.builder()
.endpointOverride(URI.create(awsConfig.endpoint)) .endpointOverride(URI.create(awsConfig.endpoint))
.region(Region.of(awsConfig.region)) .region(Region.of(awsConfig.region))

View File

@ -14,7 +14,7 @@ class SpringConfig {
lateinit var config: ApplicationConfig lateinit var config: ApplicationConfig
@Autowired @Autowired
lateinit var storageConfig: StorageConfig lateinit var s3StorageConfig: S3StorageConfig
@Bean @Bean
fun requestLoggingFilter(): CommonsRequestLoggingFilter { fun requestLoggingFilter(): CommonsRequestLoggingFilter {
@ -33,9 +33,8 @@ data class ApplicationConfig(
val url: URL val url: URL
) )
@ConfigurationProperties("hideout.storage") @ConfigurationProperties("hideout.storage.s3")
data class StorageConfig( data class S3StorageConfig(
val useS3: Boolean,
val endpoint: String, val endpoint: String,
val publicUrl: String, val publicUrl: String,
val bucket: String, val bucket: String,
@ -44,6 +43,12 @@ data class StorageConfig(
val secretKey: String val secretKey: String
) )
@ConfigurationProperties("hideout.storage.local")
data class LocalStorageConfig(
val path: String,
val publicUrl: String?
)
@ConfigurationProperties("hideout.character-limit") @ConfigurationProperties("hideout.character-limit")
data class CharacterLimit( data class CharacterLimit(
val general: General = General(), val general: General = General(),

View File

@ -0,0 +1,58 @@
package dev.usbharu.hideout.core.service.media
import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.application.config.LocalStorageConfig
import org.springframework.stereotype.Service
import java.nio.file.Path
import kotlin.io.path.copyTo
import kotlin.io.path.deleteIfExists
import kotlin.io.path.outputStream
@Service
class LocalFileSystemMediaDataStore(
applicationConfig: ApplicationConfig,
localStorageConfig: LocalStorageConfig
) : MediaDataStore {
private val savePath: Path = Path.of(localStorageConfig.path)
private val publicUrl = localStorageConfig.publicUrl ?: "${applicationConfig.url}/files/"
override suspend fun save(dataMediaSave: MediaSave): SavedMedia {
val fileSavePath = buildSavePath(savePath, dataMediaSave.name)
val thumbnailSavePath = buildSavePath(savePath, "thumbnail-" + dataMediaSave.name)
dataMediaSave.thumbnailInputStream?.inputStream()?.buffered()
?.transferTo(thumbnailSavePath.outputStream().buffered())
dataMediaSave.fileInputStream.inputStream().buffered().transferTo(fileSavePath.outputStream().buffered())
return SuccessSavedMedia(
dataMediaSave.name,
publicUrl + dataMediaSave.name,
publicUrl + "thumbnail-" + dataMediaSave.name
)
}
override suspend fun save(dataSaveRequest: MediaSaveRequest): SavedMedia {
val fileSavePath = buildSavePath(savePath, dataSaveRequest.name)
val thumbnailSavePath = buildSavePath(savePath, "thumbnail-" + dataSaveRequest.name)
dataSaveRequest.filePath.copyTo(fileSavePath)
dataSaveRequest.thumbnailPath?.copyTo(thumbnailSavePath)
return SuccessSavedMedia(
dataSaveRequest.name,
publicUrl + dataSaveRequest.name,
publicUrl + "thumbnail-" + dataSaveRequest.name
)
}
override suspend fun delete(id: String) {
buildSavePath(savePath, id).deleteIfExists()
buildSavePath(savePath, "thumbnail-$id").deleteIfExists()
}
private fun buildSavePath(savePath: Path, name: String): Path = savePath.resolve(name)
}

View File

@ -1,6 +1,6 @@
package dev.usbharu.hideout.core.service.media package dev.usbharu.hideout.core.service.media
import dev.usbharu.hideout.application.config.StorageConfig import dev.usbharu.hideout.application.config.S3StorageConfig
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
@ -14,16 +14,16 @@ import software.amazon.awssdk.services.s3.model.GetUrlRequest
import software.amazon.awssdk.services.s3.model.PutObjectRequest import software.amazon.awssdk.services.s3.model.PutObjectRequest
@Service @Service
class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig: StorageConfig) : MediaDataStore { class S3MediaDataStore(private val s3Client: S3Client, private val s3StorageConfig: S3StorageConfig) : MediaDataStore {
override suspend fun save(dataMediaSave: MediaSave): SavedMedia { override suspend fun save(dataMediaSave: MediaSave): SavedMedia {
val fileUploadRequest = PutObjectRequest.builder() val fileUploadRequest = PutObjectRequest.builder()
.bucket(storageConfig.bucket) .bucket(s3StorageConfig.bucket)
.key(dataMediaSave.name) .key(dataMediaSave.name)
.build() .build()
val thumbnailKey = "thumbnail-${dataMediaSave.name}" val thumbnailKey = "thumbnail-${dataMediaSave.name}"
val thumbnailUploadRequest = PutObjectRequest.builder() val thumbnailUploadRequest = PutObjectRequest.builder()
.bucket(storageConfig.bucket) .bucket(s3StorageConfig.bucket)
.key(thumbnailKey) .key(thumbnailKey)
.build() .build()
@ -36,7 +36,7 @@ class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig
RequestBody.fromBytes(dataMediaSave.thumbnailInputStream) RequestBody.fromBytes(dataMediaSave.thumbnailInputStream)
) )
s3Client.utilities() s3Client.utilities()
.getUrl(GetUrlRequest.builder().bucket(storageConfig.bucket).key(thumbnailKey).build()) .getUrl(GetUrlRequest.builder().bucket(s3StorageConfig.bucket).key(thumbnailKey).build())
} else { } else {
null null
} }
@ -44,14 +44,14 @@ class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig
async { async {
s3Client.putObject(fileUploadRequest, RequestBody.fromBytes(dataMediaSave.fileInputStream)) s3Client.putObject(fileUploadRequest, RequestBody.fromBytes(dataMediaSave.fileInputStream))
s3Client.utilities() s3Client.utilities()
.getUrl(GetUrlRequest.builder().bucket(storageConfig.bucket).key(dataMediaSave.name).build()) .getUrl(GetUrlRequest.builder().bucket(s3StorageConfig.bucket).key(dataMediaSave.name).build())
} }
) )
} }
return SuccessSavedMedia( return SuccessSavedMedia(
name = dataMediaSave.name, name = dataMediaSave.name,
url = "${storageConfig.publicUrl}/${storageConfig.bucket}/${dataMediaSave.name}", url = "${s3StorageConfig.publicUrl}/${s3StorageConfig.bucket}/${dataMediaSave.name}",
thumbnailUrl = "${storageConfig.publicUrl}/${storageConfig.bucket}/$thumbnailKey" thumbnailUrl = "${s3StorageConfig.publicUrl}/${s3StorageConfig.bucket}/$thumbnailKey"
) )
} }
@ -59,19 +59,19 @@ class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig
logger.info("MEDIA upload. {}", dataSaveRequest.name) logger.info("MEDIA upload. {}", dataSaveRequest.name)
val fileUploadRequest = PutObjectRequest.builder() val fileUploadRequest = PutObjectRequest.builder()
.bucket(storageConfig.bucket) .bucket(s3StorageConfig.bucket)
.key(dataSaveRequest.name) .key(dataSaveRequest.name)
.build() .build()
logger.info("MEDIA upload. bucket: {} key: {}", storageConfig.bucket, dataSaveRequest.name) logger.info("MEDIA upload. bucket: {} key: {}", s3StorageConfig.bucket, dataSaveRequest.name)
val thumbnailKey = "thumbnail-${dataSaveRequest.name}" val thumbnailKey = "thumbnail-${dataSaveRequest.name}"
val thumbnailUploadRequest = PutObjectRequest.builder() val thumbnailUploadRequest = PutObjectRequest.builder()
.bucket(storageConfig.bucket) .bucket(s3StorageConfig.bucket)
.key(thumbnailKey) .key(thumbnailKey)
.build() .build()
logger.info("MEDIA upload. bucket: {} key: {}", storageConfig.bucket, thumbnailKey) logger.info("MEDIA upload. bucket: {} key: {}", s3StorageConfig.bucket, thumbnailKey)
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
awaitAll( awaitAll(
@ -92,8 +92,8 @@ class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig
} }
val successSavedMedia = SuccessSavedMedia( val successSavedMedia = SuccessSavedMedia(
name = dataSaveRequest.name, name = dataSaveRequest.name,
url = "${storageConfig.publicUrl}/${storageConfig.bucket}/${dataSaveRequest.name}", url = "${s3StorageConfig.publicUrl}/${s3StorageConfig.bucket}/${dataSaveRequest.name}",
thumbnailUrl = "${storageConfig.publicUrl}/${storageConfig.bucket}/$thumbnailKey" thumbnailUrl = "${s3StorageConfig.publicUrl}/${s3StorageConfig.bucket}/$thumbnailKey"
) )
logger.info("SUCCESS Media upload. {}", dataSaveRequest.name) logger.info("SUCCESS Media upload. {}", dataSaveRequest.name)
@ -108,9 +108,9 @@ class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig
} }
override suspend fun delete(id: String) { override suspend fun delete(id: String) {
val fileDeleteRequest = DeleteObjectRequest.builder().bucket(storageConfig.bucket).key(id).build() val fileDeleteRequest = DeleteObjectRequest.builder().bucket(s3StorageConfig.bucket).key(id).build()
val thumbnailDeleteRequest = val thumbnailDeleteRequest =
DeleteObjectRequest.builder().bucket(storageConfig.bucket).key("thumbnail-$id").build() DeleteObjectRequest.builder().bucket(s3StorageConfig.bucket).key("thumbnail-$id").build()
s3Client.deleteObject(fileDeleteRequest) s3Client.deleteObject(fileDeleteRequest)
s3Client.deleteObject(thumbnailDeleteRequest) s3Client.deleteObject(thumbnailDeleteRequest)
} }