Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature/ROFO-184] 종합랭킹 #79

Merged
merged 22 commits into from
Sep 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
37ccaf9
[feature/ROFO-184] 종합랭킹
hyunjungkimm Sep 10, 2024
eea520d
[feature/ROFO-184] 종합랭킹 - 변수명 변경
hyunjungkimm Sep 10, 2024
9897639
[feature/ROFO-184] conflict 수정
hyunjungkimm Sep 10, 2024
c65a897
[feature/ROFO-184] 쿼리 수정
hyunjungkimm Sep 10, 2024
047abbe
[feature/ROFO-184] 쿼리 타입 수정
hyunjungkimm Sep 10, 2024
e76ec61
[feature/ROFO-184] 쿼리 타입 원복
hyunjungkimm Sep 10, 2024
7a88792
[feature/ROFO-184] github action 확인용
hyunjungkimm Sep 10, 2024
cd4c479
[feature/ROFO-184] github action 확인용 - 수정
hyunjungkimm Sep 10, 2024
a7edde6
[feature/ROFO-184] github action 확인용 - 수정
hyunjungkimm Sep 10, 2024
cba2ff6
[feature/ROFO-184] github action 확인용 - 수정
hyunjungkimm Sep 10, 2024
3932579
[feature/ROFO-184] github action 확인용 - 수정
hyunjungkimm Sep 10, 2024
f81c0b7
[feature/ROFO-184] github action 확인용 - 수정
hyunjungkimm Sep 10, 2024
48a5de7
[feature/ROFO-184] github action 확인용 - 수정
hyunjungkimm Sep 10, 2024
0b7efdd
[feature/ROFO-184] github action 확인용 - 원복, 수정
hyunjungkimm Sep 10, 2024
7cbc8e3
[feature/ROFO-184] github action 확인용
hyunjungkimm Sep 10, 2024
fc5366f
[feature/ROFO-184] github action 확인용
hyunjungkimm Sep 10, 2024
5dc806f
[feature/ROFO-184] github action 확인용
hyunjungkimm Sep 10, 2024
8728761
[feature/ROFO-184] github action 확인용
hyunjungkimm Sep 12, 2024
2046c64
[feature/ROFO-184] github action 확인용
hyunjungkimm Sep 12, 2024
c2f0492
[feature/ROFO-184] 쿼리수정
hyunjungkimm Sep 12, 2024
e750c18
[feature/ROFO-184] 리뷰 반영
hyunjungkimm Sep 14, 2024
2934cd7
[feature/ROFO-184] 쿼리수정
hyunjungkimm Sep 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import kr.weit.roadyfoody.ranking.utils.REPORT_RANKING_KEY
import kr.weit.roadyfoody.ranking.utils.REPORT_RANKING_UPDATE_LOCK
import kr.weit.roadyfoody.ranking.utils.REVIEW_RANKING_KEY
import kr.weit.roadyfoody.ranking.utils.REVIEW_RANKING_UPDATE_LOCK
import kr.weit.roadyfoody.ranking.utils.TOTAL_RANKING_KEY
import kr.weit.roadyfoody.ranking.utils.TOTAL_RANKING_UPDATE_LOCK
import kr.weit.roadyfoody.review.repository.FoodSpotsReviewRepository
import org.redisson.api.RLock
import org.redisson.api.RedissonClient
Expand Down Expand Up @@ -54,6 +56,16 @@ class RankingCommandService(
)
}

@Async("asyncTask")
@Scheduled(cron = "0 0 5 * * *")
fun updateTotalRanking() {
updateRanking(
lockName = TOTAL_RANKING_UPDATE_LOCK,
key = TOTAL_RANKING_KEY,
dataProvider = reviewRepository::findAllUserTotalCount,
)
}

