후원 랭킹 캐시 추가

This commit is contained in:
2025-12-29 12:08:41 +09:00
parent 943a88afdb
commit 78f4c56232
4 changed files with 137 additions and 9 deletions

View File

@@ -14,6 +14,7 @@ import kr.co.vividnext.sodalive.explorer.profile.ChannelNotice
import kr.co.vividnext.sodalive.explorer.profile.ChannelNoticeRepository
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheersRepository
import kr.co.vividnext.sodalive.explorer.profile.CreatorDonationRankingService
import kr.co.vividnext.sodalive.explorer.profile.PostWriteCheersRequest
import kr.co.vividnext.sodalive.explorer.profile.PutWriteCheersRequest
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityService
@@ -43,6 +44,8 @@ import kotlin.random.Random
class ExplorerService(
private val memberService: MemberService,
private val audioContentService: AudioContentService,
private val donationRankingService: CreatorDonationRankingService,
private val queryRepository: ExplorerQueryRepository,
private val cheersRepository: CreatorCheersRepository,
private val noticeRepository: ChannelNoticeRepository,
@@ -217,9 +220,9 @@ class ExplorerService(
val memberDonationRanking = if (
isCreator && (creatorId == member.id!! || creatorAccount.isVisibleDonationRank)
) {
queryRepository.getMemberDonationRanking(
creatorId,
10,
donationRankingService.getMemberDonationRanking(
creatorId = creatorId,
limit = 10,
withDonationCan = creatorId == member.id!!
)
} else {

View File

@@ -1,5 +1,8 @@
package kr.co.vividnext.sodalive.explorer
import com.fasterxml.jackson.annotation.JsonProperty
import java.io.Serializable
data class GetDonationAllResponse(
val accumulatedCansToday: Int,
val accumulatedCansLastWeek: Int,
@@ -7,11 +10,15 @@ data class GetDonationAllResponse(
val isVisibleDonationRank: Boolean,
val totalCount: Int,
val userDonationRanking: List<MemberDonationRankingResponse>
)
) : Serializable
data class MemberDonationRankingResponse(
val userId: Long,
val nickname: String,
val profileImage: String,
val donationCan: Int
)
@JsonProperty("userId") val userId: Long,
@JsonProperty("nickname") val nickname: String,
@JsonProperty("profileImage") val profileImage: String,
@JsonProperty("donationCan") val donationCan: Int
) : Serializable
data class MemberDonationRankingListResponse(
@JsonProperty("rankings") val rankings: List<MemberDonationRankingResponse>
) : Serializable

View File

@@ -0,0 +1,65 @@
package kr.co.vividnext.sodalive.explorer.profile
import com.fasterxml.jackson.annotation.JsonProperty
import com.querydsl.core.annotations.QueryProjection
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.can.use.CanUsage
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
import kr.co.vividnext.sodalive.can.use.QUseCanCalculate.useCanCalculate
import kr.co.vividnext.sodalive.member.QMember.member
import org.springframework.stereotype.Repository
import java.time.DayOfWeek
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.temporal.TemporalAdjusters
@Repository
class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFactory) {
fun getMemberDonationRanking(
creatorId: Long,
limit: Long
): List<DonationRankingProjection> {
val now = LocalDateTime.now()
val lastMonday = now.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
.minusWeeks(1)
.with(LocalTime.MIN)
val lastSunday = lastMonday.plusDays(6).with(LocalTime.MAX)
val donationCan = useCan.rewardCan.add(useCan.can).sum()
return queryFactory
.select(
QDonationRankingProjection(
member.id,
member.nickname,
member.profileImage,
donationCan
)
)
.from(useCanCalculate)
.innerJoin(useCanCalculate.useCan, useCan)
.innerJoin(useCan.member, member)
.where(
useCan.member.isActive.isTrue
.and(useCan.isRefund.isFalse)
.and(useCanCalculate.recipientCreatorId.eq(creatorId))
.and(
useCan.canUsage.eq(CanUsage.DONATION)
.or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE))
.or(useCan.canUsage.eq(CanUsage.LIVE))
)
.and(useCan.createdAt.between(lastMonday, lastSunday))
)
.offset(0)
.limit(limit)
.groupBy(member.id)
.orderBy(donationCan.desc(), member.id.desc())
.fetch()
}
}
data class DonationRankingProjection @QueryProjection constructor(
@JsonProperty("memberId") val memberId: Long,
@JsonProperty("nickname") val nickname: String,
@JsonProperty("profileImage") val profileImage: String,
@JsonProperty("donationCan") val donationCan: Int
)

View File

@@ -0,0 +1,53 @@
package kr.co.vividnext.sodalive.explorer.profile
import kr.co.vividnext.sodalive.explorer.MemberDonationRankingListResponse
import kr.co.vividnext.sodalive.explorer.MemberDonationRankingResponse
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Service
import java.time.DayOfWeek
import java.time.Duration
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.temporal.ChronoUnit
import java.time.temporal.TemporalAdjusters
@Service
class CreatorDonationRankingService(
private val repository: CreatorDonationRankingQueryRepository,
private val redisTemplate: RedisTemplate<String, Any>
) {
fun getMemberDonationRanking(
creatorId: Long,
limit: Long,
withDonationCan: Boolean
): List<MemberDonationRankingResponse> {
val cacheKey = "creator_donation_ranking:$creatorId:$limit:$withDonationCan"
val cachedData = redisTemplate.opsForValue().get(cacheKey) as? MemberDonationRankingListResponse
if (cachedData != null) {
return cachedData.rankings
}
val memberDonationRanking = repository.getMemberDonationRanking(creatorId, limit)
val result = memberDonationRanking.map {
MemberDonationRankingResponse(
it.memberId,
it.nickname,
it.profileImage,
if (withDonationCan) it.donationCan else 0
)
}
val now = LocalDateTime.now()
val nextMonday = now.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).with(LocalTime.MIN)
val secondsUntilNextMonday = ChronoUnit.SECONDS.between(now, nextMonday)
redisTemplate.opsForValue().set(
cacheKey,
MemberDonationRankingListResponse(result),
Duration.ofSeconds(secondsUntilNextMonday)
)
return result
}
}