diff --git a/src/main/kotlin/com/routebox/routebox/application/popular_route/UpdatePopularRouteUseCase.kt b/src/main/kotlin/com/routebox/routebox/application/popular_route/UpdatePopularRouteUseCase.kt new file mode 100644 index 0000000..d6e5fa6 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/application/popular_route/UpdatePopularRouteUseCase.kt @@ -0,0 +1,20 @@ +package com.routebox.routebox.application.popular_route + +import com.routebox.routebox.domain.popular_route.PopularRouteService +import com.routebox.routebox.domain.purchased_route.PurchasedRouteService +import org.springframework.stereotype.Component + +@Component +class UpdatePopularRouteUseCase( + private val popularRouteService: PopularRouteService, + private val purchasedRouteService: PurchasedRouteService, +) { + operator fun invoke(): Int { + // 많이 담은 루트 조회 + val popularRoutes = purchasedRouteService.getPurchasedRouteCountByRoute() + + // 인기 루트에 저장 + popularRouteService.createPopularRoutes(popularRoutes) + return 0 + } +} diff --git a/src/main/kotlin/com/routebox/routebox/application/popular_route/dto/PopularRouteCountDto.kt b/src/main/kotlin/com/routebox/routebox/application/popular_route/dto/PopularRouteCountDto.kt new file mode 100644 index 0000000..7bd5f79 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/application/popular_route/dto/PopularRouteCountDto.kt @@ -0,0 +1,6 @@ +package com.routebox.routebox.application.popular_route.dto + +data class PopularRouteCountDto( + val id: Long, + val count: Long, +) diff --git a/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRoute.kt b/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRoute.kt index 14e3397..00af086 100644 --- a/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRoute.kt +++ b/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRoute.kt @@ -7,12 +7,15 @@ import jakarta.persistence.GeneratedValue import jakarta.persistence.GenerationType import jakarta.persistence.Id import jakarta.persistence.Table +import java.time.LocalDate @Table(name = "popular_routes") @Entity class PopularRoute( id: Long = 0, routeId: Long, + count: Long, + date: LocalDate, ) : TimeTrackedBaseEntity() { @Id @@ -20,5 +23,12 @@ class PopularRoute( @Column(name = "popular_route_id") val id: Long = id + @Column val routeId: Long = routeId + + @Column + val count: Long = count + + @Column + val date: LocalDate = date } diff --git a/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRouteService.kt b/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRouteService.kt index 877ab53..5dc058b 100644 --- a/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRouteService.kt +++ b/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRouteService.kt @@ -1,9 +1,11 @@ package com.routebox.routebox.domain.popular_route +import com.routebox.routebox.application.popular_route.dto.PopularRouteCountDto import com.routebox.routebox.application.popular_route.dto.PopularRouteDto import com.routebox.routebox.infrastructure.popular_route.PopularRouteRepository import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate @Service class PopularRouteService( @@ -15,6 +17,24 @@ class PopularRouteService( */ @Transactional(readOnly = true) fun getPopularRoutes(): List { - return popularRouteRepository.findAllPopularRoutes() + // 가장 최근에 업데이트된 인기루트 조회 + return popularRouteRepository.findRecentPopularRoutes() + } + + /** + * 인기 루트 추가 + */ + @Transactional + fun createPopularRoutes(routes: List) { + val today = LocalDate.now() + val popularRoutes = routes.map { route -> + PopularRoute( + routeId = route.id, + count = route.count, + date = today, + ) + } + + popularRouteRepository.saveAll(popularRoutes) } } diff --git a/src/main/kotlin/com/routebox/routebox/domain/purchased_route/PurchasedRouteService.kt b/src/main/kotlin/com/routebox/routebox/domain/purchased_route/PurchasedRouteService.kt index 39cd046..f221fb9 100644 --- a/src/main/kotlin/com/routebox/routebox/domain/purchased_route/PurchasedRouteService.kt +++ b/src/main/kotlin/com/routebox/routebox/domain/purchased_route/PurchasedRouteService.kt @@ -1,5 +1,6 @@ package com.routebox.routebox.domain.purchased_route +import com.routebox.routebox.application.popular_route.dto.PopularRouteCountDto import com.routebox.routebox.exception.purchased_route.PurchasedRouteNotFoundException import com.routebox.routebox.infrastructure.purchased_route.PurchasedRouteRepository import org.springframework.data.domain.Page @@ -27,4 +28,10 @@ class PurchasedRouteService( @Transactional(readOnly = true) fun getPurchasedRouteCount(buyerId: Long): Int = purchasedRouteRepository.countByBuyer_Id(buyerId) + + @Transactional(readOnly = true) + fun getPurchasedRouteCountByRoute(): List { + val pageable = PageRequest.of(0, 5) + return purchasedRouteRepository.findTop5PopularRoutes(pageable) + } } diff --git a/src/main/kotlin/com/routebox/routebox/infrastructure/popular_route/PopularRouteRepository.kt b/src/main/kotlin/com/routebox/routebox/infrastructure/popular_route/PopularRouteRepository.kt index c2a3a08..1c93291 100644 --- a/src/main/kotlin/com/routebox/routebox/infrastructure/popular_route/PopularRouteRepository.kt +++ b/src/main/kotlin/com/routebox/routebox/infrastructure/popular_route/PopularRouteRepository.kt @@ -12,7 +12,9 @@ interface PopularRouteRepository : JpaRepository { FROM PopularRoute pr JOIN Route r ON pr.routeId = r.id WHERE r.isPublic = true + AND pr.date = (SELECT MAX(p.date) FROM PopularRoute p) + ORDER BY pr.count DESC """, ) - fun findAllPopularRoutes(): List + fun findRecentPopularRoutes(): List } diff --git a/src/main/kotlin/com/routebox/routebox/infrastructure/purchased_route/PurchasedRouteRepository.kt b/src/main/kotlin/com/routebox/routebox/infrastructure/purchased_route/PurchasedRouteRepository.kt index 59cef85..2158425 100644 --- a/src/main/kotlin/com/routebox/routebox/infrastructure/purchased_route/PurchasedRouteRepository.kt +++ b/src/main/kotlin/com/routebox/routebox/infrastructure/purchased_route/PurchasedRouteRepository.kt @@ -1,12 +1,27 @@ package com.routebox.routebox.infrastructure.purchased_route +import com.routebox.routebox.application.popular_route.dto.PopularRouteCountDto import com.routebox.routebox.domain.purchased_route.PurchasedRoute import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query @Suppress("ktlint:standard:function-naming") interface PurchasedRouteRepository : JpaRepository { fun findByBuyer_IdOrderByCreatedAtDesc(buyerId: Long, pageable: Pageable): Page fun countByBuyer_Id(buyerId: Long): Int + + @Query( + """ + SELECT NEW com.routebox.routebox.application.popular_route.dto.PopularRouteCountDto( + r.id, COALESCE(COUNT(pr), 0) + ) + FROM Route r + LEFT JOIN PopularRoute pr ON r.id = pr.routeId + GROUP BY r.id + ORDER BY COUNT(pr) DESC + """, + ) + fun findTop5PopularRoutes(pageable: Pageable): List } diff --git a/src/main/kotlin/com/routebox/routebox/scheduler/PopularRouteScheduler.kt b/src/main/kotlin/com/routebox/routebox/scheduler/PopularRouteScheduler.kt new file mode 100644 index 0000000..74a78ad --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/scheduler/PopularRouteScheduler.kt @@ -0,0 +1,23 @@ +package com.routebox.routebox.scheduler + +import com.routebox.routebox.application.popular_route.UpdatePopularRouteUseCase +import org.slf4j.LoggerFactory +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Component + +@Component +class PopularRouteScheduler( + private val updatePopularRouteUseCase: UpdatePopularRouteUseCase, +) { + + private val logger = LoggerFactory.getLogger(this::class.java) + + /** + * 매일 00:00:00에 실행 + */ + @Scheduled(cron = "0 0 0 * * *") + fun updatePopularRoute() { + logger.info("Scheduler is running") + updatePopularRouteUseCase() + } +} diff --git a/src/main/kotlin/com/routebox/routebox/scheduler/SchedulerConfig.kt b/src/main/kotlin/com/routebox/routebox/scheduler/SchedulerConfig.kt new file mode 100644 index 0000000..8c56068 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/scheduler/SchedulerConfig.kt @@ -0,0 +1,14 @@ +package com.routebox.routebox.scheduler + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.context.annotation.Configuration +import org.springframework.scheduling.annotation.EnableScheduling + +@Configuration +@EnableScheduling +@ConditionalOnProperty( + name = ["routebox.scheduler.enabled"], + havingValue = "true", + matchIfMissing = true, +) +class SchedulerConfig diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9cc4cd0..75e7c2b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,6 +2,8 @@ routebox: jwt: secret-key: ${JWT_SECRET_KEY} app-version: 0.0.1 + scheduler: + enabled: true # 스케줄러 활성화 여부 spring: application: diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index bd3328b..ffd041c 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -261,6 +261,8 @@ CREATE TABLE popular_routes ( popular_route_id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '인기 루트 ID', route_id BIGINT NOT NULL COMMENT '루트 ID', + count BIGINT NOT NULL, + date DATE NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성 시간', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '업데이트 시간' );