feat(audio-recommendation): 추천 snapshot 갱신 서비스를 추가한다
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
package kr.co.vividnext.sodalive.v2.audio.recommendation.application
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.audio.recommendation.domain.AudioRecommendationVisibility
|
||||
import kr.co.vividnext.sodalive.v2.audio.recommendation.port.out.AudioRecommendationQueryPort
|
||||
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendedSectionType
|
||||
import kr.co.vividnext.sodalive.v2.recommendation.port.out.RecommendationSnapshotPort
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Propagation
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
@Service
|
||||
class AudioRecommendationSnapshotRefreshService(
|
||||
private val snapshotPort: RecommendationSnapshotPort,
|
||||
private val queryPort: AudioRecommendationQueryPort
|
||||
) {
|
||||
private val log = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
fun refreshDailySnapshots() {
|
||||
refreshDailySnapshots(ZonedDateTime.now(KST_ZONE))
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
fun refreshDailySnapshots(now: LocalDateTime) {
|
||||
refreshDailySnapshots(now.atZone(KST_ZONE))
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
fun refreshDailySnapshots(now: ZonedDateTime) {
|
||||
val startedAt = System.currentTimeMillis()
|
||||
val snapshotAt = snapshotAt(now)
|
||||
val newAndHotWindowStart = windowStart(snapshotAt, days = 3)
|
||||
val mostCommentedWindowStart = windowStart(snapshotAt, days = 7)
|
||||
val recommendedWindowStart = mostCommentedWindowStart
|
||||
|
||||
runCatching {
|
||||
replaceNewAndHotSnapshots(newAndHotWindowStart, snapshotAt, AudioRecommendationVisibility.SAFE)
|
||||
replaceNewAndHotSnapshots(newAndHotWindowStart, snapshotAt, AudioRecommendationVisibility.ALL)
|
||||
replaceMostCommentedSnapshots(mostCommentedWindowStart, snapshotAt, AudioRecommendationVisibility.SAFE)
|
||||
replaceMostCommentedSnapshots(mostCommentedWindowStart, snapshotAt, AudioRecommendationVisibility.ALL)
|
||||
replaceRecommendedAudioSnapshots(recommendedWindowStart, snapshotAt, AudioRecommendationVisibility.SAFE)
|
||||
replaceRecommendedAudioSnapshots(recommendedWindowStart, snapshotAt, AudioRecommendationVisibility.ALL)
|
||||
}.onSuccess {
|
||||
log.info(
|
||||
"event=audio_recommendation_snapshot_refresh_success snapshotAt={} elapsedMs={}",
|
||||
snapshotAt,
|
||||
System.currentTimeMillis() - startedAt
|
||||
)
|
||||
}.onFailure { ex ->
|
||||
log.warn(
|
||||
"event=audio_recommendation_snapshot_refresh_failure snapshotAt={} elapsedMs={} error={}",
|
||||
snapshotAt,
|
||||
System.currentTimeMillis() - startedAt,
|
||||
ex.message,
|
||||
ex
|
||||
)
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
||||
private fun replaceNewAndHotSnapshots(
|
||||
windowStart: LocalDateTime,
|
||||
snapshotAt: LocalDateTime,
|
||||
visibility: AudioRecommendationVisibility
|
||||
) {
|
||||
val sectionType = visibility.newAndHotSectionType()
|
||||
val snapshots = queryPort.findNewAndHotSnapshots(windowStart, snapshotAt, visibility, NEW_AND_HOT_LIMIT)
|
||||
snapshotPort.replaceSnapshots(sectionType, snapshotAt, snapshots)
|
||||
}
|
||||
|
||||
private fun replaceMostCommentedSnapshots(
|
||||
windowStart: LocalDateTime,
|
||||
snapshotAt: LocalDateTime,
|
||||
visibility: AudioRecommendationVisibility
|
||||
) {
|
||||
val sectionType = visibility.mostCommentedSectionType()
|
||||
val snapshots = queryPort.findMostCommentedSnapshots(windowStart, snapshotAt, visibility, MOST_COMMENTED_LIMIT)
|
||||
snapshotPort.replaceSnapshots(sectionType, snapshotAt, snapshots)
|
||||
}
|
||||
|
||||
private fun replaceRecommendedAudioSnapshots(
|
||||
windowStart: LocalDateTime,
|
||||
snapshotAt: LocalDateTime,
|
||||
visibility: AudioRecommendationVisibility
|
||||
) {
|
||||
val sectionType = visibility.recommendedAudioSectionType()
|
||||
val snapshots = queryPort.findRecommendedAudioSnapshots(windowStart, snapshotAt, visibility, RECOMMENDED_AUDIO_LIMIT)
|
||||
snapshotPort.replaceSnapshots(sectionType, snapshotAt, snapshots)
|
||||
}
|
||||
|
||||
private fun snapshotAt(now: ZonedDateTime): LocalDateTime {
|
||||
val nowKst = now
|
||||
.withZoneSameInstant(KST_ZONE)
|
||||
return nowKst.toLocalDate()
|
||||
.minusDays(1)
|
||||
.atTime(23, 59, 59)
|
||||
}
|
||||
|
||||
private fun windowStart(snapshotAt: LocalDateTime, days: Long): LocalDateTime {
|
||||
return snapshotAt.toLocalDate()
|
||||
.minusDays(days - 1)
|
||||
.atStartOfDay()
|
||||
}
|
||||
|
||||
private fun AudioRecommendationVisibility.newAndHotSectionType(): RecommendedSectionType {
|
||||
return when (this) {
|
||||
AudioRecommendationVisibility.SAFE -> RecommendedSectionType.NEW_AND_HOT_AUDIO_SAFE
|
||||
AudioRecommendationVisibility.ALL -> RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL
|
||||
}
|
||||
}
|
||||
|
||||
private fun AudioRecommendationVisibility.mostCommentedSectionType(): RecommendedSectionType {
|
||||
return when (this) {
|
||||
AudioRecommendationVisibility.SAFE -> RecommendedSectionType.MOST_COMMENTED_AUDIO_SAFE
|
||||
AudioRecommendationVisibility.ALL -> RecommendedSectionType.MOST_COMMENTED_AUDIO_ALL
|
||||
}
|
||||
}
|
||||
|
||||
private fun AudioRecommendationVisibility.recommendedAudioSectionType(): RecommendedSectionType {
|
||||
return when (this) {
|
||||
AudioRecommendationVisibility.SAFE -> RecommendedSectionType.RECOMMENDED_AUDIO_SAFE
|
||||
AudioRecommendationVisibility.ALL -> RecommendedSectionType.RECOMMENDED_AUDIO_ALL
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NEW_AND_HOT_LIMIT = 12
|
||||
const val MOST_COMMENTED_LIMIT = 5
|
||||
const val RECOMMENDED_AUDIO_LIMIT = 10
|
||||
private val KST_ZONE: ZoneId = ZoneId.of("Asia/Seoul")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user