본문 바로가기
Spring

redis 사용 일간, 주간 월간 조회수 구현

by windy7271 2024. 2. 25.
728x90
반응형

 

프로젝트 도중 일간, 조회수, 주간 조회수, 월간 조회수를 를 구현하고자 했습니다. 

 

게시글을 누를때마다 post에 칼럼인 view카운트를 늘려주고 Views 테이블에 postid와 userid,게시글 클릭 한 시간 을 담으려고 했었는데

그렇게 되면 어뷰징과, 게시글 누를때마다 실시간으로 Insert 문이 발생돼서 효율적이지 않다고 생각돼 레디스에 Useremail 이 key이고 

value가 유저가 그 날 하루에 조회한 postid가 담기게 했습니다 예를들어 song@naver.com으로 로그인해서 1,3,5,7,9,10 번을 조회하면

레디스에는 song@naver.com : 1_3_5_7_9_10 으로 구분자 _ 를 기준으로 해서 들어가게 했습니다.

24시가되면 스케쥴러가 돌면서 Views테이블로 각 게시글마다 조회한 수로 들어가게 됩니다.

그럼 postid는 1개이고 views카운트도 1개라서 postId별로 1개의 insert문이 발생하지 않습니다. 

 

 

1. redis 설정

 

@RequiredArgsConstructor
@Service
public class RedisUtil {
    private final StringRedisTemplate template;

    public String getData(String key) {
        ValueOperations<String, String> valueOperations = template.opsForValue();
        return valueOperations.get(key);
    }

    public void setDataExpire(String key, String value) {
        ValueOperations<String, String> valueOperations = template.opsForValue();
        valueOperations.set(key, value);
    }
    public void deleteData(String key) {
        template.delete(key);
    }

 

getdata 불러오기, setdata 만료 설정, deletedata 삭제

 

저는 조회할때 조회수가 올라가도록 했습니다.

 

1.Controller

@GetMapping("/{id}/detail")
public DefaultResponse<PostDetailResDto> postDetail(@PathVariable Long id){
    PostDetailResDto postDetailResDto = postService.findPostDetail(id);
    return new DefaultResponse<>(postDetailResDto);
}

 

2.service

public PostDetailResDto findPostDetail(Long id) {
    Post post = postRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("검색하신 ID의 게시글 없습니다."));
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    String userEmail = authentication.getName();

    String viewCount = redisUtil.getData(userEmail);
    if (viewCount == null || !viewCount.contains(id + "_")) {
        viewCount = (viewCount == null) ? id + "_" : viewCount + id + "_";
        redisUtil.setDataExpire(userEmail, viewCount);
        post.updateView();
    }

    return PostDetailResDto.ToPostDto(post);
}

useremail을 찾아서

레디스에 들어있는지 확인한 후 없으면 그대로 들어갑니다.

이미 본 post이면 안 들어갑니다.

그리고 전체 조회수인 Post.updateView를 해줍니다.

 

3. scheduler

    @Scheduled(cron = "0 0 0 * * *")
    public void Datainit() {
        Set<String> AllViewEmail = stringRedisTemplate.keys("*@*.*");
        Map<Long, Long> postIdAllUserIds = new HashMap<>();
//        key = email, value = ViewdPostByEmail
        for (String email : AllViewEmail) {
            String[] PostIdViewdByEmail= stringRedisTemplate.opsForValue().get(email).split("_");
            for (String PostIdstr : PostIdViewdByEmail) {
                Long postId = Long.parseLong(PostIdstr);
                postIdAllUserIds.compute(postId, (key, value) -> (value == null) ? 1 : value + 1);
            }
        }
        // save db
        for (Map.Entry<Long, Long> entry : postIdAllUserIds.entrySet()) {
            Long postId = entry.getKey();
            Post post = postRepository.findById(postId).orElseThrow(() -> new EntityNotFoundException("게시글을 찾을 수 없습니다."));

            Long viewsCount = entry.getValue();
            Views init = Views.Init(post, viewsCount);
            viewsRepository.save(init);
        }
//         delete userEmail in redis
        stringRedisTemplate.delete(AllViewEmail);
    }
}

 

하루마다 디비에 넣어주는 작업입니다.

처음에 "*@*.*" 은 다른 키가 들어올 수도있어서 이메일값은 @이랑. 으로 이루어져있어서 정규식 같은 느낌입니다

 

 

대충이런식으로 들어가는겁니다. 테스트라서 1분마다 돌려놨어요

 

 

그리고 주간 월간 일간을 불러올때 jpql을 사용해서 불러왔는데. 나중에 캐싱? 을 해서 해보고싶습니다. 일단은

@Query(nativeQuery = true, value = "SELECT * FROM (" +
        "SELECT v.post_id " +
        "FROM Views v " +
        "WHERE v.created_at > :since " +
        "ORDER BY v.views_count " +
        "DESC LIMIT 10) AS sub_query")
List<Long> findTop10PostIdsByCreatedAtAfterOrderByViewsCountDesc(@Param("since") LocalDateTime since);

이런식으로 해서 날짜를 넘겨주면서.  피지컬로 데베에서 가져왔습니다.

 

반응형

댓글