프로젝트 도중 일간, 조회수, 주간 조회수, 월간 조회수를 를 구현하고자 했습니다.
게시글을 누를때마다 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);
이런식으로 해서 날짜를 넘겨주면서. 피지컬로 데베에서 가져왔습니다.
댓글