-
Notifications
You must be signed in to change notification settings - Fork 0
Feat#11 조회수 #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
The head ref may contain hidden characters: "feat#11-\uC870\uD68C\uC218"
Merged
Feat#11 조회수 #12
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
34fec80
chore: 레디스 연동
Doncham 59ede6b
feat: 조회수 서비스 로직(회원/비회원)
Doncham 13c4e88
feat: 조회수 서비스 단위 테스트
Doncham 620df02
feat: 조회수 서비스 작성
Doncham 45967a9
feat: 레디스 + testContainer 세팅
Doncham f91e64f
feat: 조회수 조회 테스트(회원/비회원)
Doncham fda589b
feat: viewCountSyncService
Doncham 6c54067
feat: 게시글 페이지 조회에 readCount도 반환
Doncham 44174ea
feat: 페이지 목록 조회 수정
Doncham 71b1263
feat: 조회수 화면 표시
Doncham 344c616
feat: 페이지 상세 조회 DTO 생성, 테스트 보완
Doncham 0bf5d27
feat: 풀리퀘 수정
Doncham bd0b669
feat: 페이지 조회 시 redis N+1 문제 최적화
Doncham File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
backend/src/main/java/org/juniortown/backend/config/RedisConfig.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package org.juniortown.backend.config; | ||
|
|
||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.context.annotation.Profile; | ||
| 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.Jackson2JsonRedisSerializer; | ||
| import org.springframework.data.redis.serializer.StringRedisSerializer; | ||
|
|
||
| @Configuration | ||
| @Profile("!test") // test 프로파일이 아닐 때만 활성화 | ||
| public class RedisConfig { | ||
| @Value("${spring.data.redis.host}") | ||
| private String host; | ||
| @Value("${spring.data.redis.port}") | ||
| private int port; | ||
|
|
||
| @Bean | ||
| public RedisConnectionFactory redisConnectionFactory() { | ||
| return new LettuceConnectionFactory(host, port); | ||
| } | ||
|
|
||
| @Bean | ||
| public RedisTemplate<String, Boolean> keyCheckRedisTemplate(RedisConnectionFactory redisConnectionFactory) { | ||
| RedisTemplate<String,Boolean> redisTemplate = new RedisTemplate<>(); | ||
| redisTemplate.setConnectionFactory(redisConnectionFactory); | ||
| redisTemplate.setKeySerializer(new StringRedisSerializer()); | ||
| redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Boolean.class)); | ||
| return redisTemplate; | ||
| } | ||
|
|
||
| @Bean | ||
| public RedisTemplate<String, Long> readCountRedisTemplate(RedisConnectionFactory redisConnectionFactory) { | ||
| RedisTemplate<String,Long> redisTemplate = new RedisTemplate<>(); | ||
| redisTemplate.setConnectionFactory(redisConnectionFactory); | ||
| redisTemplate.setKeySerializer(new StringRedisSerializer()); | ||
| redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Long.class)); | ||
| return redisTemplate; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
backend/src/main/java/org/juniortown/backend/config/WebMvcConfig.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
backend/src/main/java/org/juniortown/backend/interceptor/GuestCookieInterceptor.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package org.juniortown.backend.interceptor; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.web.servlet.HandlerInterceptor; | ||
|
|
||
| import jakarta.servlet.http.Cookie; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
|
|
||
| @Component | ||
| public class GuestCookieInterceptor implements HandlerInterceptor { | ||
| private static final String COOKIE_NAME = "guestId"; | ||
| @Override | ||
| public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { | ||
| Cookie[] cookies = request.getCookies(); | ||
| boolean hasGuestId = false; | ||
| if (cookies != null) { | ||
| for(Cookie c : cookies) { | ||
| if (COOKIE_NAME.equals(c.getName())) { | ||
| hasGuestId = true; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| if(!hasGuestId) { | ||
| String guestId = UUID.randomUUID().toString(); | ||
| Cookie cookie = new Cookie(COOKIE_NAME, guestId); | ||
| cookie.setPath("/"); | ||
| cookie.setMaxAge(60 * 60 * 24 * 15); // 15 days | ||
| cookie.setHttpOnly(true); | ||
| response.addCookie(cookie); | ||
| } | ||
| return true; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
backend/src/main/java/org/juniortown/backend/post/dto/response/PostDetailResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package org.juniortown.backend.post.dto.response; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public class PostDetailResponse { | ||
| private final Long id; | ||
| private final String title; | ||
| private final String content; | ||
| private final Long userId; | ||
| private final String userName; | ||
| private final Long likeCount; | ||
| private final Boolean isLiked; | ||
| private final Long readCount; | ||
| private final LocalDateTime createdAt; | ||
| private final LocalDateTime updatedAt; | ||
| private final LocalDateTime deletedAt; | ||
|
|
||
| @Builder | ||
| public PostDetailResponse(Long id, String title, String content, Long userId, String userName, Long likeCount, | ||
| Boolean isLiked, Long readCount, LocalDateTime createdAt, LocalDateTime updatedAt, LocalDateTime deletedAt) { | ||
| this.id = id; | ||
| this.title = title; | ||
| this.content = content; | ||
| this.userId = userId; | ||
| this.userName = userName; | ||
| this.likeCount = likeCount; | ||
| this.isLiked = isLiked; | ||
| this.readCount = readCount; | ||
| this.createdAt = createdAt; | ||
| this.updatedAt = updatedAt; | ||
| this.deletedAt = deletedAt; | ||
| } | ||
| public static PostDetailResponse from(PostResponse postResponse) { | ||
| return PostDetailResponse.builder() | ||
| .id(postResponse.getId()) | ||
| .title(postResponse.getTitle()) | ||
| .content(postResponse.getContent()) | ||
| .userId(postResponse.getUserId()) | ||
| .userName(postResponse.getUserName()) | ||
| .likeCount(postResponse.getLikeCount()) | ||
| .isLiked(postResponse.getIsLiked()) | ||
| .readCount(postResponse.getReadCount()) | ||
| .createdAt(postResponse.getCreatedAt()) | ||
| .updatedAt(postResponse.getUpdatedAt()) | ||
| .deletedAt(postResponse.getDeletedAt()) | ||
| .build(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
동시성 고려사항을 확인해주세요.
addReadCount메서드가 단순한 증가 로직을 구현하고 있습니다. Redis 동기화 서비스에서 호출될 때 동시성 문제가 발생할 수 있는지 확인이 필요합니다.다음 스크립트로 이 메서드의 사용 패턴을 확인해보겠습니다:
🏁 Script executed:
Length of output: 39
🏁 Script executed:
Length of output: 563
동시성 이슈 확인 및 원자성 보장 필요
addReadCount(Long redisReadCount)메서드가 단순히 인스턴스 필드에 덧셈만 수행하고 있어, 여러 쓰레드(또는 트랜잭션)에서 동시에 같은 게시물의readCount를 갱신할 경우 최종 카운트가 누락되거나 덮어쓰여질 가능성이 있습니다.호출 패턴 확인 결과, 아래 위치에서 사용 중입니다:
해결 방안 예시:
@Version필드를 추가해 JPA 낙관적 락 적용@Modifying+ JPQL/Native 쿼리로UPDATE post SET read_count = read_count + :count WHERE id = :id처럼 DB 레벨에서 원자적 증가 수행synchronized) 또는 분산 락(Redis Lock 등) 적용위 방안 중 하나를 선택해 동시성 문제를 방지해주세요.
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addReadCount는 5분마다 실행되는 @scheduled 작업이고 redis에서 받아오는 응답을 더할 때 사용되는데 이는 동기적으로 동작해서 동시성 이슈는 발생하지 않는다고 판단. Redisson을 통해 스케줄링 작업이 여러 인스턴스에서 동시에 실행되지 않기에 하나의 스레드에서만 스케줄링 작업이 발생함을 보장함.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.