콘텐츠 메인 다시듣기, ASMR

- 채널별 콘텐츠 조회 API 추가
This commit is contained in:
Klaus 2025-02-15 02:01:59 +09:00
parent 34452525d4
commit 3d1716d847
9 changed files with 236 additions and 6 deletions

View File

@ -0,0 +1,62 @@
package kr.co.vividnext.sodalive.content.main.tab
import com.querydsl.core.types.dsl.Expressions
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse
import kr.co.vividnext.sodalive.content.main.QContentCreatorResponse
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.QMember.member
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Repository
@Repository
class AudioContentMainTabRepository(
private val queryFactory: JPAQueryFactory,
@Value("\${cloud.aws.cloud-front.host}")
private val imageHost: String
) {
fun findCreatorByThemeContent(
memberId: Long,
theme: String,
minCount: Int
): List<ContentCreatorResponse> {
val blockMemberCondition = blockMember.member.id.eq(member.id)
.and(blockMember.isActive.isTrue)
.and(blockMember.blockedMember.id.eq(memberId))
val where = member.isActive.isTrue
.and(member.role.eq(MemberRole.CREATOR))
.and(audioContent.isActive.isTrue)
.and(audioContent.duration.isNotNull)
.and(audioContent.limited.isNull)
.and(audioContentTheme.isActive.isTrue)
.and(audioContentTheme.theme.eq(theme))
.and(blockMember.id.isNull)
return queryFactory
.select(
QContentCreatorResponse(
member.id,
member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
)
)
.from(member)
.innerJoin(audioContent).on(member.id.eq(audioContent.member.id))
.innerJoin(audioContent.theme, audioContentTheme)
.leftJoin(blockMember).on(blockMemberCondition)
.where(where)
.groupBy(member.id)
.having(audioContent.id.count().goe(minCount))
.orderBy(
Expressions.numberTemplate(Double::class.java, "function('rand')").asc()
)
.offset(0)
.limit(20)
.fetch()
}
}

View File

@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.member.Member
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@RestController @RestController
@ -19,4 +20,19 @@ class AudioContentMainTabAsmrController(private val service: AudioContentMainTab
ApiResponse.ok(service.fetchData(member)) ApiResponse.ok(service.fetchData(member))
} }
@GetMapping("/popular-content-by-creator")
fun getPopularContentByCreator(
@RequestParam creatorId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getPopularContentByCreator(
creatorId = creatorId,
isAdult = member.auth != null
)
)
}
} }

View File

@ -4,7 +4,9 @@ import kr.co.vividnext.sodalive.content.AudioContentRepository
import kr.co.vividnext.sodalive.content.ContentType import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository
import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTabRepository
import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse
import kr.co.vividnext.sodalive.event.EventService import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.rank.RankingService import kr.co.vividnext.sodalive.rank.RankingService
@ -15,6 +17,7 @@ import java.time.temporal.TemporalAdjusters
@Service @Service
class AudioContentMainTabAsmrService( class AudioContentMainTabAsmrService(
private val repository: AudioContentMainTabRepository,
private val bannerService: AudioContentBannerService, private val bannerService: AudioContentBannerService,
private val contentRepository: AudioContentRepository, private val contentRepository: AudioContentRepository,
private val rankingService: RankingService, private val rankingService: RankingService,
@ -59,6 +62,32 @@ class AudioContentMainTabAsmrService(
theme = theme theme = theme
) )
val creatorList = repository.findCreatorByThemeContent(
memberId = memberId,
theme = theme,
minCount = 4
)
val salesRankContentList = if (creatorList.isNotEmpty()) {
rankingService.fetchCreatorContentBySalesTop2(
creatorId = creatorList[0].creatorId,
isAdult = isAdult,
theme = theme
)
} else {
emptyList()
}
val salesCountRankContentList = if (creatorList.isNotEmpty()) {
rankingService.fetchCreatorContentBySalesCountTop2(
creatorId = creatorList[0].creatorId,
isAdult = isAdult,
theme = theme
)
} else {
emptyList()
}
val eventBannerList = eventService.getEventList(isAdult = isAdult) val eventBannerList = eventService.getEventList(isAdult = isAdult)
val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult)
@ -77,8 +106,31 @@ class AudioContentMainTabAsmrService(
contentBannerList = contentBannerList, contentBannerList = contentBannerList,
newAsmrContentList = newAsmrContentList, newAsmrContentList = newAsmrContentList,
rankAsmrContentList = rankAsmrContentList, rankAsmrContentList = rankAsmrContentList,
creatorList = creatorList,
salesRankContentList = salesRankContentList,
salesCountRankContentList = salesCountRankContentList,
eventBannerList = eventBannerList, eventBannerList = eventBannerList,
curationList = curationList curationList = curationList
) )
} }
fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse {
val theme = "ASMR"
val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2(
creatorId = creatorId,
isAdult = isAdult,
theme = theme
)
val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2(
creatorId = creatorId,
isAdult = isAdult,
theme = theme
)
return GetPopularContentByCreatorResponse(
salesRankContentList = salesRankContentList,
salesCountRankContentList = salesCountRankContentList
)
}
} }

