feat(ranking): 랭킹 스냅샷 저장소를 추가한다

This commit is contained in:
2026-06-08 15:24:28 +09:00
parent 70cf3b29fa
commit 49f2238b37
5 changed files with 483 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
import kr.co.vividnext.sodalive.common.BaseEntity
import java.time.LocalDateTime
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Table
@Entity
@Table(name = "creator_ranking_snapshot")
class CreatorRankingSnapshot(
@Column(name = "aggregation_start_at_utc", nullable = false, updatable = false)
val aggregationStartAtUtc: LocalDateTime,
@Column(name = "aggregation_end_at_utc", nullable = false, updatable = false)
val aggregationEndAtUtc: LocalDateTime,
@Column(name = "creator_id", nullable = false, updatable = false)
val creatorId: Long,
@Column(name = "nickname", nullable = false, updatable = false, length = 100)
val nickname: String,
@Column(name = "profile_image_url", updatable = false, length = 500)
val profileImageUrl: String?,
@Column(name = "final_score", nullable = false, updatable = false)
val finalScore: Double,
@Column(name = "content_live_score", nullable = false, updatable = false)
val contentLiveScore: Double,
@Column(name = "engagement_score", nullable = false, updatable = false)
val engagementScore: Double,
@Column(name = "support_score", nullable = false, updatable = false)
val supportScore: Double,
@Column(name = "fan_loyalty_score", nullable = false, updatable = false)
val fanLoyaltyScore: Double,
@Column(name = "live_can_amount", nullable = false, updatable = false)
val liveCanAmount: Long,
@Column(name = "content_purchase_can_amount", nullable = false, updatable = false)
val contentPurchaseCanAmount: Long,
@Column(name = "content_like_count", nullable = false, updatable = false)
val contentLikeCount: Long,
@Column(name = "content_comment_count", nullable = false, updatable = false)
val contentCommentCount: Long,
@Column(name = "channel_donation_can_amount", nullable = false, updatable = false)
val channelDonationCanAmount: Long,
@Column(name = "channel_donation_count", nullable = false, updatable = false)
val channelDonationCount: Long,
@Column(name = "fan_talk_count", nullable = false, updatable = false)
val fanTalkCount: Long,
@Column(name = "final_follower_count", nullable = false, updatable = false)
val finalFollowerCount: Long,
@Column(name = "follow_increase", nullable = false, updatable = false)
val followIncrease: Long
) : BaseEntity()

View File

@@ -0,0 +1,50 @@
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import java.time.LocalDateTime
interface CreatorRankingSnapshotRepository : JpaRepository<CreatorRankingSnapshot, Long> {
fun findAllByAggregationStartAtUtcAndAggregationEndAtUtcOrderByFinalScoreDesc(
aggregationStartAtUtc: LocalDateTime,
aggregationEndAtUtc: LocalDateTime
): List<CreatorRankingSnapshot>
@Query(
value = """
select *
from creator_ranking_snapshot crs
where crs.aggregation_end_at_utc = (
select max(latest.aggregation_end_at_utc)
from creator_ranking_snapshot latest
)
order by crs.final_score desc
""",
nativeQuery = true
)
fun findLatestSnapshots(): List<CreatorRankingSnapshot>
@Query(
value = """
select *
from creator_ranking_snapshot crs
where crs.aggregation_end_at_utc = (
select max(previous.aggregation_end_at_utc)
from creator_ranking_snapshot previous
where previous.aggregation_end_at_utc < (
select max(latest.aggregation_end_at_utc)
from creator_ranking_snapshot latest
)
)
order by crs.final_score desc
""",
nativeQuery = true
)
fun findPreviousCompletedSnapshots(): List<CreatorRankingSnapshot>
fun deleteByAggregationStartAtUtcAndAggregationEndAtUtc(
@Param("aggregationStartAtUtc") aggregationStartAtUtc: LocalDateTime,
@Param("aggregationEndAtUtc") aggregationEndAtUtc: LocalDateTime
)
}

View File

@@ -0,0 +1,91 @@
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotPort
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotRecord
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
@Repository
class DefaultCreatorRankingSnapshotRepository(
private val repository: CreatorRankingSnapshotRepository
) : CreatorRankingSnapshotPort {
override fun findSnapshotsByAggregationPeriod(
aggregationStartAtUtc: LocalDateTime,
aggregationEndAtUtc: LocalDateTime
): List<CreatorRankingSnapshotRecord> {
return repository.findAllByAggregationStartAtUtcAndAggregationEndAtUtcOrderByFinalScoreDesc(
aggregationStartAtUtc = aggregationStartAtUtc,
aggregationEndAtUtc = aggregationEndAtUtc
).map { it.toRecord() }
}
override fun findLatestSnapshots(): List<CreatorRankingSnapshotRecord> {
return repository.findLatestSnapshots().map { it.toRecord() }
}
override fun findPreviousCompletedSnapshots(): List<CreatorRankingSnapshotRecord> {
return repository.findPreviousCompletedSnapshots().map { it.toRecord() }
}
@Transactional
override fun replaceSnapshots(
aggregationStartAtUtc: LocalDateTime,
aggregationEndAtUtc: LocalDateTime,
newSnapshots: List<CreatorRankingSnapshotRecord>
) {
repository.deleteByAggregationStartAtUtcAndAggregationEndAtUtc(
aggregationStartAtUtc = aggregationStartAtUtc,
aggregationEndAtUtc = aggregationEndAtUtc
)
repository.saveAll(newSnapshots.map { it.toEntity() })
}
private fun CreatorRankingSnapshot.toRecord(): CreatorRankingSnapshotRecord {
return CreatorRankingSnapshotRecord(
aggregationStartAtUtc = aggregationStartAtUtc,
aggregationEndAtUtc = aggregationEndAtUtc,
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 CreatorRankingSnapshotRecord.toEntity(): CreatorRankingSnapshot {
return CreatorRankingSnapshot(
aggregationStartAtUtc = aggregationStartAtUtc,
aggregationEndAtUtc = aggregationEndAtUtc,
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
)
}
}

View File

@@ -0,0 +1,42 @@
package kr.co.vividnext.sodalive.v2.ranking.port.out
import java.time.LocalDateTime
interface CreatorRankingSnapshotPort {
fun findSnapshotsByAggregationPeriod(
aggregationStartAtUtc: LocalDateTime,
aggregationEndAtUtc: LocalDateTime
): List<CreatorRankingSnapshotRecord>
fun findLatestSnapshots(): List<CreatorRankingSnapshotRecord>
fun findPreviousCompletedSnapshots(): List<CreatorRankingSnapshotRecord>
fun replaceSnapshots(
aggregationStartAtUtc: LocalDateTime,
aggregationEndAtUtc: LocalDateTime,
newSnapshots: List<CreatorRankingSnapshotRecord>
)
}
data class CreatorRankingSnapshotRecord(
val aggregationStartAtUtc: LocalDateTime,
val aggregationEndAtUtc: LocalDateTime,
val creatorId: Long,
val nickname: String,
val profileImageUrl: String?,
val finalScore: Double,
val contentLiveScore: Double,
val engagementScore: Double,
val supportScore: Double,
val fanLoyaltyScore: Double,
val liveCanAmount: Long,
val contentPurchaseCanAmount: Long,
val contentLikeCount: Long,
val contentCommentCount: Long,
val channelDonationCanAmount: Long,
val channelDonationCount: Long,
val fanTalkCount: Long,
val finalFollowerCount: Long,
val followIncrease: Long
)