diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt index 015324c..ef18036 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt @@ -10,6 +10,9 @@ import org.springframework.data.redis.connection.RedisConnectionFactory import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory import org.springframework.data.redis.core.RedisTemplate import org.springframework.data.redis.repository.configuration.EnableRedisRepositories +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer +import org.springframework.data.redis.serializer.RedisSerializationContext +import org.springframework.data.redis.serializer.StringRedisSerializer import java.time.Duration @Configuration @@ -37,12 +40,30 @@ class RedisConfig( fun cacheManager(redisConnectionFactory: RedisConnectionFactory): RedisCacheManager { val defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer())) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + GenericJackson2JsonRedisSerializer() + ) + ) val cacheConfigMap = mutableMapOf() cacheConfigMap["default"] = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer())) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + GenericJackson2JsonRedisSerializer() + ) + ) cacheConfigMap["cache_ttl_3_days"] = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofDays(3)) + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer())) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + GenericJackson2JsonRedisSerializer() + ) + ) return RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(defaultCacheConfig) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentController.kt index 286f232..537f560 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentController.kt @@ -19,6 +19,9 @@ import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RequestPart import org.springframework.web.bind.annotation.RestController import org.springframework.web.multipart.MultipartFile +import java.time.DayOfWeek +import java.time.LocalDateTime +import java.time.temporal.TemporalAdjusters @RestController @RequestMapping("/audio-content") @@ -149,18 +152,41 @@ class AudioContentController(private val service: AudioContentService) { ApiResponse.ok(service.audioContentLike(request, member)) } + @GetMapping("/ranking-sort-type") + fun getAudioContentRankingSort( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.getContentRankingSortTypeList()) + } + @GetMapping("/ranking") fun getAudioContentRanking( + @RequestParam("sort-type", required = false) sortType: String? = "매출", @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, pageable: Pageable ) = run { if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + val currentDateTime = LocalDateTime.now() + val startDate = currentDateTime + .withHour(15) + .withMinute(0) + .withSecond(0) + .minusWeeks(1) + .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) + val endDate = startDate + .plusDays(7) + ApiResponse.ok( service.getAudioContentRanking( - member = member, + isAdult = member.auth != null, + startDate = startDate, + endDate = endDate, offset = pageable.offset, - limit = pageable.pageSize.toLong() + limit = pageable.pageSize.toLong(), + sortType = sortType ?: "매출" ) ) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt index e7bfebe..3b9eb84 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt @@ -2,8 +2,11 @@ package kr.co.vividnext.sodalive.content import com.querydsl.core.types.dsl.Expressions import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.can.use.QUseCan.useCan import kr.co.vividnext.sodalive.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.QBundleAudioContent.bundleAudioContent +import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment +import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem import kr.co.vividnext.sodalive.content.main.GetNewContentUploadCreator @@ -88,7 +91,8 @@ interface AudioContentQueryRepository { startDate: LocalDateTime, endDate: LocalDateTime, offset: Long = 0, - limit: Long = 12 + limit: Long = 12, + sortType: String = "매출" ): List } @@ -443,11 +447,11 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) startDate: LocalDateTime, endDate: LocalDateTime, offset: Long, - limit: Long + limit: Long, + sortType: String ): List { - var where = order.createdAt.goe(startDate) - .and(order.createdAt.lt(endDate)) - .and(audioContent.isActive.isTrue) + var where = audioContent.isActive.isTrue + .and(audioContent.member.id.ne(648)) .and(audioContent.member.isNotNull) .and(audioContent.duration.isNotNull) .and(audioContent.member.isActive.isTrue) @@ -457,7 +461,7 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) where = where.and(audioContent.isAdult.isFalse) } - return queryFactory + var select = queryFactory .select( QGetAudioContentRankingItem( audioContent.id, @@ -470,13 +474,70 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) member.nickname ) ) - .from(order) - .innerJoin(order.audioContent, audioContent) - .innerJoin(audioContent.member, member) - .innerJoin(audioContent.theme, audioContentTheme) - .where(where) - .groupBy(audioContent.id) - .orderBy(order.can.sum().desc(), audioContent.createdAt.asc()) + + select = when (sortType) { + "후원" -> { + select + .from(useCan) + .innerJoin(useCan.audioContent, audioContent) + .innerJoin(audioContent.member, member) + .innerJoin(audioContent.theme, audioContentTheme) + .where( + where + .and(useCan.createdAt.goe(startDate)) + .and(useCan.createdAt.lt(endDate)) + ) + .groupBy(audioContent.id) + .orderBy(useCan.can.add(useCan.rewardCan).sum().desc(), audioContent.createdAt.asc()) + } + + "댓글" -> { + select + .from(audioContentComment) + .innerJoin(audioContentComment.audioContent, audioContent) + .innerJoin(audioContentComment.audioContent.member, member) + .innerJoin(audioContentComment.audioContent.theme, audioContentTheme) + .where( + where + .and(audioContentComment.createdAt.goe(startDate)) + .and(audioContentComment.createdAt.lt(endDate)) + ) + .groupBy(audioContentComment.audioContent.id) + .orderBy(audioContentComment.id.count().desc(), audioContent.createdAt.asc()) + } + + "좋아요" -> { + select + .from(audioContentLike) + .innerJoin(audioContentLike.audioContent, audioContent) + .innerJoin(audioContentLike.audioContent.member, member) + .innerJoin(audioContentLike.audioContent.theme, audioContentTheme) + .where( + where + .and(audioContentLike.createdAt.goe(startDate)) + .and(audioContentLike.createdAt.lt(endDate)) + ) + .groupBy(audioContentLike.audioContent.id) + .orderBy(audioContentLike.id.count().desc(), audioContent.createdAt.asc()) + } + + else -> { + select + .from(order) + .innerJoin(order.audioContent, audioContent) + .innerJoin(audioContent.member, member) + .innerJoin(audioContent.theme, audioContentTheme) + .where( + where + .and(order.createdAt.goe(startDate)) + .and(order.createdAt.lt(endDate)) + ) + .groupBy(audioContent.id) + .orderBy(order.can.sum().desc(), audioContent.createdAt.asc()) + } + } + + return select .offset(offset) .limit(limit) .fetch() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt index ea50196..6d12fe6 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt @@ -24,17 +24,16 @@ import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.block.BlockMemberRepository import kr.co.vividnext.sodalive.utils.generateFileName import org.springframework.beans.factory.annotation.Value +import org.springframework.cache.annotation.Cacheable import org.springframework.context.ApplicationEventPublisher import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile import java.text.SimpleDateFormat -import java.time.DayOfWeek import java.time.LocalDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter -import java.time.temporal.TemporalAdjusters import java.util.Locale @Service @@ -72,11 +71,10 @@ class AudioContentService( ) if (audioContentLike == null) { - audioContentLike = AudioContentLike( - memberId = member.id!!, - contentId = request.contentId - ) + audioContentLike = AudioContentLike(memberId = member.id!!) + val audioContent = repository.findByIdAndActive(request.contentId) + audioContentLike.audioContent = audioContent audioContentLikeRepository.save(audioContentLike) } else { audioContentLike.isActive = !audioContentLike.isActive @@ -579,21 +577,19 @@ class AudioContentService( } } + @Cacheable( + cacheNames = ["cache_ttl_3_days"], + key = "'contentRanking:' + ':' +" + + "#isAdult + ':' + #startDate + ':' + #endDate + ':' + #sortType + ':' + #offset + ':' + #limit" + ) fun getAudioContentRanking( - member: Member, + isAdult: Boolean, + startDate: LocalDateTime, + endDate: LocalDateTime, offset: Long, - limit: Long + limit: Long, + sortType: String = "매출" ): GetAudioContentRanking { - val currentDateTime = LocalDateTime.now() - val startDate = currentDateTime - .withHour(15) - .withMinute(0) - .withSecond(0) - .minusWeeks(1) - .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) - val endDate = startDate - .plusDays(7) - val startDateFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일") val endDateFormatter = DateTimeFormatter.ofPattern("MM월 dd일") @@ -602,9 +598,10 @@ class AudioContentService( cloudfrontHost = coverImageHost, startDate = startDate.minusDays(1), endDate = endDate.minusDays(1), - isAdult = member.auth != null, + isAdult = isAdult, offset = offset, - limit = limit + limit = limit, + sortType = sortType ) return GetAudioContentRanking( @@ -613,4 +610,8 @@ class AudioContentService( items = contentRankingItemList ) } + + fun getContentRankingSortTypeList(): List { + return listOf("매출", "후원", "댓글", "좋아요") + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/like/AudioContentLike.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/like/AudioContentLike.kt index 5b9b16a..d54c5dd 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/like/AudioContentLike.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/like/AudioContentLike.kt @@ -1,20 +1,21 @@ package kr.co.vividnext.sodalive.content.like +import kr.co.vividnext.sodalive.content.AudioContent import java.time.LocalDateTime import javax.persistence.Entity +import javax.persistence.FetchType import javax.persistence.GeneratedValue import javax.persistence.GenerationType import javax.persistence.Id +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne import javax.persistence.PrePersist import javax.persistence.PreUpdate import javax.persistence.Table @Entity @Table(name = "content_like") -data class AudioContentLike( - val memberId: Long, - val contentId: Long -) { +data class AudioContentLike(val memberId: Long) { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null @@ -34,4 +35,8 @@ data class AudioContentLike( } var isActive = true + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "content_id", nullable = false) + var audioContent: AudioContent? = null } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/like/AudioContentLikeRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/like/AudioContentLikeRepository.kt index db8b1ad..72e9310 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/like/AudioContentLikeRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/like/AudioContentLikeRepository.kt @@ -20,7 +20,7 @@ class AudioContentLikeQueryRepositoryImpl(private val queryFactory: JPAQueryFact .selectFrom(audioContentLike) .where( audioContentLike.memberId.eq(memberId) - .and(audioContentLike.contentId.eq(contentId)) + .and(audioContentLike.audioContent.id.eq(contentId)) ) .fetchFirst() } @@ -30,7 +30,7 @@ class AudioContentLikeQueryRepositoryImpl(private val queryFactory: JPAQueryFact .select(audioContentLike.id) .from(audioContentLike) .where( - audioContentLike.contentId.eq(contentId) + audioContentLike.audioContent.id.eq(contentId) .and(audioContentLike.isActive.isTrue) ) .fetch() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt index c2d80d0..4b3fa20 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.content.main import kr.co.vividnext.sodalive.content.AudioContentRepository +import kr.co.vividnext.sodalive.content.AudioContentService import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse import kr.co.vividnext.sodalive.content.main.curation.GetAudioContentCurationResponse @@ -15,12 +16,12 @@ import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import java.time.DayOfWeek import java.time.LocalDateTime -import java.time.format.DateTimeFormatter import java.time.temporal.TemporalAdjusters @Service class AudioContentMainService( private val repository: AudioContentRepository, + private val audioContentService: AudioContentService, private val blockMemberRepository: BlockMemberRepository, private val orderService: OrderService, private val audioContentThemeRepository: AudioContentThemeQueryRepository, @@ -66,7 +67,14 @@ class AudioContentMainService( .withSecond(0) val endDate = startDate.plusDays(7) - val contentRanking = getContentRanking(isAdult = isAdult, startDate = startDate, endDate = endDate) + val contentRankingSortTypeList = audioContentService.getContentRankingSortTypeList() + val contentRanking = audioContentService.getAudioContentRanking( + isAdult = isAdult, + startDate = startDate, + endDate = endDate, + offset = 0, + limit = 12 + ) return GetAudioContentMainResponse( newContentUploadCreatorList = newContentUploadCreatorList, @@ -75,6 +83,7 @@ class AudioContentMainService( themeList = themeList, newContentList = newContentList, curationList = curationList, + contentRankingSortTypeList = contentRankingSortTypeList, contentRanking = contentRanking ) } @@ -109,7 +118,7 @@ class AudioContentMainService( return GetNewContentAllResponse(totalCount, items) } - @Cacheable(cacheNames = ["default"], key = "'getNewContentUploadCreatorList:' + #memberId + ':' + #isAdult") + @Cacheable(cacheNames = ["default"], key = "'newContentUploadCreatorList:' + #memberId + ':' + #isAdult") fun getNewContentUploadCreatorList(memberId: Long, isAdult: Boolean): List { return repository.getNewContentUploadCreatorList( cloudfrontHost = imageHost, @@ -120,7 +129,7 @@ class AudioContentMainService( .toList() } - @Cacheable(cacheNames = ["default"], key = "'getAudioContentMainBannerList:' + #memberId + ':' + #isAdult") + @Cacheable(cacheNames = ["default"], key = "'contentMainBannerList:' + #memberId + ':' + #isAdult") fun getAudioContentMainBannerList(memberId: Long, isAdult: Boolean) = repository.getAudioContentMainBannerList(isAdult = isAdult) .asSequence() @@ -169,7 +178,7 @@ class AudioContentMainService( } .toList() - @Cacheable(cacheNames = ["default"], key = "'getAudioContentCurationList:' + #memberId + ':' + #isAdult") + @Cacheable(cacheNames = ["default"], key = "'contentCurationList:' + #memberId + ':' + #isAdult") fun getAudioContentCurationList(memberId: Long, isAdult: Boolean) = repository.getAudioContentCurations(isAdult = isAdult) .asSequence() @@ -192,27 +201,4 @@ class AudioContentMainService( } .filter { it.contents.isNotEmpty() } .toList() - - @Cacheable( - cacheNames = ["cache_ttl_3_days"], - key = "'getAudioContentCurationList:' + ':' + #isAdult + ':' + #startDate + ':' + #endDate" - ) - fun getContentRanking(isAdult: Boolean, startDate: LocalDateTime, endDate: LocalDateTime): GetAudioContentRanking { - val startDateFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일") - val endDateFormatter = DateTimeFormatter.ofPattern("MM월 dd일") - - val contentRankingItemList = repository - .getAudioContentRanking( - cloudfrontHost = imageHost, - startDate = startDate.minusDays(1), - endDate = endDate.minusDays(1), - isAdult = isAdult - ) - - return GetAudioContentRanking( - startDate = startDate.format(startDateFormatter), - endDate = endDate.minusDays(1).format(endDateFormatter), - contentRankingItemList - ) - } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetAudioContentMainResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetAudioContentMainResponse.kt index 9734f21..1cd184c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetAudioContentMainResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetAudioContentMainResponse.kt @@ -10,5 +10,6 @@ data class GetAudioContentMainResponse( val themeList: List, val newContentList: List, val curationList: List, + val contentRankingSortTypeList: List, val contentRanking: GetAudioContentRanking ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetAudioContentRanking.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetAudioContentRanking.kt index 3209e71..df6ab26 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetAudioContentRanking.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetAudioContentRanking.kt @@ -4,9 +4,9 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.querydsl.core.annotations.QueryProjection data class GetAudioContentRanking( - val startDate: String, - val endDate: String, - val items: List + @JsonProperty("startDate") val startDate: String, + @JsonProperty("endDate") val endDate: String, + @JsonProperty("items") val items: List ) data class GetAudioContentRankingItem @QueryProjection constructor( diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/theme/AudioContentThemeQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/theme/AudioContentThemeQueryRepository.kt index bf7bfc1..8b220b2 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/theme/AudioContentThemeQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/theme/AudioContentThemeQueryRepository.kt @@ -28,7 +28,7 @@ class AudioContentThemeQueryRepository( .fetch() } - @Cacheable(cacheNames = ["default"], key = "'getActiveThemeOfContent:' + ':' + #isAdult") + @Cacheable(cacheNames = ["default"], key = "'activeThemeOfContent:' + ':' + #isAdult") fun getActiveThemeOfContent(isAdult: Boolean = false): List { var where = audioContent.isActive.isTrue .and(audioContentTheme.isActive.isTrue)