feat(home): AI 캐릭터 부족분을 랜덤 보충한다

This commit is contained in:
2026-07-22 22:14:26 +09:00
parent 075ca88f01
commit a0bf71be47
10 changed files with 230 additions and 4 deletions

View File

@@ -75,7 +75,10 @@ class HomeRecommendationFacade(
includeAdultContents = includeAdult
)
.map { it.toItem() },
aiCharacters = queryService.findAiCharacterRecommendations(limit = HOME_AI_CHARACTER_LIMIT).map { it.toItem() },
aiCharacters = queryService.findAiCharacterRecommendations(
limit = HOME_AI_CHARACTER_LIMIT,
backfillRandom = true
).map { it.toItem() },
genreCreators = queryService.findGenreCreatorRecommendations(
memberId = member?.id,
includeAdultGenres = includeAdult,

View File

@@ -775,6 +775,29 @@ class DefaultHomeRecommendationQueryRepository(
.fetch()
}
override fun findRandomAiCharacterRecommendationIds(
excludeCharacterIds: List<Long>,
limit: Int
): List<Long> {
val creatorMember = QMember("creatorMember")
val randomTieBreaker = Expressions.numberTemplate(Double::class.java, "function('rand')")
return queryFactory
.select(chatCharacter.id)
.from(chatCharacter)
.join(chatCharacter.creatorMember, creatorMember)
.where(
chatCharacter.isActive.isTrue,
excludeAiCharacterCondition(excludeCharacterIds),
creatorMember.isActive.isTrue,
creatorMember.role.eq(MemberRole.CREATOR),
creatorMember.memberKind.eq(MemberKind.AI_CHARACTER)
)
.orderBy(randomTieBreaker.asc())
.limit(limit.toLong())
.fetch()
}
override fun findCheerCreatorRecommendationDetails(
creatorIds: List<Long>,
memberId: Long?
@@ -1235,6 +1258,10 @@ class DefaultHomeRecommendationQueryRepository(
return if (includeAdultLives) null else liveRoom.isAdult.isFalse
}
private fun excludeAiCharacterCondition(excludeCharacterIds: List<Long>): BooleanExpression? {
return if (excludeCharacterIds.isEmpty()) null else chatCharacter.id.notIn(excludeCharacterIds)
}
private fun notBlockedCreatorCondition(memberId: Long?, creatorIdPath: Expression<Long>): BooleanExpression? {
if (memberId == null) return null
val blockMember = QBlockMember("recommendationBlockMember")

View File

@@ -78,13 +78,23 @@ class HomeRecommendationQueryService(
fun findAiCharacterRecommendations(
offset: Long = 0,
limit: Int = DEFAULT_AI_CHARACTER_LIMIT
limit: Int = DEFAULT_AI_CHARACTER_LIMIT,
backfillRandom: Boolean = false
): List<HomeAiCharacterRecommendationRecord> {
val snapshots = findAiCharacterSnapshotsWithFallback(offset, limit)
val detailsById = queryPort.findAiCharacterRecommendationDetails(snapshots.map { it.targetId })
.associateBy { it.characterId }
val snapshotDetails = snapshots.mapNotNull { detailsById[it.targetId] }
return snapshots.mapNotNull { detailsById[it.targetId] }
if (!backfillRandom || snapshots.isEmpty() || snapshotDetails.size >= limit) return snapshotDetails
val randomIds = queryPort.findRandomAiCharacterRecommendationIds(
excludeCharacterIds = snapshots.map { it.targetId },
limit = limit - snapshotDetails.size
)
val randomDetailsById = queryPort.findAiCharacterRecommendationDetails(randomIds).associateBy { it.characterId }
val randomDetails = randomIds.mapNotNull { randomDetailsById[it] }
return (snapshotDetails + randomDetails).distinctBy { it.characterId }.take(limit)
}
private fun findAiCharacterSnapshotsWithFallback(

View File

@@ -58,6 +58,11 @@ interface HomeRecommendationQueryPort {
fun findAiCharacterRecommendationDetails(characterIds: List<Long>): List<HomeAiCharacterRecommendationRecord>
fun findRandomAiCharacterRecommendationIds(
excludeCharacterIds: List<Long>,
limit: Int
): List<Long>
fun findCheerCreatorRecommendationDetails(
creatorIds: List<Long>,
memberId: Long? = null