feat(ranking): 크리에이터 랭킹 집계 저장소를 추가한다
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingScorePolicy
|
||||
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingSnapshotCandidate
|
||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingAggregationPort
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.EntityManager
|
||||
|
||||
@Repository
|
||||
class DefaultCreatorRankingAggregationRepository(
|
||||
private val entityManager: EntityManager
|
||||
) : CreatorRankingAggregationPort {
|
||||
private val scorePolicy = CreatorRankingScorePolicy()
|
||||
|
||||
override fun aggregateCandidates(
|
||||
startInclusiveUtc: LocalDateTime,
|
||||
endExclusiveUtc: LocalDateTime
|
||||
): List<CreatorRankingSnapshotCandidate> {
|
||||
val rows = entityManager.createNativeQuery(AGGREGATION_SQL)
|
||||
.setParameter("startInclusiveUtc", startInclusiveUtc)
|
||||
.setParameter("endExclusiveUtc", endExclusiveUtc)
|
||||
.resultList
|
||||
|
||||
return rows
|
||||
.map { row -> (row as Array<*>).toCandidate() }
|
||||
.filter { candidate -> candidate.finalScore >= MINIMUM_FINAL_SCORE }
|
||||
.sortedWith(compareByDescending<CreatorRankingSnapshotCandidate> { it.finalScore }.thenBy { it.creatorId })
|
||||
}
|
||||
|
||||
private fun Array<*>.toCandidate(): CreatorRankingSnapshotCandidate {
|
||||
val creatorId = this[0].toLong()
|
||||
val nickname = this[1] as String
|
||||
val profileImageUrl = this[2] as String?
|
||||
val liveCanAmount = this[3].toLong()
|
||||
val contentPurchaseCanAmount = this[4].toLong()
|
||||
val contentLikeCount = this[5].toLong()
|
||||
val contentCommentCount = this[6].toLong()
|
||||
val channelDonationCanAmount = this[7].toLong()
|
||||
val channelDonationCount = this[8].toLong()
|
||||
val fanTalkCount = this[9].toLong()
|
||||
val finalFollowerCount = this[10].toLong()
|
||||
val followIncrease = this[11].toLong()
|
||||
val contentLiveScore = scorePolicy.calculateContentLiveScore(liveCanAmount, contentPurchaseCanAmount)
|
||||
val engagementScore = scorePolicy.calculateEngagementScore(contentLikeCount, contentCommentCount)
|
||||
val supportScore = scorePolicy.calculateSupportScore(channelDonationCanAmount, channelDonationCount, fanTalkCount)
|
||||
val fanLoyaltyScore = scorePolicy.calculateFanLoyaltyScore(finalFollowerCount, followIncrease)
|
||||
val finalScore = scorePolicy.calculateFinalScore(contentLiveScore, engagementScore, supportScore, fanLoyaltyScore)
|
||||
|
||||
return CreatorRankingSnapshotCandidate(
|
||||
creatorId = creatorId,
|
||||
nickname = nickname,
|
||||
profileImageUrl = profileImageUrl,
|
||||
finalScore = finalScore,
|
||||
contentLiveScore = contentLiveScore,
|
||||
engagementScore = engagementScore,
|
||||
supportScore = supportScore,
|
||||
fanLoyaltyScore = fanLoyaltyScore,
|
||||
liveCanAmount = liveCanAmount,
|
||||
contentPurchaseCanAmount = contentPurchaseCanAmount,
|
||||
contentLikeCount = contentLikeCount,
|
||||
contentCommentCount = contentCommentCount,
|
||||
channelDonationCanAmount = channelDonationCanAmount,
|
||||
channelDonationCount = channelDonationCount,
|
||||
fanTalkCount = fanTalkCount,
|
||||
finalFollowerCount = finalFollowerCount,
|
||||
followIncrease = followIncrease
|
||||
)
|
||||
}
|
||||
|
||||
private fun Any?.toLong(): Long {
|
||||
return (this as Number?)?.toLong() ?: 0L
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MINIMUM_FINAL_SCORE = 1.0
|
||||
|
||||
private val AGGREGATION_SQL = """
|
||||
with active_creators as (
|
||||
select id, nickname, profile_image
|
||||
from member
|
||||
where role = 'CREATOR'
|
||||
and is_active = true
|
||||
), can_metrics as (
|
||||
select ucc.recipient_creator_id as creator_id,
|
||||
sum(case when uc.can_usage in ('DONATION', 'LIVE', 'SPIN_ROULETTE') then ucc.can else 0 end) as live_can_amount,
|
||||
sum(case when uc.can_usage = 'ORDER_CONTENT' then ucc.can else 0 end) as content_purchase_can_amount,
|
||||
sum(case when uc.can_usage = 'CHANNEL_DONATION' then ucc.can else 0 end) as channel_donation_can_amount,
|
||||
sum(case when uc.can_usage = 'CHANNEL_DONATION' then 1 else 0 end) as channel_donation_count
|
||||
from use_can_calculate ucc
|
||||
join use_can uc on uc.id = ucc.use_can_id
|
||||
where ucc.recipient_creator_id is not null
|
||||
and ucc.status = 'RECEIVED'
|
||||
and uc.is_refund = false
|
||||
and ucc.created_at >= :startInclusiveUtc
|
||||
and ucc.created_at < :endExclusiveUtc
|
||||
group by ucc.recipient_creator_id
|
||||
), like_metrics as (
|
||||
select c.member_id as creator_id,
|
||||
count(cl.id) as content_like_count
|
||||
from content_like cl
|
||||
join content c on c.id = cl.content_id
|
||||
where c.is_active = true
|
||||
and cl.is_active = true
|
||||
and cl.created_at >= :startInclusiveUtc
|
||||
and cl.created_at < :endExclusiveUtc
|
||||
group by c.member_id
|
||||
), comment_metrics as (
|
||||
select c.member_id as creator_id,
|
||||
count(cc.id) as content_comment_count
|
||||
from content_comment cc
|
||||
join content c on c.id = cc.content_id
|
||||
where c.is_active = true
|
||||
and cc.is_active = true
|
||||
and cc.member_id <> c.member_id
|
||||
and cc.created_at >= :startInclusiveUtc
|
||||
and cc.created_at < :endExclusiveUtc
|
||||
group by c.member_id
|
||||
), fan_talk_metrics as (
|
||||
select creator_id,
|
||||
count(id) as fan_talk_count
|
||||
from creator_cheers
|
||||
where is_active = true
|
||||
and parent_id is null
|
||||
and created_at >= :startInclusiveUtc
|
||||
and created_at < :endExclusiveUtc
|
||||
group by creator_id
|
||||
), final_follower_metrics as (
|
||||
select creator_id,
|
||||
count(id) as final_follower_count
|
||||
from creator_following
|
||||
where is_active = true
|
||||
and created_at < :endExclusiveUtc
|
||||
group by creator_id
|
||||
), new_follow_metrics as (
|
||||
select creator_id,
|
||||
count(id) as new_follow_count
|
||||
from creator_following
|
||||
where created_at >= :startInclusiveUtc
|
||||
and created_at < :endExclusiveUtc
|
||||
group by creator_id
|
||||
), unfollow_metrics as (
|
||||
select creator_id,
|
||||
count(id) as unfollow_count
|
||||
from creator_following
|
||||
where is_active = false
|
||||
and updated_at >= :startInclusiveUtc
|
||||
and updated_at < :endExclusiveUtc
|
||||
group by creator_id
|
||||
)
|
||||
select ac.id as creator_id,
|
||||
ac.nickname as nickname,
|
||||
ac.profile_image as profile_image_url,
|
||||
coalesce(cm.live_can_amount, 0) as live_can_amount,
|
||||
coalesce(cm.content_purchase_can_amount, 0) as content_purchase_can_amount,
|
||||
coalesce(lm.content_like_count, 0) as content_like_count,
|
||||
coalesce(com.content_comment_count, 0) as content_comment_count,
|
||||
coalesce(cm.channel_donation_can_amount, 0) as channel_donation_can_amount,
|
||||
coalesce(cm.channel_donation_count, 0) as channel_donation_count,
|
||||
coalesce(ftm.fan_talk_count, 0) as fan_talk_count,
|
||||
coalesce(ffm.final_follower_count, 0) as final_follower_count,
|
||||
coalesce(nfm.new_follow_count, 0) - coalesce(um.unfollow_count, 0) as follow_increase
|
||||
from active_creators ac
|
||||
left join can_metrics cm on cm.creator_id = ac.id
|
||||
left join like_metrics lm on lm.creator_id = ac.id
|
||||
left join comment_metrics com on com.creator_id = ac.id
|
||||
left join fan_talk_metrics ftm on ftm.creator_id = ac.id
|
||||
left join final_follower_metrics ffm on ffm.creator_id = ac.id
|
||||
left join new_follow_metrics nfm on nfm.creator_id = ac.id
|
||||
left join unfollow_metrics um on um.creator_id = ac.id
|
||||
where coalesce(cm.live_can_amount, 0) <> 0
|
||||
or coalesce(cm.content_purchase_can_amount, 0) <> 0
|
||||
or coalesce(lm.content_like_count, 0) <> 0
|
||||
or coalesce(com.content_comment_count, 0) <> 0
|
||||
or coalesce(cm.channel_donation_can_amount, 0) <> 0
|
||||
or coalesce(cm.channel_donation_count, 0) <> 0
|
||||
or coalesce(ftm.fan_talk_count, 0) <> 0
|
||||
or coalesce(ffm.final_follower_count, 0) <> 0
|
||||
or coalesce(nfm.new_follow_count, 0) <> 0
|
||||
or coalesce(um.unfollow_count, 0) <> 0
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package kr.co.vividnext.sodalive.v2.ranking.port.out
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingSnapshotCandidate
|
||||
import java.time.LocalDateTime
|
||||
|
||||
interface CreatorRankingAggregationPort {
|
||||
fun aggregateCandidates(
|
||||
startInclusiveUtc: LocalDateTime,
|
||||
endExclusiveUtc: LocalDateTime
|
||||
): List<CreatorRankingSnapshotCandidate>
|
||||
}
|
||||
Reference in New Issue
Block a user