diff --git a/src/main/java/com/example/green/domain/certification/infra/projections/MemberCertifiedCountProjection.java b/src/main/java/com/example/green/domain/certification/infra/projections/MemberCertifiedCountProjection.java new file mode 100644 index 00000000..10d6450e --- /dev/null +++ b/src/main/java/com/example/green/domain/certification/infra/projections/MemberCertifiedCountProjection.java @@ -0,0 +1,7 @@ +package com.example.green.domain.certification.infra.projections; + +public interface MemberCertifiedCountProjection { + Long getMemberId(); + + int getCertifiedCount(); +} diff --git a/src/main/java/com/example/green/domain/certification/repository/ChallengeCertificationRepository.java b/src/main/java/com/example/green/domain/certification/repository/ChallengeCertificationRepository.java new file mode 100644 index 00000000..1b0cdddb --- /dev/null +++ b/src/main/java/com/example/green/domain/certification/repository/ChallengeCertificationRepository.java @@ -0,0 +1,23 @@ +package com.example.green.domain.certification.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import com.example.green.domain.certification.domain.ChallengeCertification; +import com.example.green.domain.certification.infra.projections.MemberCertifiedCountProjection; + +public interface ChallengeCertificationRepository extends JpaRepository { + + //단일 회원 인증 수 + int countChallengeCertificationByMemberMemberId(Long memberId); + + //여러 회원 인증 수 (한번에 조회) + @Query("SELECT c.member.memberId AS memberId, COUNT(c) AS certifiedCount " + + "FROM ChallengeCertification c " + + "WHERE c.member.memberId IN :memberIds " + + "GROUP BY c.member.memberId") + List findCertifiedCountByMemberIds(@Param("memberIds") List memberIds); +} diff --git a/src/main/java/com/example/green/domain/dashboard/rankingmodule/repository/WeeklyRankingRepository.java b/src/main/java/com/example/green/domain/dashboard/rankingmodule/repository/WeeklyRankingRepository.java index 04a4d121..e95fdbc6 100644 --- a/src/main/java/com/example/green/domain/dashboard/rankingmodule/repository/WeeklyRankingRepository.java +++ b/src/main/java/com/example/green/domain/dashboard/rankingmodule/repository/WeeklyRankingRepository.java @@ -10,4 +10,6 @@ public interface WeeklyRankingRepository extends JpaRepository, WeeklyRankingRepositoryCustom { List findByWeekStart(LocalDate weekStart); + + void deleteByWeekStart(LocalDate weekStart); } diff --git a/src/main/java/com/example/green/domain/dashboard/rankingmodule/service/WeeklyRankingScheduler.java b/src/main/java/com/example/green/domain/dashboard/rankingmodule/service/WeeklyRankingScheduler.java index 0e420b3e..1c7768e6 100644 --- a/src/main/java/com/example/green/domain/dashboard/rankingmodule/service/WeeklyRankingScheduler.java +++ b/src/main/java/com/example/green/domain/dashboard/rankingmodule/service/WeeklyRankingScheduler.java @@ -1,5 +1,9 @@ package com.example.green.domain.dashboard.rankingmodule.service; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.ZoneId; + import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -16,6 +20,12 @@ public class WeeklyRankingScheduler { */ @Scheduled(cron = "0 0 0 * * MON", zone = "Asia/Seoul") public void calculateWeeklyRankingBatch() { - weeklyRankingService.updateWeeklyRanks(); + LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); + + // 오늘이 월요일 → 지난주 계산 + LocalDate weekStart = today.minusWeeks(1).with(DayOfWeek.MONDAY); + LocalDate weekEnd = today.minusWeeks(1).with(DayOfWeek.SUNDAY); + + weeklyRankingService.updateWeeklyRanks(weekStart, weekEnd); } } diff --git a/src/main/java/com/example/green/domain/dashboard/rankingmodule/service/WeeklyRankingService.java b/src/main/java/com/example/green/domain/dashboard/rankingmodule/service/WeeklyRankingService.java index 209aa625..85953c98 100644 --- a/src/main/java/com/example/green/domain/dashboard/rankingmodule/service/WeeklyRankingService.java +++ b/src/main/java/com/example/green/domain/dashboard/rankingmodule/service/WeeklyRankingService.java @@ -4,6 +4,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -12,6 +13,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.example.green.domain.certification.infra.projections.MemberCertifiedCountProjection; +import com.example.green.domain.certification.repository.ChallengeCertificationRepository; import com.example.green.domain.dashboard.rankingmodule.dto.response.MemberPointResponse; import com.example.green.domain.dashboard.rankingmodule.dto.response.TopMemberPointResponse; import com.example.green.domain.dashboard.rankingmodule.entity.WeeklyRanking; @@ -20,6 +23,7 @@ import com.example.green.domain.dashboard.rankingmodule.repository.WeeklyRankingRepository; import com.example.green.domain.member.entity.Member; import com.example.green.domain.member.repository.MemberRepository; +import com.example.green.domain.point.repository.PointTransactionQueryRepository; import io.swagger.v3.oas.annotations.media.Schema; import lombok.RequiredArgsConstructor; @@ -32,34 +36,78 @@ public class WeeklyRankingService { private final WeeklyRankingRepository weeklyRankingRepository; private final MemberRepository memberRepository; + private final PointTransactionQueryRepository pointTransactionQueryRepository; + private final ChallengeCertificationRepository challengeCertificationRepository; //모든 주별 TopN 계산 로직 추가 @Transactional - public void updateWeeklyRanks() { + public void updateWeeklyRanks(LocalDate weekStart, LocalDate weekEnd) { - List allWeeklyRankings = weeklyRankingRepository.findAllRankings(); + // 기존 주차 랭킹 삭제 + weeklyRankingRepository.deleteByWeekStart(weekStart); - Map> rankingsByWeek = allWeeklyRankings.stream() - .collect(Collectors.groupingBy(WeeklyRanking::getWeekStart)); - - for (Map.Entry> entry : rankingsByWeek.entrySet()) { + // 모든 회원 조회 + List allMembers = memberRepository.findAll(); + List memberIds = allMembers.stream() + .map(Member::getId) + .toList(); - List weeklyRankings = entry.getValue(); + Map memberPoints = Optional.ofNullable( + pointTransactionQueryRepository.findEarnedPointByMember(memberIds)) + .orElse(new HashMap<>()); + + Map certifiedCounts = Optional.ofNullable( + challengeCertificationRepository.findCertifiedCountByMemberIds(memberIds)) + .orElse(new ArrayList<>()) + .stream() + .collect(Collectors.toMap( + MemberCertifiedCountProjection::getMemberId, + MemberCertifiedCountProjection::getCertifiedCount + )); + + List weeklyRankings = allMembers.stream() + .map(member -> { + BigDecimal point = memberPoints.getOrDefault(member.getId(), BigDecimal.ZERO); + int certificationCount = certifiedCounts.getOrDefault(member.getId(), 0); + String profileImageUrl = (member.getProfile() != null) ? + member.getProfile().getProfileImageUrl() : null; + + return WeeklyRanking.builder() + .memberId(member.getId()) + .memberName(member.getName()) + .profileImageUrl(profileImageUrl) + .totalPoint(point) // DB 저장/정렬용 + .certificationCount(certificationCount) + .weekStart(weekStart) + .weekEnd(weekEnd) + .rank(0) + .build(); + }) + .toList(); - weeklyRankings.sort( - Comparator.comparing(WeeklyRanking::getTotalPoint, Comparator.reverseOrder()) - .thenComparing(WeeklyRanking::getCertificationCount, Comparator.reverseOrder()) - ); + // 정렬 (포인트 내림차순, 동점이면 인증 수 내림차순) + weeklyRankings.sort( + Comparator.comparing(WeeklyRanking::getTotalPoint, Comparator.reverseOrder()) + .thenComparing(WeeklyRanking::getCertificationCount, Comparator.reverseOrder()) + ); - for (int i = 0; i < weeklyRankings.size(); i++) { - WeeklyRanking weeklyRanking = weeklyRankings.get(i); - int newRank = i + 1; - if (weeklyRanking.getRank() != newRank) { - weeklyRanking.setRank(newRank); - } + int rankCounter = 1; + for (int i = 0; i < weeklyRankings.size(); i++) { + if (i > 0 && + weeklyRankings.get(i).getTotalPoint().compareTo(weeklyRankings.get(i - 1).getTotalPoint()) == 0 && + weeklyRankings.get(i).getCertificationCount() == + weeklyRankings.get(i - 1).getCertificationCount()) { + + // 완전 동점 → 이전 rank와 동일 + weeklyRankings.get(i).setRank(weeklyRankings.get(i - 1).getRank()); + } else { + weeklyRankings.get(i).setRank(rankCounter); } + rankCounter++; } + // DB 저장 + weeklyRankingRepository.saveAll(weeklyRankings); } public List getAllRankData(LocalDate weekStart, int topN) { diff --git a/src/main/java/com/example/green/domain/pointshop/item/infra/PointItemQueryExecutor.java b/src/main/java/com/example/green/domain/pointshop/item/infra/PointItemQueryExecutor.java index cfe95900..8ebf36da 100644 --- a/src/main/java/com/example/green/domain/pointshop/item/infra/PointItemQueryExecutor.java +++ b/src/main/java/com/example/green/domain/pointshop/item/infra/PointItemQueryExecutor.java @@ -8,6 +8,7 @@ import com.example.green.domain.pointshop.item.dto.response.PointItemSearchResponse; import com.example.green.domain.pointshop.item.entity.QPointItem; import com.example.green.global.api.page.Pagination; +import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -50,7 +51,14 @@ public List findItemsForExcel(BooleanExpression express public List findItemByCursor(BooleanExpression expression, int cursorViewSize) { return jpaQueryFactory - .select(PointItemProjections.toPointItemView(qPointItem)) + .select(Projections.constructor( + PointItemResponse.class, + qPointItem.id, + qPointItem.itemBasicInfo.itemName, + qPointItem.itemMedia.itemThumbNailUrl, + qPointItem.itemPrice.itemPrice, + qPointItem.sellingStatus + )) .from(qPointItem) .where(expression) .orderBy(qPointItem.id.desc()) diff --git a/src/main/java/com/example/green/infra/client/CertificationClient.java b/src/main/java/com/example/green/infra/client/CertificationClient.java index cdf66062..be28c719 100644 --- a/src/main/java/com/example/green/infra/client/CertificationClient.java +++ b/src/main/java/com/example/green/infra/client/CertificationClient.java @@ -8,4 +8,5 @@ public interface CertificationClient { int getTotalCertifiedCountByMember(Long memberId); Map getCertificationCountByMembers(List memberIds); + }