From ce15025c8d030b926a21824a77d77cf7a199d8d3 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 7 Nov 2023 16:47:25 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20API=20-=20=EC=BD=94=EB=A3=A8=ED=8B=B4=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 2 + .../sodalive/common/SemaphoreManager.kt | 10 ++ .../sodalive/configs/SemaphoreConfig.kt | 14 ++ .../main/AudioContentMainController.kt | 2 +- .../content/main/AudioContentMainService.kt | 136 ++++++++++++------ 5 files changed, 116 insertions(+), 48 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/common/SemaphoreManager.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/configs/SemaphoreConfig.kt diff --git a/build.gradle.kts b/build.gradle.kts index 84666cf..ff88d2f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -55,6 +55,8 @@ dependencies { implementation("org.json:json:20230227") implementation("com.google.code.findbugs:jsr305:3.0.2") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") + // firebase admin sdk implementation("com.google.firebase:firebase-admin:9.2.0") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/common/SemaphoreManager.kt b/src/main/kotlin/kr/co/vividnext/sodalive/common/SemaphoreManager.kt new file mode 100644 index 0000000..b840ea1 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/common/SemaphoreManager.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.common + +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit + +class SemaphoreManager(private val semaphore: Semaphore) { + suspend fun withPermit(block: suspend () -> T): T { + return semaphore.withPermit { block() } + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SemaphoreConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SemaphoreConfig.kt new file mode 100644 index 0000000..422a09c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SemaphoreConfig.kt @@ -0,0 +1,14 @@ +package kr.co.vividnext.sodalive.configs + +import kotlinx.coroutines.sync.Semaphore +import kr.co.vividnext.sodalive.common.SemaphoreManager +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class SemaphoreConfig { + @Bean + fun semaphoreManager(): SemaphoreManager { + return SemaphoreManager(Semaphore(4)) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainController.kt index 18c4884..e78b513 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainController.kt @@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RestController class AudioContentMainController(private val service: AudioContentMainService) { @GetMapping - fun getMain( + suspend fun getMain( @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { if (member == null) throw SodaException("로그인 정보를 확인해주세요.") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt index 9808a59..8a4b387 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt @@ -1,5 +1,8 @@ package kr.co.vividnext.sodalive.content.main +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kr.co.vividnext.sodalive.common.SemaphoreManager import kr.co.vividnext.sodalive.content.AudioContentRepository import kr.co.vividnext.sodalive.content.AudioContentService import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType @@ -26,64 +29,100 @@ class AudioContentMainService( private val orderService: OrderService, private val audioContentThemeRepository: AudioContentThemeQueryRepository, + private val semaphoreManager: SemaphoreManager, + @Value("\${cloud.aws.cloud-front.host}") private val imageHost: String ) { - @Cacheable(cacheNames = ["default"], key = "'contentMain:' + #memberId + ':' + #isAdult") - fun getMain(memberId: Long, isAdult: Boolean): GetAudioContentMainResponse { - // 2주일 이내에 콘텐츠를 올린 크리에이터 20명 조회 - val newContentUploadCreatorList = getNewContentUploadCreatorList(memberId = memberId, isAdult = isAdult) + suspend fun getMain(memberId: Long, isAdult: Boolean): GetAudioContentMainResponse { + return coroutineScope { + // 2주일 이내에 콘텐츠를 올린 크리에이터 20명 조회 + val newContentUploadCreatorList = async { + semaphoreManager.withPermit { + getNewContentUploadCreatorList(memberId = memberId, isAdult = isAdult) + } + } - val bannerList = getAudioContentMainBannerList(memberId = memberId, isAdult = isAdult) + val bannerList = async { + semaphoreManager.withPermit { + getAudioContentMainBannerList(memberId = memberId, isAdult = isAdult) + } + } - // 구매목록 20개 - val orderList = orderService.getAudioContentMainOrderList( - memberId = memberId, - limit = 20 - ) + // 구매목록 20개 + val orderList = async { + semaphoreManager.withPermit { + orderService.getAudioContentMainOrderList( + memberId = memberId, + limit = 20 + ) + } + } - // 콘텐츠 테마 - val themeList = audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult) + // 콘텐츠 테마 + val themeList = async { + semaphoreManager.withPermit { + audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult) + } + } - // 새 콘텐츠 20개 - 시간 내림차순 정렬 - val newContentList = repository.findByTheme( - cloudfrontHost = imageHost, - isAdult = isAdult - ) - .asSequence() - .filter { !blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = it.creatorId) } - .toList() + // 새 콘텐츠 20개 - 시간 내림차순 정렬 + val newContentList = async { + semaphoreManager.withPermit { + repository.findByTheme( + cloudfrontHost = imageHost, + isAdult = isAdult + ) + .asSequence() + .filter { + !blockMemberRepository.isBlocked( + blockedMemberId = memberId, + memberId = it.creatorId + ) + } + .toList() + } + } - val curationList = getAudioContentCurationList(memberId = memberId, isAdult = isAdult) + val curationList = async { + semaphoreManager.withPermit { + getAudioContentCurationList(memberId = memberId, isAdult = isAdult) + } + } - val currentDateTime = LocalDateTime.now() - val startDate = currentDateTime - .minusWeeks(1) - .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) - .withHour(15) - .withMinute(0) - .withSecond(0) - val endDate = startDate.plusDays(7) + val currentDateTime = LocalDateTime.now() + val startDate = currentDateTime + .minusWeeks(1) + .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) + .withHour(15) + .withMinute(0) + .withSecond(0) + val endDate = startDate.plusDays(7) - val contentRankingSortTypeList = audioContentService.getContentRankingSortTypeList() - val contentRanking = audioContentService.getAudioContentRanking( - isAdult = isAdult, - startDate = startDate, - endDate = endDate, - offset = 0, - limit = 12 - ) + val contentRankingSortTypeList = audioContentService.getContentRankingSortTypeList() + val contentRanking = async { + semaphoreManager.withPermit { + audioContentService.getAudioContentRanking( + isAdult = isAdult, + startDate = startDate, + endDate = endDate, + offset = 0, + limit = 12 + ) + } + } - return GetAudioContentMainResponse( - newContentUploadCreatorList = newContentUploadCreatorList, - bannerList = bannerList, - orderList = orderList, - themeList = themeList, - newContentList = newContentList, - curationList = curationList, - contentRankingSortTypeList = contentRankingSortTypeList, - contentRanking = contentRanking - ) + GetAudioContentMainResponse( + newContentUploadCreatorList = newContentUploadCreatorList.await(), + bannerList = bannerList.await(), + orderList = orderList.await(), + themeList = themeList.await(), + newContentList = newContentList.await(), + curationList = curationList.await(), + contentRankingSortTypeList = contentRankingSortTypeList, + contentRanking = contentRanking.await() + ) + } } fun getThemeList(member: Member): List { @@ -116,6 +155,7 @@ class AudioContentMainService( return GetNewContentAllResponse(totalCount, items) } + @Cacheable(cacheNames = ["default"], key = "'newContentUploadCreatorList:' + #memberId + ':' + #isAdult") fun getNewContentUploadCreatorList(memberId: Long, isAdult: Boolean): List { return repository.getNewContentUploadCreatorList( cloudfrontHost = imageHost, @@ -126,6 +166,7 @@ class AudioContentMainService( .toList() } + @Cacheable(cacheNames = ["default"], key = "'contentMainBannerList:' + #memberId + ':' + #isAdult") fun getAudioContentMainBannerList(memberId: Long, isAdult: Boolean) = repository.getAudioContentMainBannerList(isAdult = isAdult) .asSequence() @@ -174,6 +215,7 @@ class AudioContentMainService( } .toList() + @Cacheable(cacheNames = ["default"], key = "'contentCurationList:' + #memberId + ':' + #isAdult") fun getAudioContentCurationList(memberId: Long, isAdult: Boolean) = repository.getAudioContentCurations(isAdult = isAdult) .asSequence()