From 9d0c8d063ea24dfb67e7093a11b6447ee3121a21 Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 12 Feb 2026 19:16:35 +0900 Subject: [PATCH] =?UTF-8?q?=ED=99=88=20-=20=EB=AC=B4=EB=A3=8C=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0,=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EA=B0=80=EB=8A=A5=20=EC=BD=98=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EB=9E=9C=EB=8D=A4=20=EC=B6=94=EC=B2=9C=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EC=B2=9C=20=EC=BD=98=ED=85=90=EC=B8=A0?= =?UTF-8?q?=EC=99=80=20=EB=8F=99=EC=9D=BC=ED=95=98=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/api/home/HomeService.kt | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt index a844b37b..929ef50d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt @@ -237,29 +237,28 @@ class HomeService( recommendChannelList } - val freeContentList = contentService.getLatestContentByTheme( + val freeContentList = getRandomizedContentList( memberId = memberId, + isAdult = isAdult, + contentType = contentType, theme = contentThemeService.getActiveThemeOfContent( isAdult = isAdult, isFree = true, contentType = contentType ), - contentType = contentType, isFree = true, - isAdult = isAdult, - orderByRandom = true + isPointAvailableOnly = false ) val translatedFreeContentList = getTranslatedContentList(contentList = freeContentList) // 포인트 사용가능 콘텐츠 리스트 - 랜덤으로 가져오기 (DB에서 isPointAvailable 조건 적용) - val pointAvailableContentList = contentService.getLatestContentByTheme( + val pointAvailableContentList = getRandomizedContentList( memberId = memberId, - theme = emptyList(), - contentType = contentType, - isFree = false, isAdult = isAdult, - orderByRandom = true, + contentType = contentType, + theme = emptyList(), + isFree = false, isPointAvailableOnly = true ) @@ -501,6 +500,61 @@ class HomeService( return candidates.lastIndex } + private fun getRandomizedContentList( + memberId: Long?, + isAdult: Boolean, + contentType: ContentType, + theme: List, + isFree: Boolean, + isPointAvailableOnly: Boolean, + targetSize: Int = 20 + ): List { + val buckets = listOf( + RecommendBucket(offset = 0L, limit = 50L), + RecommendBucket(offset = 50L, limit = 100L), + RecommendBucket(offset = 150L, limit = 150L) + ) + + val result = mutableListOf() + val seenIds = mutableSetOf() + + repeat(RECOMMEND_MAX_ATTEMPTS) { + if (result.size >= targetSize) return@repeat + + val remaining = targetSize - result.size + val targetPerBucket = maxOf(1, (remaining + buckets.size - 1) / buckets.size) + + for (bucket in buckets) { + if (result.size >= targetSize) break + + val batch = contentService.getLatestContentByTheme( + memberId = memberId, + theme = theme, + contentType = contentType, + offset = bucket.offset, + limit = bucket.limit, + sortType = SortType.NEWEST, + isFree = isFree, + isAdult = isAdult, + orderByRandom = false, + isPointAvailableOnly = isPointAvailableOnly, + excludeContentIds = seenIds.toList() + ) + + val selected = pickByTimeDecay( + batch = batch, + targetSize = minOf(targetPerBucket, targetSize - result.size), + seenIds = seenIds + ) + if (selected.isNotEmpty()) { + result.addAll(selected) + } + } + } + + return getTranslatedContentList(contentList = result.take(targetSize).shuffled()) + } + /** * 콘텐츠 리스트의 제목을 현재 언어(locale)에 맞춰 일괄 번역한다. *