fun updateRanking(
lockName: String,
key: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@ import kr.weit.roadyfoody.ranking.utils.REPORT_RANKING_KEY
import kr.weit.roadyfoody.ranking.utils.REPORT_RANKING_UPDATE_LOCK
import kr.weit.roadyfoody.ranking.utils.REVIEW_RANKING_KEY
import kr.weit.roadyfoody.ranking.utils.REVIEW_RANKING_UPDATE_LOCK
import kr.weit.roadyfoody.ranking.utils.TOTAL_RANKING_KEY
import kr.weit.roadyfoody.ranking.utils.TOTAL_RANKING_UPDATE_LOCK
import kr.weit.roadyfoody.review.repository.FoodSpotsReviewRepository
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Service
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutorService

@Service
class RankingQueryService(
private val redisTemplate: RedisTemplate<String, String>,
private val foodSpotsHistoryRepository: FoodSpotsHistoryRepository,
private val reviewRepository: FoodSpotsReviewRepository,
private val rankingCommandService: RankingCommandService,
private val executor: ExecutorService,
) {
fun getReportRanking(size: Long): List<UserRanking> =
getRanking(
Expand All @@ -45,6 +49,14 @@ class RankingQueryService(
dataProvider = reviewRepository::findAllUserLikeCount,
)

fun getTotalRanking(size: Long): List<UserRanking> =
getRanking(
lockName = TOTAL_RANKING_UPDATE_LOCK,
size = size,
key = TOTAL_RANKING_KEY,
dataProvider = reviewRepository::findAllUserTotalCount,
)

private fun getRanking(
lockName: String,
size: Long,
Expand All @@ -57,13 +69,13 @@ class RankingQueryService(
.range(key, 0, size - 1)

if (ranking.isNullOrEmpty()) {
CompletableFuture.runAsync {
CompletableFuture.runAsync({
rankingCommandService.updateRanking(
lockName = lockName,
key = key,
dataProvider = dataProvider,
)
}
}, executor)
throw RankingNotFoundException()
}
return ranking.map { score ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,11 @@ class RankingController(
@RequestParam(defaultValue = "10")
size: Long,
): List<UserRanking> = rankingQueryService.getLikeRanking(size)

@GetMapping("total")
override fun getTotalRanking(
@Positive(message = "size는 양수여야 합니다")
@RequestParam(defaultValue = "10")
size: Long,
): List<UserRanking> = rankingQueryService.getTotalRanking(size)
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,33 @@ interface RankingControllerSpec {
@RequestParam(defaultValue = "10")
size: Long,
): List<UserRanking>

@ApiErrorCodeExamples(
[
ErrorCode.SIZE_NON_POSITIVE,
],
)
@Operation(
description = "종합 랭킹 조회 API",
responses = [
ApiResponse(
responseCode = "200",
description = "종합 랭킹 조회 성공",
content = [
Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema =
Schema(
implementation = UserRanking::class,
),
),
],
),
],
)
fun getTotalRanking(
@Positive(message = "size는 양수여야 합니다")
@RequestParam(defaultValue = "10")
size: Long,
): List<UserRanking>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package kr.weit.roadyfoody.ranking.utils
const val REPORT_RANKING_KEY = "rofo:user-report-ranking"
const val REVIEW_RANKING_KEY = "rofo:user-review-ranking"
const val LIKE_RANKING_KEY = "rofo:user-like-ranking"
const val TOTAL_RANKING_KEY = "rofo:user-total-ranking"
const val REPORT_RANKING_UPDATE_LOCK = "updateReportRankingLock"
const val REVIEW_RANKING_UPDATE_LOCK = "updateReviewRankingLock"
const val LIKE_RANKING_UPDATE_LOCK = "updateLikeRankingLock"
const val TOTAL_RANKING_UPDATE_LOCK = "totalLikeRankingLock"
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package kr.weit.roadyfoody.review.repository

import com.linecorp.kotlinjdsl.dsl.jpql.Jpql
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions
import com.linecorp.kotlinjdsl.querymodel.jpql.predicate.Predicate
import com.linecorp.kotlinjdsl.querymodel.jpql.sort.Sortable
import com.linecorp.kotlinjdsl.support.spring.data.jpa.repository.KotlinJdslJpqlExecutor
import kr.weit.roadyfoody.badge.domain.Badge
import kr.weit.roadyfoody.foodSpots.application.dto.CountRate
import kr.weit.roadyfoody.foodSpots.application.dto.ReviewAggregatedInfoResponse
import kr.weit.roadyfoody.foodSpots.domain.FoodSpots
import kr.weit.roadyfoody.foodSpots.domain.FoodSpotsHistory
import kr.weit.roadyfoody.global.utils.findList
import kr.weit.roadyfoody.global.utils.findMutableList
import kr.weit.roadyfoody.global.utils.getSlice
Expand Down Expand Up @@ -55,6 +57,8 @@ interface CustomFoodSpotsReviewRepository {
fun findAllUserLikeCount(): List<UserRanking>

fun getRatingCount(foodSpotsId: Long): List<CountRate>

fun findAllUserTotalCount(): List<UserRanking>
}

class CustomFoodSpotsReviewRepositoryImpl(
Expand Down Expand Up @@ -184,6 +188,72 @@ class CustomFoodSpotsReviewRepositoryImpl(
)
}

override fun findAllUserTotalCount(): List<UserRanking> =
hyunjungkimm marked this conversation as resolved.
Show resolved Hide resolved
kotlinJdslJpqlExecutor
.findList {
val foodSpotsReview = entity(FoodSpotsReview::class, "foodSpotsReview")
val foodSpotsHistory = entity(FoodSpotsHistory::class, "foodSpotsHistory")
val reviewLike = entity(ReviewLike::class, "reviewLike")

val subquery =
select<Long>(
coalesce(
count(
foodSpotsReview(FoodSpotsReview::id),
).plus(sum(foodSpotsReview(FoodSpotsReview::likeTotal))),
0,
),
).from(
foodSpotsReview,
).where(foodSpotsReview(FoodSpotsReview::user)(User::id).eq(entity(User::class)(User::id)))
.asSubquery()

val subquery2 =
select<Long>(
coalesce(count(foodSpotsHistory(FoodSpotsHistory::id)), 0),
).from(
foodSpotsHistory,
).where(foodSpotsHistory(FoodSpotsHistory::user)(User::id).eq(entity(User::class)(User::id)))
.asSubquery()

val defaultDate = LocalDateTime.parse("1970-12-31T00:00:00")

val maxReviewDate = coalesce(max(foodSpotsReview(FoodSpotsReview::createdDateTime)), defaultDate)
val maxHistoryDate = coalesce(max(foodSpotsHistory(FoodSpotsHistory::createdDateTime)), defaultDate)
val maxLikeDate = coalesce(max(reviewLike(ReviewLike::createdDateTime)), defaultDate)

val greatestDateExpression =
Expressions.customExpression(
LocalDateTime::class,
"GREATEST({0}, {1}, {2})",
listOf(
maxReviewDate,
maxHistoryDate,
maxLikeDate,
),
)
val total = expression(Long::class, "total")
selectNew<UserRanking>(
path(User::profile)(Profile::nickname),
subquery2.plus(subquery).`as`(total),
hyunjungkimm marked this conversation as resolved.
Show resolved Hide resolved
).from(
entity(User::class),
leftJoin(foodSpotsHistory).on(foodSpotsHistory(FoodSpotsHistory::user)(User::id).eq(path(User::id))),
leftJoin(
foodSpotsReview,
).on(foodSpotsReview(FoodSpotsReview::user)(User::id).eq(path(User::id))),
leftJoin(
reviewLike,
).on(reviewLike(ReviewLike::review)(FoodSpotsReview::id).eq(foodSpotsReview(FoodSpotsReview::id))),
).groupBy(
path(User::id),
path(User::profile)(Profile::nickname),
).orderBy(
total.desc(),
greatestDateExpression.asc(),
)
}

private fun Jpql.dynamicOrder(sortType: ReviewSortType): Array<Sortable> =
when (sortType) {
ReviewSortType.LATEST -> arrayOf(path(FoodSpotsReview::id).desc())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import io.mockk.mockk
import io.mockk.verify
import kr.weit.roadyfoody.foodSpots.repository.FoodSpotsHistoryRepository
import kr.weit.roadyfoody.ranking.fixture.createUserRankingResponse
import kr.weit.roadyfoody.ranking.utils.LIKE_RANKING_KEY
import kr.weit.roadyfoody.ranking.utils.REPORT_RANKING_KEY
import kr.weit.roadyfoody.ranking.utils.REVIEW_RANKING_KEY
import kr.weit.roadyfoody.ranking.utils.TOTAL_RANKING_KEY
import kr.weit.roadyfoody.review.repository.FoodSpotsReviewRepository
import org.redisson.api.RLock
import org.redisson.api.RedissonClient
Expand Down Expand Up @@ -39,7 +43,7 @@ class RankingCommandServiceTest :
`when`("Lock을 획득한 경우") {
every { lock.tryLock(0, 10, TimeUnit.MINUTES) } returns true

every { redisTemplate.delete("rofo:user-report-ranking") } returns true
every { redisTemplate.delete(REPORT_RANKING_KEY) } returns true
every { foodSpotsHistoryRepository.findAllUserReportCount() } returns createUserRankingResponse()
every { redisTemplate.opsForList() } returns list
every { list.rightPushAll(any(), any<List<String>>()) } returns 1L
Expand Down Expand Up @@ -67,7 +71,7 @@ class RankingCommandServiceTest :
`when`("Lock을 획득한 경우") {
every { lock.tryLock(0, 10, TimeUnit.MINUTES) } returns true

every { redisTemplate.delete("rofo:user-review-ranking") } returns true
every { redisTemplate.delete(REVIEW_RANKING_KEY) } returns true
every { reviewRepository.findAllUserReviewCount() } returns createUserRankingResponse()
every { redisTemplate.opsForList() } returns list
every { list.rightPushAll(any(), any<List<String>>()) } returns 1L
Expand Down Expand Up @@ -95,7 +99,7 @@ class RankingCommandServiceTest :
`when`("Lock을 획득한 경우") {
every { lock.tryLock(0, 10, TimeUnit.MINUTES) } returns true

every { redisTemplate.delete("rofo:user-like-ranking") } returns true
every { redisTemplate.delete(LIKE_RANKING_KEY) } returns true
every { reviewRepository.findAllUserLikeCount() } returns createUserRankingResponse()
every { redisTemplate.opsForList() } returns list
every { list.rightPushAll(any(), any<List<String>>()) } returns 1L
Expand All @@ -115,5 +119,33 @@ class RankingCommandServiceTest :
}
}
}

given("updateTotalRanking 테스트") {
every { redissonClient.getLock(any<String>()) } returns lock
afterEach { clearMocks(reviewRepository) }

`when`("Lock을 획득한 경우") {
every { lock.tryLock(0, 10, TimeUnit.MINUTES) } returns true

every { redisTemplate.delete(TOTAL_RANKING_KEY) } returns true
every { reviewRepository.findAllUserTotalCount() } returns createUserRankingResponse()
hyunjungkimm marked this conversation as resolved.
Show resolved Hide resolved
every { redisTemplate.opsForList() } returns list
every { list.rightPushAll(any(), any<List<String>>()) } returns 1L

then("레디스의 데이터가 정상적으로 업데이트된다.") {
rankingCommandService.updateTotalRanking()
verify(exactly = 1) { reviewRepository.findAllUserTotalCount() }
}
}

`when`("Lock을 획득하지 못한 경우") {
every { lock.tryLock(0, 10, TimeUnit.MINUTES) } returns false

then("레디스의 데이터가 업데이트되지 않는다.") {
rankingCommandService.updateTotalRanking()
verify(exactly = 0) { reviewRepository.findAllUserTotalCount() }
}
}
}
},
)
Loading