View File

@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.content.main.tab.asmr package kr.co.vividnext.sodalive.content.main.tab.asmr
import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse
import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse
@ -11,6 +12,9 @@ data class GetContentMainTabAsmrResponse(
val contentBannerList: List<GetAudioContentBannerResponse>, val contentBannerList: List<GetAudioContentBannerResponse>,
val newAsmrContentList: List<GetAudioContentMainItem>, val newAsmrContentList: List<GetAudioContentMainItem>,
val rankAsmrContentList: List<GetAudioContentRankingItem>, val rankAsmrContentList: List<GetAudioContentRankingItem>,
val creatorList: List<ContentCreatorResponse>,
val salesRankContentList: List<GetAudioContentRankingItem>,
val salesCountRankContentList: List<GetAudioContentRankingItem>,
val eventBannerList: GetEventResponse, val eventBannerList: GetEventResponse,
val curationList: List<GetContentCurationResponse> val curationList: List<GetContentCurationResponse>
) )

View File

@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.member.Member
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@RestController @RestController
@ -19,4 +20,19 @@ class AudioContentMainTabLiveReplayController(private val service: AudioContentM
ApiResponse.ok(service.fetchData(member)) ApiResponse.ok(service.fetchData(member))
} }
@GetMapping("/popular-content-by-creator")
fun getPopularContentByCreator(
@RequestParam creatorId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getPopularContentByCreator(
creatorId = creatorId,
isAdult = member.auth != null
)
)
}
} }

View File

@ -4,7 +4,9 @@ import kr.co.vividnext.sodalive.content.AudioContentRepository
import kr.co.vividnext.sodalive.content.ContentType import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository
import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTabRepository
import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse
import kr.co.vividnext.sodalive.event.EventService import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.rank.RankingService import kr.co.vividnext.sodalive.rank.RankingService
@ -15,6 +17,7 @@ import java.time.temporal.TemporalAdjusters
@Service @Service
class AudioContentMainTabLiveReplayService( class AudioContentMainTabLiveReplayService(
private val repository: AudioContentMainTabRepository,
private val bannerService: AudioContentBannerService, private val bannerService: AudioContentBannerService,
private val contentRepository: AudioContentRepository, private val contentRepository: AudioContentRepository,
private val rankingService: RankingService, private val rankingService: RankingService,
@ -59,6 +62,32 @@ class AudioContentMainTabLiveReplayService(
theme = theme theme = theme
) )
val creatorList = repository.findCreatorByThemeContent(
memberId = memberId,
theme = theme,
minCount = 4
)
val salesRankContentList = if (creatorList.isNotEmpty()) {
rankingService.fetchCreatorContentBySalesTop2(
creatorId = creatorList[0].creatorId,
isAdult = isAdult,
theme = theme
)
} else {
emptyList()
}
val salesCountRankContentList = if (creatorList.isNotEmpty()) {
rankingService.fetchCreatorContentBySalesCountTop2(
creatorId = creatorList[0].creatorId,
isAdult = isAdult,
theme = theme
)
} else {
emptyList()
}
val eventBannerList = eventService.getEventList(isAdult = isAdult) val eventBannerList = eventService.getEventList(isAdult = isAdult)
val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult)
@ -77,8 +106,31 @@ class AudioContentMainTabLiveReplayService(
contentBannerList = contentBannerList, contentBannerList = contentBannerList,
newLiveReplayContentList = newLiveReplayContentList, newLiveReplayContentList = newLiveReplayContentList,
rankLiveReplayContentList = rankLiveReplayContentList, rankLiveReplayContentList = rankLiveReplayContentList,
creatorList = creatorList,
salesRankContentList = salesRankContentList,
salesCountRankContentList = salesCountRankContentList,
eventBannerList = eventBannerList, eventBannerList = eventBannerList,
curationList = curationList curationList = curationList
) )
} }
fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse {
val theme = "다시듣기"
val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2(
creatorId = creatorId,
isAdult = isAdult,
theme = theme
)
val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2(
creatorId = creatorId,
isAdult = isAdult,
theme = theme
)
return GetPopularContentByCreatorResponse(
salesRankContentList = salesRankContentList,
salesCountRankContentList = salesCountRankContentList
)
}
} }

