카테고리 없음

Redis 사용 기록 겸 후기 (카드 조회, 조회 인기순)

llmmhh 2024. 10. 21. 22:47
public CardResponseDto cardRead(Long cardId, AuthUser user) {

 

// Redis Key 설정
String viewKey = "card:" + cardId + "views"; // 카드 조회수 키
String userKey = "user:" + user.getUserId() + ":card:" + cardId + ":";
String cardInfoKey = "card_info" + cardId; // 캐시에 저장 키 (인기순위 조회용)
String rankingKey = "popular_cards"; // 인기 카드 랭킹 키

 

조회수가 오르는지 확인하기 위한 초기값(현재 카드 조회수)

// 현재 카드 조회수
Integer currentViews = redisTemplate.opsForValue().get(viewKey);
if (currentViews == null) {
    currentViews = 0; // 초기값 설정
}

 

유저 중복 체크 후 오늘 하루 조회하지 않은 유저일 경우 조회 수 증가

1. opsForvalue().set 으로 1 늘리고

2. opsForZset().incrementScore 로 1 늘린다 (인기 랭킹)

 // 유저 중복 조회 체크
        if (Boolean.FALSE.equals(redisTemplate.hasKey(userKey))){
            // 오늘 첫 조회 = 조회수 증가
            int updatedViews = redisTemplate.opsForValue().increment(viewKey).intValue();
            System.out.println("updatedViews = " + updatedViews);
//             유저 조회 기록 저장 (endTime 만큼 유효 = 다음날 00시까지 남은 초)
            long endTime = getSecondsUntilMidnight();
            redisTemplate.opsForValue().set(userKey, 1,endTime, TimeUnit.SECONDS);
            redisTemplate.opsForZSet().incrementScore(rankingKey, cardId.intValue(), 1);

            // 조회수 변경 시 랭킹 점수 업데이트
            updateRanking(cardId,updatedViews);
        }
// 카드 조회수 인기랭크 조회
public  List<Map<String,String>> getCardsRanking(int count){
    // 인기 카드 랭킹 키
    String rankingKey = "popular_cards";
    // 인기 카드 설정 0 번째 부터 ~ count -1 번째까지
    Set<Integer> topCardIds = redisTemplate.opsForZSet().reverseRange(rankingKey, 0, count-1);

    // 인기카드 리스트
    List<Map<String,String>> popularCards = new ArrayList<>();

    if (topCardIds != null && !topCardIds.isEmpty()){
        for (Integer cardId : topCardIds){

            //  Card 정보 가져오기
            String cardInfoKey = "card_info" + cardId;
            String title = (String) redisTemplate.opsForHash().get(cardInfoKey,"title");
            Integer views = redisTemplate.opsForValue().get("card:" + cardId + "views");
            // Id 와 제목을 Map 에 저장
            Map<String, String> cardInfo = new HashMap<>();
            cardInfo.put("card_id",cardId.toString());
            cardInfo.put("title", title);
            cardInfo.put("views", views != null ? String.valueOf(views) : "0"); // 조회수가 null 이면 0으로 설정


            popularCards.add(cardInfo);
        }
    }
    return popularCards;
}

 

cardInfo 에 조회수 랭킹보여줄 때 같이 보여줄 정보(DB에서 가져올 때 Dto처럼) 담아서 리턴

카드랭킹에서 반영되는 조회수 

// 랭킹 업데이트 로직
private void updateRanking(Long cardId, int updatedviews){
    String rankingKey = "popular_cards";
    Double currentviews = redisTemplate.opsForZSet().score(rankingKey, cardId);

    // 현재 랭킹 점수와 비교하여 업데이트
    if(currentviews == null || updatedviews > currentviews){
        redisTemplate.opsForZSet().add(rankingKey, cardId.intValue(), updatedviews);
    }
}

 

레디스 TTL을 적용시키기 위해 자정까지 남은시간 계산하는 로직

// 현재 시간부터 자정까지 남은 초 계산
private long getSecondsUntilMidnight() {
    LocalDateTime now = LocalDateTime.now(); // 지금
    LocalDateTime midnight = now.toLocalDate().plusDays(1).atStartOfDay(); // 내일 00시
    return Duration.between(now, midnight).getSeconds(); // 남은 시간 단위:초 (내일 00시 - 지금)
}

 

 

레디스 기본 세팅 

package com.sparta.tse.config;

import org.springframework.cache.annotation.EnableCaching;
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.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching // Spring 에서 캐시 기능을 활성화
public class RedisConfig {

    // Redis 서버랑 연결
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        // "localhost"와 6379는 Redis 서버의 기본 호스트와 포트입니다.
        return new LettuceConnectionFactory("localhost", 6379);
    }

    // RedisTemplate<String, Integer> 설정
    @Bean
    public RedisTemplate<String, Integer> redisTemplate() {
        // RedisTemplate<Key,Value>
        RedisTemplate<String, Integer> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory());

        // Key를 String으로 직렬화
        template.setKeySerializer(new StringRedisSerializer());
        // Value를 Integer로 직렬화
        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));

        return template;
    }
}