Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,13 @@ public ResponseEntity<PostUpdateResponse> updatePost(
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))),
})
@GetMapping("/{postId}")
public ResponseEntity<PostReadResponse> readPost(@PathVariable Long postId) {
PostReadResponse response = postService.getPost(postId);
public ResponseEntity<PostReadResponse> readPost(@PathVariable Long postId,
@AuthenticationPrincipal CustomUserDetails customUserDetails) {
Long currentAuthUserId = null;
if (customUserDetails != null) {
currentAuthUserId = customUserDetails.getId();
}
PostReadResponse response = postService.getPost(postId, currentAuthUserId);
return ResponseEntity.status(HttpStatus.OK).body(response);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ private PostReadResponse(Long id, String author, String title, String content, L

@Builder

public static PostReadResponse toDto(Post post, String author, List<PostImage> images) {
public static PostReadResponse toDto(Post post, String author, List<PostImage> images, Long viewCount) {
return PostReadResponse.builder()
.id(post.getId())
.author(author)
.title(post.getTitle())
.content(post.getContent())
.likeCount(post.getLikeCount())
.viewCount(post.getViewCount())
.viewCount(viewCount)
.images(images.stream().map(PostImageReadResponse::toDto).toList())
.createdDate(post.getCreatedDate())
.lastModifiedDate(post.getLastModifiedDate())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.backendboard.domain.post.respository;

import java.util.Map;

public interface ViewCountRedisRepository {
void delete();

Map<Object, Object> getEntries();

void incrementCount(String postId);

Long getIncrementCount(String postId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.backendboard.domain.post.respository;

import java.util.Map;
import java.util.Optional;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;

import lombok.RequiredArgsConstructor;

@Repository
@RequiredArgsConstructor
public class ViewCountRedisRepositoryImpl implements ViewCountRedisRepository {
private static final String KEY = "post:view:count";

private final RedisTemplate<String, Object> redisTemplate;

@Override
public void delete() {
redisTemplate.delete(KEY);
}

@Override
public Map<Object, Object> getEntries() {
return redisTemplate.opsForHash().entries(KEY);
}

@Override
public void incrementCount(String postId) {
redisTemplate.opsForHash().increment(KEY, postId, 1);
}

@Override
public Long getIncrementCount(String postId) {
return Optional.ofNullable(redisTemplate.opsForHash().get(KEY, postId))
.map(value -> ((Number)value).longValue())
.orElse(0L);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.backendboard.domain.post.respository.ViewCountRedisRepository;
import com.backendboard.domain.postlike.repository.PostLikeRedisRepository;

import lombok.RequiredArgsConstructor;
Expand All @@ -18,11 +19,13 @@
@RequiredArgsConstructor
public class PostScheduler {
private static final String UPDATE_LIKE_COUNT_SQL = "UPDATE post SET like_count = ? WHERE id = ?";
private static final String UPDATE_VIEW_COUNT_SQL = "UPDATE post SET view_count = view_count + ? WHERE id = ?";

private final PostLikeRedisRepository postLikeRedisRepository;
private final ViewCountRedisRepository viewCountRedisRepository;
private final JdbcTemplate jdbcTemplate;

@Scheduled(fixedDelay = 300_000)
@Scheduled(fixedDelay = 180_000)
public void syncLikeCount() {
Map<Object, Object> entries = postLikeRedisRepository.getEntries();
List<Object[]> batchArgs = new ArrayList<>();
Expand All @@ -36,4 +39,19 @@ public void syncLikeCount() {
jdbcTemplate.batchUpdate(UPDATE_LIKE_COUNT_SQL, batchArgs);
postLikeRedisRepository.delete();
}

@Scheduled(fixedDelay = 60_000)
public void syncViewCount() {
Map<Object, Object> entries = viewCountRedisRepository.getEntries();
List<Object[]> batchArgs = new ArrayList<>();

for (Map.Entry<Object, Object> entry : entries.entrySet()) {
String postId = (String)entry.getKey();
long viewCount = ((Number)entry.getValue()).longValue();
batchArgs.add(new Object[] {viewCount, Long.valueOf(postId)});
}

jdbcTemplate.batchUpdate(UPDATE_VIEW_COUNT_SQL, batchArgs);
viewCountRedisRepository.delete();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public interface PostService {

PostUpdateResponse updatePost(PostUpdateRequest request, Long postId, Long authUserId);

PostReadResponse getPost(Long postId);
PostReadResponse getPost(Long postId, Long currentAuthUserId);

void deletePost(Long postId, Long authUserId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.backendboard.domain.post.dto.PostUpdateResponse;
import com.backendboard.domain.post.entity.Post;
import com.backendboard.domain.post.respository.PostRepository;
import com.backendboard.domain.post.respository.ViewCountRedisRepository;
import com.backendboard.domain.postimage.entity.PostImage;
import com.backendboard.domain.postimage.repository.PostImageRepository;
import com.backendboard.domain.user.entity.User;
Expand All @@ -35,6 +36,7 @@ public class PostServiceImpl implements PostService {
private final PostRepository postRepository;
private final UserRepository userRepository;
private final PostImageRepository postImageRepository;
private final ViewCountRedisRepository viewCountRedisRepository;

@Transactional
@Override
Expand Down Expand Up @@ -73,13 +75,23 @@ public PostUpdateResponse updatePost(PostUpdateRequest request, Long postId, Lon
}

@Override
public PostReadResponse getPost(Long postId) {
public PostReadResponse getPost(Long postId, Long currentAuthUserId) {
Post post = postRepository.findById(postId).orElseThrow(() -> new CustomException(CustomError.POST_NOT_FOUND));
User user = userRepository.findById(post.getUserId())
.orElseThrow(() -> new CustomException(CustomError.USER_NOT_FOUND));

if (user.getAuthUser().getId() != currentAuthUserId) {
incrementViewCount(postId);
}

Long viewCount = post.getViewCount() + viewCountRedisRepository.getIncrementCount(postId.toString());
List<PostImage> images = postImageRepository.findByPostId(postId);
return PostReadResponse.toDto(post, user.getNickname(), images);
return PostReadResponse.toDto(post, user.getNickname(), images, viewCount);
}

private void incrementViewCount(Long postId) {
String postIdKey = postId.toString();
viewCountRedisRepository.incrementCount(postIdKey);
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ public interface PostLikeRedisRepository {
void delete();

Map<Object, Object> getEntries();

void incrementCount(String postId, Long delta);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
import org.springframework.stereotype.Repository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Repository
@RequiredArgsConstructor
public class PostLikeRedisRepositoryImpl implements PostLikeRedisRepository {
Expand Down Expand Up @@ -38,4 +36,9 @@ public void delete() {
public Map<Object, Object> getEntries() {
return redisTemplate.opsForHash().entries(KEY);
}

@Override
public void incrementCount(String postId, Long delta) {
redisTemplate.opsForHash().increment(KEY, postId, delta);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ public class PostLikeServiceImpl implements PostLikeService {
public PostLikeStatusResponse toggleLike(Long authUserId, Long postId) {
User user = userRepository.getByAuthUserId(authUserId);
boolean isLiked = postLikeRepository.deleteByUserIdAndPostId(user.getId(), postId) == 0;
String postIdKey = postId.toString();
long delta = -1L;

if (isLiked) {
PostLike postLike = PostLike.create(user.getId(), postId);
postLikeRepository.save(postLike);
delta = 1L;
}
postLikeRedisRepository.incrementCount(postIdKey, delta);
return PostLikeStatusResponse.toDto(isLiked);
}

Expand Down
7 changes: 3 additions & 4 deletions frontend/src/main.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import {createRoot} from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import {StrictMode} from "react";

createRoot(document.getElementById('root')).render(
<StrictMode>
<App/>
</StrictMode>,
//<StrictMode>
<App/>
//</StrictMode>,
)
49 changes: 28 additions & 21 deletions frontend/src/pages/post/PostDetail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,35 +35,42 @@ const PostDetail = () => {
setIsLoggedIn(!!user && !!accessToken);
}, []);

// 1. 게시글 상세(조회수 포함) - 한 번만 호출
useEffect(() => {
api.get(`/posts/${id}`)
.then(res => {
if (res.status !== 200)
throw new Error('게시글을 불러올 수 없습니다.');
return res.data;
})
.then(data => {
setPost(data);
api.get(`/post-likes/${id}/count`).then(res => {
if (res.status !== 200) {
throw new Error('좋아요 상태를 불러올 수 없습니다.');
}
setLikeCount(res.data.count);
})

// 로그인한 경우 좋아요 상태 확인
if (isLoggedIn) {
api.get(`/post-likes/${id}/status`).then(res => {
if (res.status !== 200) {
throw new Error('좋아요 상태를 불러올 수 없습니다.');
}
setIsLiked(res.data.liked);
})
// 좋아요 상태 확인 로직 추가
}
setPost(res.data);
})
.catch(err => setError(err.message))
.finally(() => setLoading(false));
}, [id]);

// 2. 좋아요 개수는 id만 의존 (로그인 상관없이 보여주려면)
useEffect(() => {
api.get(`/post-likes/${id}/count`)
.then(res => {
if (res.status !== 200)
throw new Error('좋아요 개수를 불러올 수 없습니다.');
setLikeCount(res.data.count);
})
.catch(err => setError(err.message));
}, [id]);

// 3. 좋아요 상태는 로그인 상태와 id에 따라 호출
useEffect(() => {
if (isLoggedIn) {
api.get(`/post-likes/${id}/status`)
.then(res => {
if (res.status !== 200)
throw new Error('좋아요 상태를 불러올 수 없습니다.');
setIsLiked(res.data.liked);
})
.catch(err => setError(err.message));
} else {
setIsLiked(false); // 로그아웃 시 상태 초기화
}
}, [id, isLoggedIn]);

// 댓글 불러오기
Expand Down