diff --git a/src/main/java/com/jiwon/mylog/domain/statistic/UserStatsController.java b/src/main/java/com/jiwon/mylog/domain/statistic/UserStatsController.java index 7d3c449..9609730 100644 --- a/src/main/java/com/jiwon/mylog/domain/statistic/UserStatsController.java +++ b/src/main/java/com/jiwon/mylog/domain/statistic/UserStatsController.java @@ -1,6 +1,7 @@ package com.jiwon.mylog.domain.statistic; import com.jiwon.mylog.domain.statistic.dto.DailyReportResponse; +import com.jiwon.mylog.domain.statistic.dto.UserRankResponse; import com.jiwon.mylog.global.security.auth.annotation.LoginUser; import lombok.RequiredArgsConstructor; import org.springframework.format.annotation.DateTimeFormat; @@ -11,6 +12,7 @@ import org.springframework.web.bind.annotation.RestController; import java.time.LocalDate; +import java.util.List; @RequiredArgsConstructor @RequestMapping("/api") @@ -19,6 +21,19 @@ public class UserStatsController { private final UserStatsService userStatsService; + @GetMapping("/users/rankers/weekly") + public ResponseEntity> getRanker( + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate + ) { + LocalDate end = endDate != null ? endDate : LocalDate.now().minusDays(1); + LocalDate start = startDate != null ? startDate : end.minusDays(6); + + List response = userStatsService.getRanker(start, end); + return ResponseEntity.ok(response); + } + + @GetMapping("/users/stats") public ResponseEntity getDailyStats( @LoginUser Long userId, diff --git a/src/main/java/com/jiwon/mylog/domain/statistic/UserStatsService.java b/src/main/java/com/jiwon/mylog/domain/statistic/UserStatsService.java index 0705aac..0f5e0e3 100644 --- a/src/main/java/com/jiwon/mylog/domain/statistic/UserStatsService.java +++ b/src/main/java/com/jiwon/mylog/domain/statistic/UserStatsService.java @@ -1,13 +1,17 @@ package com.jiwon.mylog.domain.statistic; import com.jiwon.mylog.domain.statistic.dto.DailyReportResponse; +import com.jiwon.mylog.domain.statistic.dto.UserRankResponse; +import com.jiwon.mylog.domain.statistic.repository.UserStatsRepository; import com.jiwon.mylog.global.redis.key.RedisKey; import com.jiwon.mylog.global.redis.RedisUtil; import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; +import java.util.List; @RequiredArgsConstructor @Service @@ -20,6 +24,12 @@ public class UserStatsService { private final static String REDIS_SET_DEFAULT = "0"; private final static int REDIS_GET_DEFAULT = 0; + @Cacheable(value = "stat::ranker", key = "'start:' + #start + ':end:' + #end") + @Transactional(readOnly = true) + public List getRanker(LocalDate start, LocalDate end) { + return userStatsRepository.findWeeklyTopUsers(start, end, 3); + } + @Transactional(readOnly = true) public DailyReportResponse getUserDailyStats(Long userId, LocalDate date) { if (date.isEqual(LocalDate.now())) { diff --git a/src/main/java/com/jiwon/mylog/domain/statistic/dto/UserRankResponse.java b/src/main/java/com/jiwon/mylog/domain/statistic/dto/UserRankResponse.java new file mode 100644 index 0000000..4ae9de4 --- /dev/null +++ b/src/main/java/com/jiwon/mylog/domain/statistic/dto/UserRankResponse.java @@ -0,0 +1,14 @@ +package com.jiwon.mylog.domain.statistic.dto; + +import com.jiwon.mylog.domain.user.dto.response.UserSummaryResponse; + +public record UserRankResponse( + Long userId, + String username, + String imageKey, + int receivedLikes, + int receivedComments, + int createdPosts, + int createdComments, + int total) { +} diff --git a/src/main/java/com/jiwon/mylog/domain/statistic/repository/UserStatsCustomRepository.java b/src/main/java/com/jiwon/mylog/domain/statistic/repository/UserStatsCustomRepository.java new file mode 100644 index 0000000..436fc53 --- /dev/null +++ b/src/main/java/com/jiwon/mylog/domain/statistic/repository/UserStatsCustomRepository.java @@ -0,0 +1,10 @@ +package com.jiwon.mylog.domain.statistic.repository; + +import com.jiwon.mylog.domain.statistic.dto.UserRankResponse; + +import java.time.LocalDate; +import java.util.List; + +public interface UserStatsCustomRepository { + List findWeeklyTopUsers(LocalDate startDate, LocalDate endDate, int limit); +} diff --git a/src/main/java/com/jiwon/mylog/domain/statistic/UserStatsRepository.java b/src/main/java/com/jiwon/mylog/domain/statistic/repository/UserStatsRepository.java similarity index 88% rename from src/main/java/com/jiwon/mylog/domain/statistic/UserStatsRepository.java rename to src/main/java/com/jiwon/mylog/domain/statistic/repository/UserStatsRepository.java index ba98925..d0f7d08 100644 --- a/src/main/java/com/jiwon/mylog/domain/statistic/UserStatsRepository.java +++ b/src/main/java/com/jiwon/mylog/domain/statistic/repository/UserStatsRepository.java @@ -1,5 +1,6 @@ -package com.jiwon.mylog.domain.statistic; +package com.jiwon.mylog.domain.statistic.repository; +import com.jiwon.mylog.domain.statistic.UserDailyStats; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -10,7 +11,7 @@ import java.util.Optional; @Repository -public interface UserStatsRepository extends JpaRepository { +public interface UserStatsRepository extends JpaRepository, UserStatsCustomRepository { Optional findByUserIdAndDate(Long userId, LocalDate date); @Modifying diff --git a/src/main/java/com/jiwon/mylog/domain/statistic/repository/UserStatsRepositoryImpl.java b/src/main/java/com/jiwon/mylog/domain/statistic/repository/UserStatsRepositoryImpl.java new file mode 100644 index 0000000..77ffd3f --- /dev/null +++ b/src/main/java/com/jiwon/mylog/domain/statistic/repository/UserStatsRepositoryImpl.java @@ -0,0 +1,59 @@ +package com.jiwon.mylog.domain.statistic.repository; + +import com.jiwon.mylog.domain.image.entity.QProfileImage; +import com.jiwon.mylog.domain.statistic.QUserDailyStats; +import com.jiwon.mylog.domain.statistic.dto.UserRankResponse; +import com.jiwon.mylog.domain.user.entity.QUser; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.NumberExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; +import java.util.List; + +@Slf4j +@RequiredArgsConstructor +@Repository +public class UserStatsRepositoryImpl implements UserStatsCustomRepository { + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public List findWeeklyTopUsers(LocalDate startDate, LocalDate endDate, int limit) { + QUserDailyStats stat = QUserDailyStats.userDailyStats; + QUser user = QUser.user; + QProfileImage profileImage = QProfileImage.profileImage; + + NumberExpression receivedLikesSum = stat.receivedLikes.sum(); + NumberExpression receivedCommentsSum = stat.receivedComments.sum(); + NumberExpression createdPostsSum = stat.createdPosts.sum(); + NumberExpression createdCommentsSum = stat.createdComments.sum(); + NumberExpression totalScore = receivedLikesSum + .add(receivedCommentsSum) + .add(createdPostsSum) + .add(createdCommentsSum); + + return jpaQueryFactory + .select(Projections.constructor(UserRankResponse.class, + user.id, + user.username, + profileImage.fileKey.coalesce(""), + receivedLikesSum, + receivedCommentsSum, + createdPostsSum, + createdCommentsSum, + totalScore + )) + .from(stat) + .join(stat.user, user) + .leftJoin(user.profileImage, profileImage) + .where(stat.date.between(startDate, endDate)) + .groupBy(user.id) + .orderBy(totalScore.desc()) + .limit(limit) + .fetch(); + } +} diff --git a/src/main/java/com/jiwon/mylog/global/schedular/StatsScheduler.java b/src/main/java/com/jiwon/mylog/global/schedular/StatsScheduler.java index 7967f18..7d1f7cc 100644 --- a/src/main/java/com/jiwon/mylog/global/schedular/StatsScheduler.java +++ b/src/main/java/com/jiwon/mylog/global/schedular/StatsScheduler.java @@ -1,6 +1,6 @@ package com.jiwon.mylog.global.schedular; -import com.jiwon.mylog.domain.statistic.UserStatsRepository; +import com.jiwon.mylog.domain.statistic.repository.UserStatsRepository; import com.jiwon.mylog.global.redis.key.RedisKey; import com.jiwon.mylog.global.redis.RedisUtil; import com.jiwon.mylog.global.redis.key.UserStatsKey;