test #69
@@ -55,6 +55,8 @@ dependencies {
 | 
				
			|||||||
    implementation("org.json:json:20230227")
 | 
					    implementation("org.json:json:20230227")
 | 
				
			||||||
    implementation("com.google.code.findbugs:jsr305:3.0.2")
 | 
					    implementation("com.google.code.findbugs:jsr305:3.0.2")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // firebase admin sdk
 | 
					    // firebase admin sdk
 | 
				
			||||||
    implementation("com.google.firebase:firebase-admin:9.2.0")
 | 
					    implementation("com.google.firebase:firebase-admin:9.2.0")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 <T> withPermit(block: suspend () -> T): T {
 | 
				
			||||||
 | 
					        return semaphore.withPermit { block() }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RestController
 | 
				
			|||||||
class AudioContentMainController(private val service: AudioContentMainService) {
 | 
					class AudioContentMainController(private val service: AudioContentMainService) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @GetMapping
 | 
					    @GetMapping
 | 
				
			||||||
    fun getMain(
 | 
					    suspend fun getMain(
 | 
				
			||||||
        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
 | 
					        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
 | 
				
			||||||
    ) = run {
 | 
					    ) = run {
 | 
				
			||||||
        if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
 | 
					        if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,8 @@
 | 
				
			|||||||
package kr.co.vividnext.sodalive.content.main
 | 
					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.AudioContentRepository
 | 
				
			||||||
import kr.co.vividnext.sodalive.content.AudioContentService
 | 
					import kr.co.vividnext.sodalive.content.AudioContentService
 | 
				
			||||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
 | 
					import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
 | 
				
			||||||
@@ -26,64 +29,100 @@ class AudioContentMainService(
 | 
				
			|||||||
    private val orderService: OrderService,
 | 
					    private val orderService: OrderService,
 | 
				
			||||||
    private val audioContentThemeRepository: AudioContentThemeQueryRepository,
 | 
					    private val audioContentThemeRepository: AudioContentThemeQueryRepository,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val semaphoreManager: SemaphoreManager,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Value("\${cloud.aws.cloud-front.host}")
 | 
					    @Value("\${cloud.aws.cloud-front.host}")
 | 
				
			||||||
    private val imageHost: String
 | 
					    private val imageHost: String
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    @Cacheable(cacheNames = ["default"], key = "'contentMain:' + #memberId + ':' + #isAdult")
 | 
					    suspend fun getMain(memberId: Long, isAdult: Boolean): GetAudioContentMainResponse {
 | 
				
			||||||
    fun getMain(memberId: Long, isAdult: Boolean): GetAudioContentMainResponse {
 | 
					        return coroutineScope {
 | 
				
			||||||
        // 2주일 이내에 콘텐츠를 올린 크리에이터 20명 조회
 | 
					            // 2주일 이내에 콘텐츠를 올린 크리에이터 20명 조회
 | 
				
			||||||
        val newContentUploadCreatorList = getNewContentUploadCreatorList(memberId = memberId, isAdult = isAdult)
 | 
					            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개
 | 
					            // 구매목록 20개
 | 
				
			||||||
        val orderList = orderService.getAudioContentMainOrderList(
 | 
					            val orderList = async {
 | 
				
			||||||
            memberId = memberId,
 | 
					                semaphoreManager.withPermit {
 | 
				
			||||||
            limit = 20
 | 
					                    orderService.getAudioContentMainOrderList(
 | 
				
			||||||
        )
 | 
					                        memberId = memberId,
 | 
				
			||||||
 | 
					                        limit = 20
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 콘텐츠 테마
 | 
					            // 콘텐츠 테마
 | 
				
			||||||
        val themeList = audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult)
 | 
					            val themeList = async {
 | 
				
			||||||
 | 
					                semaphoreManager.withPermit {
 | 
				
			||||||
 | 
					                    audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 새 콘텐츠 20개 - 시간 내림차순 정렬
 | 
					            // 새 콘텐츠 20개 - 시간 내림차순 정렬
 | 
				
			||||||
        val newContentList = repository.findByTheme(
 | 
					            val newContentList = async {
 | 
				
			||||||
            cloudfrontHost = imageHost,
 | 
					                semaphoreManager.withPermit {
 | 
				
			||||||
            isAdult = isAdult
 | 
					                    repository.findByTheme(
 | 
				
			||||||
        )
 | 
					                        cloudfrontHost = imageHost,
 | 
				
			||||||
            .asSequence()
 | 
					                        isAdult = isAdult
 | 
				
			||||||
            .filter { !blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = it.creatorId) }
 | 
					                    )
 | 
				
			||||||
            .toList()
 | 
					                        .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 currentDateTime = LocalDateTime.now()
 | 
				
			||||||
        val startDate = currentDateTime
 | 
					            val startDate = currentDateTime
 | 
				
			||||||
            .minusWeeks(1)
 | 
					                .minusWeeks(1)
 | 
				
			||||||
            .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
 | 
					                .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
 | 
				
			||||||
            .withHour(15)
 | 
					                .withHour(15)
 | 
				
			||||||
            .withMinute(0)
 | 
					                .withMinute(0)
 | 
				
			||||||
            .withSecond(0)
 | 
					                .withSecond(0)
 | 
				
			||||||
        val endDate = startDate.plusDays(7)
 | 
					            val endDate = startDate.plusDays(7)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val contentRankingSortTypeList = audioContentService.getContentRankingSortTypeList()
 | 
					            val contentRankingSortTypeList = audioContentService.getContentRankingSortTypeList()
 | 
				
			||||||
        val contentRanking = audioContentService.getAudioContentRanking(
 | 
					            val contentRanking = async {
 | 
				
			||||||
            isAdult = isAdult,
 | 
					                semaphoreManager.withPermit {
 | 
				
			||||||
            startDate = startDate,
 | 
					                    audioContentService.getAudioContentRanking(
 | 
				
			||||||
            endDate = endDate,
 | 
					                        isAdult = isAdult,
 | 
				
			||||||
            offset = 0,
 | 
					                        startDate = startDate,
 | 
				
			||||||
            limit = 12
 | 
					                        endDate = endDate,
 | 
				
			||||||
        )
 | 
					                        offset = 0,
 | 
				
			||||||
 | 
					                        limit = 12
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return GetAudioContentMainResponse(
 | 
					            GetAudioContentMainResponse(
 | 
				
			||||||
            newContentUploadCreatorList = newContentUploadCreatorList,
 | 
					                newContentUploadCreatorList = newContentUploadCreatorList.await(),
 | 
				
			||||||
            bannerList = bannerList,
 | 
					                bannerList = bannerList.await(),
 | 
				
			||||||
            orderList = orderList,
 | 
					                orderList = orderList.await(),
 | 
				
			||||||
            themeList = themeList,
 | 
					                themeList = themeList.await(),
 | 
				
			||||||
            newContentList = newContentList,
 | 
					                newContentList = newContentList.await(),
 | 
				
			||||||
            curationList = curationList,
 | 
					                curationList = curationList.await(),
 | 
				
			||||||
            contentRankingSortTypeList = contentRankingSortTypeList,
 | 
					                contentRankingSortTypeList = contentRankingSortTypeList,
 | 
				
			||||||
            contentRanking = contentRanking
 | 
					                contentRanking = contentRanking.await()
 | 
				
			||||||
        )
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun getThemeList(member: Member): List<String> {
 | 
					    fun getThemeList(member: Member): List<String> {
 | 
				
			||||||
@@ -116,6 +155,7 @@ class AudioContentMainService(
 | 
				
			|||||||
        return GetNewContentAllResponse(totalCount, items)
 | 
					        return GetNewContentAllResponse(totalCount, items)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Cacheable(cacheNames = ["default"], key = "'newContentUploadCreatorList:' + #memberId + ':' + #isAdult")
 | 
				
			||||||
    fun getNewContentUploadCreatorList(memberId: Long, isAdult: Boolean): List<GetNewContentUploadCreator> {
 | 
					    fun getNewContentUploadCreatorList(memberId: Long, isAdult: Boolean): List<GetNewContentUploadCreator> {
 | 
				
			||||||
        return repository.getNewContentUploadCreatorList(
 | 
					        return repository.getNewContentUploadCreatorList(
 | 
				
			||||||
            cloudfrontHost = imageHost,
 | 
					            cloudfrontHost = imageHost,
 | 
				
			||||||
@@ -126,6 +166,7 @@ class AudioContentMainService(
 | 
				
			|||||||
            .toList()
 | 
					            .toList()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Cacheable(cacheNames = ["default"], key = "'contentMainBannerList:' + #memberId + ':' + #isAdult")
 | 
				
			||||||
    fun getAudioContentMainBannerList(memberId: Long, isAdult: Boolean) =
 | 
					    fun getAudioContentMainBannerList(memberId: Long, isAdult: Boolean) =
 | 
				
			||||||
        repository.getAudioContentMainBannerList(isAdult = isAdult)
 | 
					        repository.getAudioContentMainBannerList(isAdult = isAdult)
 | 
				
			||||||
            .asSequence()
 | 
					            .asSequence()
 | 
				
			||||||
@@ -174,6 +215,7 @@ class AudioContentMainService(
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            .toList()
 | 
					            .toList()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Cacheable(cacheNames = ["default"], key = "'contentCurationList:' + #memberId + ':' + #isAdult")
 | 
				
			||||||
    fun getAudioContentCurationList(memberId: Long, isAdult: Boolean) =
 | 
					    fun getAudioContentCurationList(memberId: Long, isAdult: Boolean) =
 | 
				
			||||||
        repository.getAudioContentCurations(isAdult = isAdult)
 | 
					        repository.getAudioContentCurations(isAdult = isAdult)
 | 
				
			||||||
            .asSequence()
 | 
					            .asSequence()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user