feat(chat-character): 보온 주간 차트 콘텐츠 정렬 기준 추가

- 매출, 판매량, 댓글 수, 좋아요 수, 후원
This commit is contained in:
2025-11-11 23:02:58 +09:00
parent 16b6c13309
commit fe76ecdfa9
5 changed files with 135 additions and 1 deletions

View File

@@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.rank.ContentRankingSortType
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
@@ -79,4 +80,28 @@ class HomeController(private val service: HomeService) {
)
)
}
// 콘텐츠 랭킹 엔드포인트
@GetMapping("/content-ranking")
fun getContentRanking(
@RequestParam("sort", required = false) sort: ContentRankingSortType? = null,
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@RequestParam("offset", required = false) offset: Long? = null,
@RequestParam("limit", required = false) limit: Long? = null,
@RequestParam("theme", required = false) theme: String? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
ApiResponse.ok(
service.getContentRankingBySort(
sort = sort ?: ContentRankingSortType.REVENUE,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
offset = offset,
limit = limit,
theme = theme,
member = member
)
)
}
}

View File

@@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService
import kr.co.vividnext.sodalive.content.AudioContentMainItem
import kr.co.vividnext.sodalive.content.AudioContentService
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationService
import kr.co.vividnext.sodalive.content.series.ContentSeriesService
@@ -18,6 +19,7 @@ import kr.co.vividnext.sodalive.live.room.LiveRoomStatus
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberService
import kr.co.vividnext.sodalive.query.recommend.RecommendChannelQueryService
import kr.co.vividnext.sodalive.rank.ContentRankingSortType
import kr.co.vividnext.sodalive.rank.RankingRepository
import kr.co.vividnext.sodalive.rank.RankingService
import org.springframework.beans.factory.annotation.Value
@@ -153,7 +155,7 @@ class HomeService(
contentType = contentType,
startDate = startDate.minusDays(1),
endDate = endDate,
sortType = "매출"
sort = ContentRankingSortType.REVENUE
)
val recommendChannelList = recommendChannelService.getRecommendChannel(
@@ -277,6 +279,40 @@ class HomeService(
)
}
fun getContentRankingBySort(
sort: ContentRankingSortType,
isAdultContentVisible: Boolean,
contentType: ContentType,
offset: Long?,
limit: Long?,
theme: String?,
member: Member?
): List<GetAudioContentRankingItem> {
val memberId = member?.id
val isAdult = member?.auth != null && isAdultContentVisible
val currentDateTime = LocalDateTime.now()
val startDate = currentDateTime
.withHour(15)
.withMinute(0)
.withSecond(0)
.minusWeeks(1)
.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
val endDate = startDate.plusDays(6)
return rankingService.getContentRanking(
memberId = memberId,
isAdult = isAdult,
contentType = contentType,
startDate = startDate.minusDays(1),
endDate = endDate,
offset = offset ?: 0,
limit = limit ?: 12,
sort = sort,
theme = theme ?: ""
)
}
private fun getDayOfWeekByTimezone(timezone: String): SeriesPublishedDaysOfWeek {
val systemTime = LocalDateTime.now()
val zoneId = ZoneId.of(timezone)

View File

@@ -0,0 +1,21 @@
package kr.co.vividnext.sodalive.rank
/**
* 콘텐츠 랭킹 정렬 기준
*/
enum class ContentRankingSortType {
// 매출: order.can.sum.desc
REVENUE,
// 판매량: order.id.count.desc
SALES_COUNT,
// 댓글 수: audioContentComment.id.count.desc
COMMENT_COUNT,
// 좋아요 수: audioContentLike.id.count.desc
LIKE_COUNT,
// 후원: audioContentComment.donationCan.sum.desc
DONATION
}

View File

@@ -132,6 +132,14 @@ class RankingRepository(
.innerJoin(audioContent.theme, audioContentTheme)
}
"판매량" -> {
select
.from(order)
.innerJoin(order.audioContent, audioContent)
.innerJoin(audioContent.member, member)
.innerJoin(audioContent.theme, audioContentTheme)
}
else -> {
select
.from(order)
@@ -184,6 +192,18 @@ class RankingRepository(
.orderBy(audioContentLike.id.count().desc(), audioContent.createdAt.asc())
}
"판매량" -> {
select
.where(
where
.and(order.isActive.isTrue)
.and(order.createdAt.goe(startDate))
.and(order.createdAt.lt(endDate))
)
.groupBy(audioContent.id)
.orderBy(order.id.count().desc(), audioContent.createdAt.asc())
}
else -> {
select
.where(

View File

@@ -76,6 +76,38 @@ class RankingService(
return contentList
}
private fun toSortString(sort: ContentRankingSortType): String = when (sort) {
ContentRankingSortType.REVENUE -> "매출"
ContentRankingSortType.SALES_COUNT -> "판매량"
ContentRankingSortType.COMMENT_COUNT -> "댓글"
ContentRankingSortType.LIKE_COUNT -> "좋아요"
ContentRankingSortType.DONATION -> "후원"
}
fun getContentRanking(
memberId: Long?,
isAdult: Boolean,
contentType: ContentType,
startDate: LocalDateTime,
endDate: LocalDateTime,
offset: Long = 0,
limit: Long = 12,
sort: ContentRankingSortType,
theme: String = ""
): List<GetAudioContentRankingItem> {
return getContentRanking(
memberId = memberId,
isAdult = isAdult,
contentType = contentType,
startDate = startDate,
endDate = endDate,
offset = offset,
limit = limit,
sortType = toSortString(sort),
theme = theme
)
}
fun getSeriesRanking(
memberId: Long?,
isAdult: Boolean,