View File

@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.content.main.tab.replay package kr.co.vividnext.sodalive.content.main.tab.replay
import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse
import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse
@ -11,6 +12,9 @@ data class GetContentMainTabLiveReplayResponse(
val contentBannerList: List<GetAudioContentBannerResponse>, val contentBannerList: List<GetAudioContentBannerResponse>,
val newLiveReplayContentList: List<GetAudioContentMainItem>, val newLiveReplayContentList: List<GetAudioContentMainItem>,
val rankLiveReplayContentList: List<GetAudioContentRankingItem>, val rankLiveReplayContentList: List<GetAudioContentRankingItem>,
val creatorList: List<ContentCreatorResponse>,
val salesRankContentList: List<GetAudioContentRankingItem>,
val salesCountRankContentList: List<GetAudioContentRankingItem>,
val eventBannerList: GetEventResponse, val eventBannerList: GetEventResponse,
val curationList: List<GetContentCurationResponse> val curationList: List<GetContentCurationResponse>
) )

View File

@ -381,7 +381,11 @@ class RankingRepository(
.fetch() .fetch()
} }
fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { fun fetchCreatorContentBySalesTop2(
creatorId: Long,
isAdult: Boolean,
theme: String
): List<GetAudioContentRankingItem> {
var where = member.isActive.isTrue var where = member.isActive.isTrue
.and(member.role.eq(MemberRole.CREATOR)) .and(member.role.eq(MemberRole.CREATOR))
.and(audioContent.isActive.isTrue) .and(audioContent.isActive.isTrue)
@ -395,6 +399,10 @@ class RankingRepository(
where = where.and(series.isAdult.isFalse) where = where.and(series.isAdult.isFalse)
} }
if (theme.isNotBlank()) {
where = where.and(audioContentTheme.theme.eq(theme))
}
return queryFactory return queryFactory
.select( .select(
QGetAudioContentRankingItem( QGetAudioContentRankingItem(
@ -421,7 +429,11 @@ class RankingRepository(
.fetch() .fetch()
} }
fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { fun fetchCreatorContentBySalesCountTop2(
creatorId: Long,
isAdult: Boolean,
theme: String
): List<GetAudioContentRankingItem> {
var where = member.isActive.isTrue var where = member.isActive.isTrue
.and(member.role.eq(MemberRole.CREATOR)) .and(member.role.eq(MemberRole.CREATOR))
.and(audioContent.isActive.isTrue) .and(audioContent.isActive.isTrue)
@ -435,6 +447,10 @@ class RankingRepository(
where = where.and(series.isAdult.isFalse) where = where.and(series.isAdult.isFalse)
} }
if (theme.isNotBlank()) {
where = where.and(audioContentTheme.theme.eq(theme))
}
return queryFactory return queryFactory
.select( .select(
QGetAudioContentRankingItem( QGetAudioContentRankingItem(

View File

@ -189,12 +189,20 @@ class RankingService(
return repository.fetchCreatorByContentRevenueRankTop20(memberId, startDate, endDate) return repository.fetchCreatorByContentRevenueRankTop20(memberId, startDate, endDate)
} }
fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { fun fetchCreatorContentBySalesTop2(
return repository.fetchCreatorContentBySalesTop2(creatorId, isAdult) creatorId: Long,
isAdult: Boolean,
theme: String = ""
): List<GetAudioContentRankingItem> {
return repository.fetchCreatorContentBySalesTop2(creatorId, isAdult, theme)
} }
fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { fun fetchCreatorContentBySalesCountTop2(
return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult) creatorId: Long,
isAdult: Boolean,
theme: String = ""
): List<GetAudioContentRankingItem> {
return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult, theme)
} }
fun fetchCreatorBySeriesRevenueRankTop20( fun fetchCreatorBySeriesRevenueRankTop20(