feat(content-preference): 콘텐츠 조회 설정 서버 저장 전환을 반영한다

This commit is contained in:
2026-03-27 13:33:51 +09:00
parent 1ba3cb8a40
commit a87bd147dc
75 changed files with 3593 additions and 301 deletions

View File

@@ -24,8 +24,8 @@ class HomeController(private val service: HomeService) {
ApiResponse.ok(
service.fetchData(
timezone = timezone,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType,
member
)
)
@@ -41,8 +41,8 @@ class HomeController(private val service: HomeService) {
ApiResponse.ok(
service.getLatestContentByTheme(
theme = theme,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType,
member
)
)
@@ -58,8 +58,8 @@ class HomeController(private val service: HomeService) {
ApiResponse.ok(
service.getDayOfWeekSeriesList(
dayOfWeek = dayOfWeek,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType,
member
)
)
@@ -74,8 +74,8 @@ class HomeController(private val service: HomeService) {
) = run {
ApiResponse.ok(
service.getRecommendContentList(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType,
member = member
)
)
@@ -95,8 +95,8 @@ class HomeController(private val service: HomeService) {
ApiResponse.ok(
service.getContentRankingBySort(
sort = sort ?: ContentRankingSortType.REVENUE,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType,
offset = offset,
limit = limit,
theme = theme,

View File

@@ -18,6 +18,8 @@ import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.live.room.LiveRoomService
import kr.co.vividnext.sodalive.live.room.LiveRoomStatus
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.member.contentpreference.ViewerContentPreference
import kr.co.vividnext.sodalive.query.recommend.RecommendChannelQueryService
import kr.co.vividnext.sodalive.rank.ContentRankingSortType
import kr.co.vividnext.sodalive.rank.RankingRepository
@@ -47,6 +49,7 @@ class HomeService(
private val explorerQueryRepository: ExplorerQueryRepository,
private val langContext: LangContext,
private val memberContentPreferenceService: MemberContentPreferenceService,
@Value("\${cloud.aws.cloud-front.host}")
private val imageHost: String
@@ -69,17 +72,19 @@ class HomeService(
fun fetchData(
timezone: String,
isAdultContentVisible: Boolean,
contentType: ContentType,
isAdultContentVisible: Boolean?,
contentType: ContentType?,
member: Member?
): GetHomeResponse {
val preference = resolvePreference(member, isAdultContentVisible, contentType)
val memberId = member?.id
val isAdult = member?.auth != null && isAdultContentVisible
val isAdult = preference.isAdult
val resolvedContentType = preference.contentType
val liveList = liveRoomService.getRoomList(
dateString = null,
status = LiveRoomStatus.NOW,
isAdultContentVisible = isAdultContentVisible,
isAdultContentVisible = isAdult,
pageable = Pageable.ofSize(10),
member = member,
timezone = timezone
@@ -102,14 +107,14 @@ class HomeService(
val latestContentThemeList = contentThemeService.getActiveThemeOfContent(
isAdult = isAdult,
contentType = contentType,
contentType = resolvedContentType,
excludeThemes = listOf("다시듣기")
)
val latestContentList = contentService.getLatestContentByTheme(
memberId = memberId,
theme = latestContentThemeList,
contentType = contentType,
contentType = resolvedContentType,
isFree = false,
isAdult = isAdult
)
@@ -128,7 +133,7 @@ class HomeService(
val originalAudioDramaList = seriesService.getOriginalAudioDramaList(
memberId = memberId,
isAdult = isAdult,
contentType = contentType
contentType = resolvedContentType
)
val auditionList = auditionService.getInProgressAuditionList(isAdult = isAdult)
@@ -137,7 +142,7 @@ class HomeService(
val translatedDayOfWeekSeriesList = seriesService.getDayOfWeekSeriesList(
memberId = memberId,
isAdult = isAdult,
contentType = contentType,
contentType = resolvedContentType,
dayOfWeek = getDayOfWeekByTimezone(timezone)
)
@@ -157,7 +162,7 @@ class HomeService(
val contentRanking = rankingService.getContentRanking(
memberId = memberId,
isAdult = isAdult,
contentType = contentType,
contentType = resolvedContentType,
startDate = startDate.minusDays(1),
endDate = endDate,
sort = ContentRankingSortType.REVENUE
@@ -166,17 +171,17 @@ class HomeService(
val recommendChannelList = recommendChannelService.getRecommendChannel(
memberId = memberId,
isAdult = isAdult,
contentType = contentType
contentType = resolvedContentType
)
val freeContentList = getRandomizedContentList(
memberId = memberId,
isAdult = isAdult,
contentType = contentType,
contentType = resolvedContentType,
theme = contentThemeService.getActiveThemeOfContent(
isAdult = isAdult,
isFree = true,
contentType = contentType
contentType = resolvedContentType
),
isFree = true,
isPointAvailableOnly = false
@@ -186,7 +191,7 @@ class HomeService(
val pointAvailableContentList = getRandomizedContentList(
memberId = memberId,
isAdult = isAdult,
contentType = contentType,
contentType = resolvedContentType,
theme = emptyList(),
isFree = false,
isPointAvailableOnly = true
@@ -212,9 +217,8 @@ class HomeService(
recommendChannelList = recommendChannelList,
freeContentList = freeContentList,
pointAvailableContentList = pointAvailableContentList,
recommendContentList = getRecommendContentList(
isAdultContentVisible = isAdultContentVisible,
contentType = contentType,
recommendContentList = getRecommendContentListByPreference(
preference = preference,
member = member,
excludeContentIds = excludeContentIds
)
@@ -223,18 +227,20 @@ class HomeService(
fun getLatestContentByTheme(
theme: String,
isAdultContentVisible: Boolean,
contentType: ContentType,
isAdultContentVisible: Boolean?,
contentType: ContentType?,
member: Member?
): List<AudioContentMainItem> {
val preference = resolvePreference(member, isAdultContentVisible, contentType)
val memberId = member?.id
val isAdult = member?.auth != null && isAdultContentVisible
val isAdult = preference.isAdult
val resolvedContentType = preference.contentType
val themeList = if (theme.isBlank()) {
contentThemeService.getActiveThemeOfContent(
isAdult = isAdult,
isFree = false,
contentType = contentType,
contentType = resolvedContentType,
excludeThemes = listOf("다시듣기")
)
} else {
@@ -244,7 +250,7 @@ class HomeService(
return contentService.getLatestContentByTheme(
memberId = memberId,
theme = themeList,
contentType = contentType,
contentType = resolvedContentType,
isFree = false,
isAdult = isAdult
)
@@ -252,32 +258,34 @@ class HomeService(
fun getDayOfWeekSeriesList(
dayOfWeek: SeriesPublishedDaysOfWeek,
isAdultContentVisible: Boolean,
contentType: ContentType,
isAdultContentVisible: Boolean?,
contentType: ContentType?,
member: Member?
): List<GetSeriesListResponse.SeriesListItem> {
val preference = resolvePreference(member, isAdultContentVisible, contentType)
val memberId = member?.id
val isAdult = member?.auth != null && isAdultContentVisible
val isAdult = preference.isAdult
return seriesService.getDayOfWeekSeriesList(
memberId = memberId,
isAdult = isAdult,
contentType = contentType,
contentType = preference.contentType,
dayOfWeek = dayOfWeek
)
}
fun getContentRankingBySort(
sort: ContentRankingSortType,
isAdultContentVisible: Boolean,
contentType: ContentType,
isAdultContentVisible: Boolean?,
contentType: ContentType?,
offset: Long?,
limit: Long?,
theme: String?,
member: Member?
): List<GetAudioContentRankingItem> {
val preference = resolvePreference(member, isAdultContentVisible, contentType)
val memberId = member?.id
val isAdult = member?.auth != null && isAdultContentVisible
val isAdult = preference.isAdult
val currentDateTime = LocalDateTime.now()
val startDate = currentDateTime
@@ -291,7 +299,7 @@ class HomeService(
return rankingService.getContentRanking(
memberId = memberId,
isAdult = isAdult,
contentType = contentType,
contentType = preference.contentType,
startDate = startDate.minusDays(1),
endDate = endDate,
offset = offset ?: 0,
@@ -320,13 +328,22 @@ class HomeService(
}
fun getRecommendContentList(
isAdultContentVisible: Boolean,
contentType: ContentType,
isAdultContentVisible: Boolean?,
contentType: ContentType?,
member: Member?,
excludeContentIds: List<Long> = emptyList()
): List<AudioContentMainItem> {
val preference = resolvePreference(member, isAdultContentVisible, contentType)
return getRecommendContentListByPreference(preference, member, excludeContentIds)
}
private fun getRecommendContentListByPreference(
preference: ViewerContentPreference,
member: Member?,
excludeContentIds: List<Long>
): List<AudioContentMainItem> {
val memberId = member?.id
val isAdult = member?.auth != null && isAdultContentVisible
val isAdult = preference.isAdult
// 3개의 버킷(최근/중간/과거)에서 후보군을 조회한 뒤, 시간감쇠 점수 기반으로 샘플링한다.
val buckets = listOf(
@@ -350,7 +367,7 @@ class HomeService(
val batch = contentService.getLatestContentByTheme(
memberId = memberId,
theme = emptyList(),
contentType = contentType,
contentType = preference.contentType,
offset = bucket.offset,
limit = bucket.limit,
sortType = SortType.NEWEST,
@@ -374,6 +391,27 @@ class HomeService(
return result.take(RECOMMEND_TARGET_SIZE).shuffled()
}
private fun resolvePreference(
member: Member?,
isAdultContentVisible: Boolean?,
contentType: ContentType?
): ViewerContentPreference {
if (member == null) {
return ViewerContentPreference(
countryCode = "KR",
isAdultContentVisible = isAdultContentVisible ?: false,
contentType = contentType ?: ContentType.ALL,
isAdult = false
)
}
return memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}
private fun pickByTimeDecay(
batch: List<AudioContentMainItem>,
targetSize: Int,

View File

@@ -23,8 +23,8 @@ class LiveApiController(
) = run {
ApiResponse.ok(
service.fetchData(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType,
timezone = timezone,
member = member
)

View File

@@ -8,6 +8,8 @@ import kr.co.vividnext.sodalive.live.room.LiveRoomService
import kr.co.vividnext.sodalive.live.room.LiveRoomStatus
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.member.contentpreference.ViewerContentPreference
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
@@ -17,22 +19,24 @@ class LiveApiService(
private val contentService: AudioContentService,
private val recommendService: LiveRecommendService,
private val creatorCommunityService: CreatorCommunityService,
private val memberContentPreferenceService: MemberContentPreferenceService,
private val blockMemberRepository: BlockMemberRepository
) {
fun fetchData(
isAdultContentVisible: Boolean,
contentType: ContentType,
isAdultContentVisible: Boolean?,
contentType: ContentType?,
timezone: String,
member: Member?
): LiveMainResponse {
val preference = resolvePreference(member, isAdultContentVisible, contentType)
val memberId = member?.id
val isAdult = member?.auth != null && isAdultContentVisible
val isAdult = preference.isAdult
val liveOnAirRoomList = liveService.getRoomList(
dateString = null,
status = LiveRoomStatus.NOW,
isAdultContentVisible = isAdultContentVisible,
isAdultContentVisible = isAdult,
pageable = Pageable.ofSize(20),
member = member,
timezone = timezone
@@ -55,7 +59,7 @@ class LiveApiService(
val replayLive = contentService.getLatestContentByTheme(
memberId = memberId,
theme = listOf("다시듣기"),
contentType = contentType,
contentType = preference.contentType,
isFree = false,
isAdult = isAdult
)
@@ -77,7 +81,7 @@ class LiveApiService(
val liveReservationRoomList = liveService.getRoomList(
dateString = null,
status = LiveRoomStatus.RESERVATION,
isAdultContentVisible = isAdultContentVisible,
isAdultContentVisible = isAdult,
pageable = Pageable.ofSize(10),
member = member,
timezone = timezone
@@ -93,4 +97,25 @@ class LiveApiService(
liveReservationRoomList = liveReservationRoomList
)
}
private fun resolvePreference(
member: Member?,
isAdultContentVisible: Boolean?,
contentType: ContentType?
): ViewerContentPreference {
if (member == null) {
return ViewerContentPreference(
countryCode = "KR",
isAdultContentVisible = isAdultContentVisible ?: false,
contentType = contentType ?: ContentType.ALL,
isAdult = false
)
}
return memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}
}