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;
}
}