Skip to content

Commit

Permalink
Merge pull request #93 from tukcomCD2024/feat#92/redis-leaderboard
Browse files Browse the repository at this point in the history
Feat#92/redis leaderboard
  • Loading branch information
ggamD00 authored May 27, 2024
2 parents 053d426 + 8a29d4b commit 41f21ee
Show file tree
Hide file tree
Showing 14 changed files with 445 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package com.example.memetory.domain.like.dto;

import com.example.memetory.domain.like.entity.Like;
import com.example.memetory.domain.member.entity.Member;
import com.example.memetory.domain.memes.entity.Memes;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -11,20 +8,13 @@
@Builder
@AllArgsConstructor
public class LikeServiceDto {
private Long memesId;
private String email;
private Long memesId;
private String email;

public static LikeServiceDto fromEmailAndMemesId(String email, Long memesId) {
return LikeServiceDto.builder()
.email(email)
.memesId(memesId)
.build();
}

public Like toEntityFromMemberAndMemes(Member member, Memes memes) {
return Like.builder()
.member(member)
.memes(memes)
.build();
}
public static LikeServiceDto fromEmailAndMemesId(String email, Long memesId) {
return LikeServiceDto.builder()
.email(email)
.memesId(memesId)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,11 @@ public Like(Member member, Memes memes) {
this.member = member;
this.memes = memes;
}

public static Like fromMemberAndMemes(Member member, Memes memes) {
return Like.builder()
.member(member)
.memes(memes)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.example.memetory.domain.member.service.MemberService;
import com.example.memetory.domain.memes.entity.Memes;
import com.example.memetory.domain.memes.service.MemesService;
import com.example.memetory.domain.memes.service.RankingService;

import lombok.RequiredArgsConstructor;

Expand All @@ -22,15 +23,16 @@ public class LikeService {
private final MemberService memberService;
private final MemesService memesService;
private final LikeRepository likeRepository;
private final RankingService rankingService;

@Transactional
public void registerLike(LikeServiceDto likeServiceDto) {
Member member = memberService.findMemberFromEmail(likeServiceDto.getEmail());
Memes memes = memesService.findMemesFromMemesId(likeServiceDto.getMemesId());

Like newLike = likeServiceDto.toEntityFromMemberAndMemes(member, memes);
Like newLike = Like.fromMemberAndMemes(member, memes);
saveLike(newLike);
memes.addLikeCount();
increaseMemesLikeCount(memes);
}

private void saveLike(Like like) {
Expand All @@ -41,13 +43,23 @@ private void saveLike(Like like) {
}
}

private void increaseMemesLikeCount(Memes memes) {
memes.addLikeCount();
rankingService.increaseTodayMemesLikeCountFromMemesId(memes.getId());
}

@Transactional
public void cancelLike(LikeServiceDto likeServiceDto) {
Member member = memberService.findMemberFromEmail(likeServiceDto.getEmail());
Memes memes = memesService.findMemesFromMemesId(likeServiceDto.getMemesId());
Like like = likeRepository.findLikeByMemberAndMemes(member, memes).orElseThrow(NotFoundLikeException::new);

likeRepository.delete(like);
decreaseMemesLikeCount(memes);
}

private void decreaseMemesLikeCount(Memes memes) {
memes.cancelLikeCount();
rankingService.decreaseTodayMemesLikeCountFromMemesId(memes.getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public MemeResponse findMemberMemeResponse(MemeServiceDto memeServiceDto) {
return MemeResponse.of(meme);
}

public static void certifyMemeMember(Member m1, Member m2) {
public void certifyMemeMember(Member m1, Member m2) {
if (!m1.equals(m2)) {
throw new AccessDeniedMemeException();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example.memetory.domain.memes.dto;

import org.springframework.data.redis.core.ZSetOperations;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@AllArgsConstructor
@Builder
public class MemesRankDto {
private Long memesId;
private Long score;

public static MemesRankDto of(ZSetOperations.TypedTuple<Long> zSet) {
return MemesRankDto.builder()
.memesId(zSet.getValue())
.score(zSet.getScore().longValue())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,15 @@ public static MemesInfoResponse of(Memes memes) {
.createdAt(memes.getCreatedAt())
.build();
}

public static MemesInfoResponse fromMemesAndLikeCount(Memes memes, Long likeCount) {
return MemesInfoResponse.builder()
.memesId(memes.getId())
.memberNickname(memes.getMember().getNickname())
.title(memes.getTitle())
.commentCount(memes.getCommentCount())
.likeCount(likeCount)
.createdAt(memes.getCreatedAt())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.example.memetory.domain.memes.service;

import static java.time.LocalDateTime.*;

import java.util.List;

import org.springframework.data.domain.Pageable;
Expand All @@ -13,6 +11,7 @@
import com.example.memetory.domain.member.service.MemberService;
import com.example.memetory.domain.meme.entity.Meme;
import com.example.memetory.domain.meme.service.MemeService;
import com.example.memetory.domain.memes.dto.MemesRankDto;
import com.example.memetory.domain.memes.dto.MemesServiceDto;
import com.example.memetory.domain.memes.dto.response.MemesInfoResponse;
import com.example.memetory.domain.memes.dto.response.MemesInfoSliceResponse;
Expand All @@ -30,6 +29,7 @@ public class MemesService {
private final MemberService memberService;
private final MemeService memeService;
private final MemesRepository memesRepository;
private final RankingService rankingService;

@Transactional
public MemesResponse registerMemes(MemesServiceDto memesServiceDto) {
Expand Down Expand Up @@ -97,12 +97,26 @@ public List<MemesInfoResponse> findTopMemesByLike() {

@Transactional(readOnly = true)
public List<MemesInfoResponse> findTopMemesByLikeForMonth() {
return memesRepository.findTopMemesOrderByLikeCountForPeriod(now().minusMonths(1));
List<MemesRankDto> memesRankDtoList = rankingService.findTopTenMemesLikeCountForMonth();

return convertMemesRankDtoListIntoMemesInfoResponseList(memesRankDtoList);
}

@Transactional(readOnly = true)
public List<MemesInfoResponse> findTopMemesByLikeForWeek() {
return memesRepository.findTopMemesOrderByLikeCountForPeriod(now().minusWeeks(1));
List<MemesRankDto> memesRankDtoList = rankingService.findTopTenMemesLikeCountForWeek();

return convertMemesRankDtoListIntoMemesInfoResponseList(memesRankDtoList);
}

private List<MemesInfoResponse> convertMemesRankDtoListIntoMemesInfoResponseList(
List<MemesRankDto> memesRankDtoList) {

return memesRankDtoList.stream().map(this::convertMemesRankDtoInooMemesInfoResponse).toList();
}

private MemesInfoResponse convertMemesRankDtoInooMemesInfoResponse(MemesRankDto memesRank) {
Memes memes = findMemesFromMemesId(memesRank.getMemesId());
return MemesInfoResponse.fromMemesAndLikeCount(memes, memesRank.getScore());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.example.memetory.domain.memes.service;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.memetory.domain.memes.dto.MemesRankDto;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class RankingService {
private final String PREFIX = "LIKE_RANKING_DATE::";
private final String POSTFIX_WEEK = "::WEEK";
private final String POSTFIX_MONTH = "::MONTH";
private final Long TOP_TEN = 9L;

private final ZSetOperations<String, Long> rankingZSet;

@Transactional
public void increaseTodayMemesLikeCountFromMemesId(Long memesId) {
String key = PREFIX + LocalDate.now();

increaseMemesLikeCountForToday(key, memesId);
increaseMemesLikeCountForLastWeek(key, memesId);
increaseMemesLikeCountForLastMonth(key, memesId);
}

private void increaseMemesLikeCountForToday(String key, Long memesId) {
rankingZSet.incrementScore(key, memesId, 1);
}

private void increaseMemesLikeCountForLastWeek(String key, Long memesId) {
key += POSTFIX_WEEK;

unionMemesIfKeyNotExists(key, 7);
rankingZSet.incrementScore(key, memesId, 1);
}

private void increaseMemesLikeCountForLastMonth(String key, Long memesId) {
key += POSTFIX_MONTH;

unionMemesIfKeyNotExists(key, 30);
rankingZSet.incrementScore(key, memesId, 1);
}

private void unionMemesIfKeyNotExists(String key, int day) {
if (isNotExistedKey(key)) {
unionMemesFromKeyAndDay(key, day);
}
}

private boolean isNotExistedKey(String key) {
Set<Long> check = rankingZSet.range(key, 0, 1);

return check.isEmpty();
}

private void unionMemesFromKeyAndDay(String key, int day) {
List<String> keyList = new ArrayList<>();
LocalDate today = LocalDate.now();

for (int i = 1; i < day; i++) {
LocalDate date = today.minusDays(i);
keyList.add(PREFIX + date);
}

rankingZSet.unionAndStore(key, keyList, key);
}

@Transactional
public void decreaseTodayMemesLikeCountFromMemesId(Long memesId) {
String key = PREFIX + LocalDate.now();

decreaseMemesLikeCountForToday(key, memesId);
decreaseMemesLikeCountForLastWeek(key, memesId);
decreaseMemesLikeCountForLastMonth(key, memesId);
}

private void decreaseMemesLikeCountForToday(String key, Long memesId) {
rankingZSet.incrementScore(key, memesId, -1);
}

private void decreaseMemesLikeCountForLastWeek(String key, Long memesId) {
key += POSTFIX_WEEK;

unionMemesIfKeyNotExists(key, 7);
rankingZSet.incrementScore(key, memesId, -1);
}

private void decreaseMemesLikeCountForLastMonth(String key, Long memesId) {
key += POSTFIX_MONTH;

unionMemesIfKeyNotExists(key, 30);
rankingZSet.incrementScore(key, memesId, -1);
}

@Transactional(readOnly = true)
public List<MemesRankDto> findTopTenMemesLikeCountForWeek() {
String key = PREFIX + LocalDate.now() + POSTFIX_WEEK;

unionMemesIfKeyNotExists(key, 7);

Set<ZSetOperations.TypedTuple<Long>> rankTuple = rankingZSet.reverseRangeWithScores(key, 0, TOP_TEN);
List<MemesRankDto> result = rankTuple.stream().map(MemesRankDto::of).toList();

return result;
}

@Transactional(readOnly = true)
public List<MemesRankDto> findTopTenMemesLikeCountForMonth() {
String key = PREFIX + LocalDate.now() + POSTFIX_MONTH;

unionMemesIfKeyNotExists(key, 30);

Set<ZSetOperations.TypedTuple<Long>> rankTuple = rankingZSet.reverseRangeWithScores(key, 0, TOP_TEN);
List<MemesRankDto> result = rankTuple.stream().map(MemesRankDto::of).toList();

return result;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
package com.example.memetory.global.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableRedisRepositories
@EnableTransactionManagement
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
Expand All @@ -16,6 +28,29 @@ public class RedisConfig {

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
return new LettuceConnectionFactory(new RedisStandaloneConfiguration(host, port));
}

@Bean(name = "rankingRedisTemplate")
public RedisTemplate<String, Long> rankingRedisTemplate() {
RedisTemplate<String, Long> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Long.class));
redisTemplate.setEnableTransactionSupport(true);

redisTemplate.setConnectionFactory(redisConnectionFactory());

return redisTemplate;
}

@Bean(name = "rankingZSetOperations")
public ZSetOperations<String, Long> rankingZSetOperations(
@Qualifier("rankingRedisTemplate") RedisTemplate<String, Long> redisTemplate) {
return redisTemplate.opsForZSet();
}

@Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}
Loading

0 comments on commit 41f21ee

Please sign in to comment.