Skip to content

Commit 0facac0

Browse files
authored
Merge pull request #115 from tukcomCD2024/refactor/#114
[Refactor/#114] Redis 기반 실시간 리더보드 정합성 개선
2 parents 3bc5113 + 8812f99 commit 0facac0

File tree

19 files changed

+412
-152
lines changed

19 files changed

+412
-152
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
package com.example.memetory.domain.like.repository;
22

3+
import java.time.LocalDate;
4+
import java.time.Year;
5+
import java.time.YearMonth;
6+
import java.util.List;
37
import java.util.Optional;
48

59
import com.example.memetory.domain.like.entity.Like;
610
import com.example.memetory.domain.member.entity.Member;
11+
import com.example.memetory.domain.memes.dto.MemesRankDto;
712
import com.example.memetory.domain.memes.entity.Memes;
813

914
public interface LikeQueryRepository {
1015
Optional<Like> findLikeByMemberAndMemes(Member member, Memes memes);
16+
17+
List<MemesRankDto> findDailyRankLimit100(LocalDate localDate);
18+
19+
List<MemesRankDto> findWeeklyRankLimit100(Year year, int week);
20+
21+
List<MemesRankDto> findMonthlyRankLimit100(YearMonth yearMonth);
1122
}

backend/memetory/src/main/java/com/example/memetory/domain/like/repository/LikeQueryRepositoryImpl.java

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@
22

33
import static com.example.memetory.domain.like.entity.QLike.*;
44

5+
import java.time.LocalDate;
6+
import java.time.LocalDateTime;
7+
import java.time.Year;
8+
import java.time.YearMonth;
9+
import java.time.temporal.WeekFields;
10+
import java.util.List;
11+
import java.util.Locale;
512
import java.util.Optional;
613

714
import org.springframework.stereotype.Repository;
815

916
import com.example.memetory.domain.like.entity.Like;
1017
import com.example.memetory.domain.member.entity.Member;
18+
import com.example.memetory.domain.memes.dto.MemesRankDto;
1119
import com.example.memetory.domain.memes.entity.Memes;
20+
import com.querydsl.core.types.Projections;
1221
import com.querydsl.jpa.impl.JPAQueryFactory;
1322

1423
import lombok.RequiredArgsConstructor;
@@ -20,10 +29,60 @@ public class LikeQueryRepositoryImpl implements LikeQueryRepository {
2029

2130
@Override
2231
public Optional<Like> findLikeByMemberAndMemes(Member member, Memes memes) {
23-
return Optional.ofNullable(jpaQueryFactory
24-
.selectFrom(like)
25-
.where(like.member.eq(member)
26-
.and(like.memes.eq(memes)))
27-
.fetchFirst());
32+
return Optional.ofNullable(
33+
jpaQueryFactory.selectFrom(like).where(like.member.eq(member).and(like.memes.eq(memes))).fetchFirst());
2834
}
35+
36+
@Override
37+
public List<MemesRankDto> findDailyRankLimit100(LocalDate localDate) {
38+
return jpaQueryFactory.select(Projections.constructor(MemesRankDto.class, like.memes.id, like.id.count()))
39+
.from(like)
40+
.where(like.createdAt.goe(localDate.atStartOfDay()),
41+
like.createdAt.lt(localDate.plusDays(1).atStartOfDay()))
42+
.groupBy(like.memes)
43+
.orderBy(like.id.count().desc())
44+
.limit(100L)
45+
.fetch();
46+
}
47+
48+
@Override
49+
public List<MemesRankDto> findWeeklyRankLimit100(Year year, int week) {
50+
// 주차 기준: 한국 (월요일 시작)
51+
WeekFields weekFields = WeekFields.of(Locale.KOREA);
52+
53+
// 해당 주의 시작 날짜 (월요일)
54+
LocalDate startOfWeek = year.atDay(1).with(weekFields.weekOfYear(), week).with(weekFields.dayOfWeek(), 1);
55+
56+
// 다음 주의 시작 날짜 (exclusive)
57+
LocalDate endOfWeek = startOfWeek.plusDays(7);
58+
59+
LocalDateTime startDateTime = startOfWeek.atStartOfDay(); // 월요일 00:00
60+
LocalDateTime endDateTime = endOfWeek.atStartOfDay(); // 다음 주 월요일 00:00
61+
62+
return jpaQueryFactory.select(Projections.constructor(MemesRankDto.class, like.memes.id, like.id.count()))
63+
.from(like)
64+
.where(like.createdAt.goe(startDateTime), like.createdAt.lt(endDateTime))
65+
.groupBy(like.memes.id)
66+
.orderBy(like.id.count().desc())
67+
.limit(100)
68+
.fetch();
69+
}
70+
71+
@Override
72+
public List<MemesRankDto> findMonthlyRankLimit100(YearMonth yearMonth) {
73+
// 해당 월의 시작일 00:00
74+
LocalDateTime startOfMonth = yearMonth.atDay(1).atStartOfDay();
75+
76+
// 다음 달의 시작일 00:00 (해당 월의 마지막까지 포함되도록)
77+
LocalDateTime startOfNextMonth = yearMonth.plusMonths(1).atDay(1).atStartOfDay();
78+
79+
return jpaQueryFactory.select(Projections.constructor(MemesRankDto.class, like.memes.id, like.id.count()))
80+
.from(like)
81+
.where(like.createdAt.goe(startOfMonth), like.createdAt.lt(startOfNextMonth))
82+
.groupBy(like.memes.id)
83+
.orderBy(like.id.count().desc())
84+
.limit(100)
85+
.fetch();
86+
}
87+
2988
}

backend/memetory/src/main/java/com/example/memetory/domain/like/repository/LikeRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
import com.example.memetory.domain.like.entity.Like;
44
import com.example.memetory.domain.member.entity.Member;
5+
import com.example.memetory.domain.memes.dto.MemesRankDto;
56
import com.example.memetory.domain.memes.entity.Memes;
67
import org.springframework.data.jpa.repository.JpaRepository;
78

9+
import java.time.Year;
10+
import java.util.List;
811
import java.util.Optional;
912

1013
public interface LikeRepository extends JpaRepository<Like, Long>, LikeQueryRepository {
Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.example.memetory.domain.like.service;
22

3+
import org.springframework.context.ApplicationEventPublisher;
34
import org.springframework.dao.DataIntegrityViolationException;
45
import org.springframework.stereotype.Service;
56
import org.springframework.transaction.annotation.Transactional;
@@ -12,8 +13,9 @@
1213
import com.example.memetory.domain.member.entity.Member;
1314
import com.example.memetory.domain.member.service.MemberService;
1415
import com.example.memetory.domain.memes.entity.Memes;
16+
import com.example.memetory.domain.memes.event.LikeCreatedEvent;
17+
import com.example.memetory.domain.memes.event.LikeDeletedEvent;
1518
import com.example.memetory.domain.memes.service.MemesService;
16-
import com.example.memetory.domain.memes.service.RankingService;
1719

1820
import lombok.RequiredArgsConstructor;
1921

@@ -23,7 +25,7 @@ public class LikeService {
2325
private final MemberService memberService;
2426
private final MemesService memesService;
2527
private final LikeRepository likeRepository;
26-
private final RankingService rankingService;
28+
private final ApplicationEventPublisher eventPublisher;
2729

2830
@Transactional
2931
public void registerLike(LikeServiceDto likeServiceDto) {
@@ -32,7 +34,7 @@ public void registerLike(LikeServiceDto likeServiceDto) {
3234

3335
Like newLike = Like.fromMemberAndMemes(member, memes);
3436
saveLike(newLike);
35-
increaseMemesLikeCount(memes);
37+
eventPublisher.publishEvent(new LikeCreatedEvent(memes.getId()));
3638
}
3739

3840
private void saveLike(Like like) {
@@ -43,23 +45,13 @@ private void saveLike(Like like) {
4345
}
4446
}
4547

46-
private void increaseMemesLikeCount(Memes memes) {
47-
memes.addLikeCount();
48-
rankingService.increaseTodayMemesLikeCountFromMemesId(memes.getId());
49-
}
50-
5148
@Transactional
5249
public void cancelLike(LikeServiceDto likeServiceDto) {
5350
Member member = memberService.findMemberFromEmail(likeServiceDto.getEmail());
5451
Memes memes = memesService.findMemesFromMemesId(likeServiceDto.getMemesId());
5552
Like like = likeRepository.findLikeByMemberAndMemes(member, memes).orElseThrow(NotFoundLikeException::new);
5653

5754
likeRepository.delete(like);
58-
decreaseMemesLikeCount(memes);
59-
}
60-
61-
private void decreaseMemesLikeCount(Memes memes) {
62-
memes.cancelLikeCount();
63-
rankingService.decreaseTodayMemesLikeCountFromMemesId(memes.getId());
55+
eventPublisher.publishEvent(new LikeDeletedEvent(memes.getId()));
6456
}
6557
}

backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/like/MemesLikeApi.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.example.memetory.domain.memes.controller.like;
22

3+
import java.time.LocalDate;
4+
import java.time.YearMonth;
5+
36
import org.springframework.http.ResponseEntity;
47

58
import com.example.memetory.global.response.ResultResponse;
@@ -23,7 +26,7 @@ public interface MemesLikeApi {
2326
description = "인기차트 조회"
2427
)
2528
})
26-
ResponseEntity<ResultResponse> findTopMemesByLike();
29+
ResponseEntity<ResultResponse> findTopMemesByLike(LocalDate date);
2730

2831
@Operation(
2932
summary = "meme`s 이달의 인기차트 조회",
@@ -36,7 +39,7 @@ public interface MemesLikeApi {
3639
description = "이달의 인기차트 조회"
3740
)
3841
})
39-
ResponseEntity<ResultResponse> findTopMemesByLikeForMonth();
42+
ResponseEntity<ResultResponse> findTopMemesByLikeForMonth(YearMonth yearMonth);
4043

4144
@Operation(
4245
summary = "meme`s 이주의 인기차트 조회",
@@ -49,7 +52,7 @@ public interface MemesLikeApi {
4952
description = "이주의 인기차트 조회"
5053
)
5154
})
52-
ResponseEntity<ResultResponse> findTopMemesByLikeForWeek();
55+
ResponseEntity<ResultResponse> findTopMemesByLikeForWeek(int year, int week);
5356

5457
@Operation(
5558
summary = "meme`s 좋아요 등록",

backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/like/MemesLikeController.java

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@
22

33
import static com.example.memetory.global.response.ResultCode.*;
44

5+
import java.time.LocalDate;
6+
import java.time.Year;
7+
import java.time.YearMonth;
58
import java.util.List;
69

10+
import org.springframework.format.annotation.DateTimeFormat;
711
import org.springframework.http.HttpStatus;
812
import org.springframework.http.ResponseEntity;
913
import org.springframework.web.bind.annotation.DeleteMapping;
1014
import org.springframework.web.bind.annotation.GetMapping;
1115
import org.springframework.web.bind.annotation.PathVariable;
1216
import org.springframework.web.bind.annotation.PostMapping;
1317
import org.springframework.web.bind.annotation.RequestMapping;
18+
import org.springframework.web.bind.annotation.RequestParam;
1419
import org.springframework.web.bind.annotation.RestController;
1520

1621
import com.example.memetory.domain.like.dto.LikeServiceDto;
@@ -29,25 +34,30 @@ public class MemesLikeController implements MemesLikeApi {
2934
private final MemesService memesService;
3035
private final LikeService likeService;
3136

32-
@GetMapping("/like/all")
37+
@GetMapping("/like/daily")
3338
@Override
34-
public ResponseEntity<ResultResponse> findTopMemesByLike() {
35-
List<MemesInfoResponse> response = memesService.findTopMemesByLike();
39+
public ResponseEntity<ResultResponse> findTopMemesByLike(@RequestParam("date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) {
40+
List<MemesInfoResponse> response = memesService.findDailyTop10Memes(date);
3641
return ResponseEntity.ok(ResultResponse.of(GET_TOP_TEN_MEMES_SUCCESS, response));
3742
}
3843

39-
@GetMapping("/like/month")
44+
@GetMapping("/like/week")
4045
@Override
41-
public ResponseEntity<ResultResponse> findTopMemesByLikeForMonth() {
42-
List<MemesInfoResponse> response = memesService.findTopMemesByLikeForMonth();
43-
return ResponseEntity.ok(ResultResponse.of(GET_MONTH_TOP_TEN_MEMES_SUCCESS, response));
46+
public ResponseEntity<ResultResponse> findTopMemesByLikeForWeek(
47+
@RequestParam("year") int year,
48+
@RequestParam("week") int week
49+
) {
50+
List<MemesInfoResponse> response = memesService.findWeeklyTop10Memes(Year.of(year), week);
51+
return ResponseEntity.ok(ResultResponse.of(GET_WEEK_TOP_TEN_MEMES_SUCCESS, response));
4452
}
4553

46-
@GetMapping("/like/week")
54+
@GetMapping("/like/month")
4755
@Override
48-
public ResponseEntity<ResultResponse> findTopMemesByLikeForWeek() {
49-
List<MemesInfoResponse> response = memesService.findTopMemesByLikeForWeek();
50-
return ResponseEntity.ok(ResultResponse.of(GET_WEEK_TOP_TEN_MEMES_SUCCESS, response));
56+
public ResponseEntity<ResultResponse> findTopMemesByLikeForMonth(
57+
@RequestParam("yearMonth") @DateTimeFormat(pattern = "yyyy-MM") YearMonth yearMonth
58+
) {
59+
List<MemesInfoResponse> response = memesService.findMonthlyTop10Memes(yearMonth);
60+
return ResponseEntity.ok(ResultResponse.of(GET_MONTH_TOP_TEN_MEMES_SUCCESS, response));
5161
}
5262

5363
@PostMapping("/{memesId}/like")
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.example.memetory.domain.memes.event;
2+
3+
import java.time.LocalDate;
4+
import java.util.List;
5+
6+
import com.example.memetory.domain.memes.dto.MemesRankDto;
7+
8+
public record DailyRankingCreatedEvent(LocalDate date, List<MemesRankDto> memesRank) {
9+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.example.memetory.domain.memes.event;
2+
3+
public record LikeCreatedEvent(Long memesId) {
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.example.memetory.domain.memes.event;
2+
3+
public record LikeDeletedEvent(Long memesId) {
4+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.example.memetory.domain.memes.event;
2+
3+
import java.time.YearMonth;
4+
import java.util.List;
5+
6+
import com.example.memetory.domain.memes.dto.MemesRankDto;
7+
8+
public record MonthlyRankingCreatedEvent(YearMonth yearMonth, List<MemesRankDto> memesRankDtoList) {
9+
10+
}

0 commit comments

Comments
 (0)