-
Notifications
You must be signed in to change notification settings - Fork 1
[feat] 특정 책으로 작성된 피드 목록 조회 api 개발 #230
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
Changes from all commits
0f6c3c6
0d3a976
ae11b9b
144aa91
80ff395
441b307
11865c4
92dcef2
983ac2f
5326622
aa17057
4dd26d3
aca5594
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package konkuk.thip.feed.adapter.in.web.response; | ||
|
|
||
| import lombok.Builder; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Builder | ||
| public record FeedRelatedWithBookResponse( | ||
| List<FeedRelatedWithBookDto> feeds, | ||
| String nextCursor, | ||
| boolean isLast | ||
| ) { | ||
| public record FeedRelatedWithBookDto( | ||
| Long feedId, | ||
| Long creatorId, | ||
| boolean isWriter, | ||
| String creatorNickname, | ||
| String creatorProfileImageUrl, | ||
| String aliasName, | ||
| String aliasColor, | ||
| String postDate, | ||
| String isbn, | ||
| String bookTitle, | ||
| String bookAuthor, | ||
| String contentBody, | ||
| String[] contentUrls, | ||
| int likeCount, | ||
| int commentCount, | ||
| boolean isSaved, | ||
| boolean isLiked | ||
| ) {} | ||
|
|
||
| public static FeedRelatedWithBookResponse of(List<FeedRelatedWithBookDto> feeds, String nextCursor, boolean isLast) { | ||
| return new FeedRelatedWithBookResponse(feeds, nextCursor, isLast); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,17 @@ | ||
| package konkuk.thip.feed.adapter.out.persistence.repository; | ||
|
|
||
| import com.querydsl.core.Tuple; | ||
| import com.querydsl.core.types.dsl.BooleanExpression; | ||
| import com.querydsl.core.types.dsl.CaseBuilder; | ||
| import com.querydsl.core.types.dsl.Expressions; | ||
| import com.querydsl.core.types.dsl.NumberExpression; | ||
| import com.querydsl.core.types.dsl.*; | ||
| import com.querydsl.jpa.JPAExpressions; | ||
| import com.querydsl.jpa.impl.JPAQueryFactory; | ||
| import jakarta.annotation.Nullable; | ||
| import konkuk.thip.book.adapter.out.jpa.QBookJpaEntity; | ||
| import konkuk.thip.common.entity.StatusType; | ||
| import konkuk.thip.feed.adapter.out.jpa.*; | ||
| import konkuk.thip.feed.application.port.out.dto.FeedQueryDto; | ||
| import konkuk.thip.feed.application.port.out.dto.QFeedQueryDto; | ||
| import konkuk.thip.feed.application.port.out.dto.QTagCategoryQueryDto; | ||
| import konkuk.thip.feed.application.port.out.dto.TagCategoryQueryDto; | ||
| import konkuk.thip.feed.application.port.out.dto.FeedQueryDto; | ||
| import konkuk.thip.room.adapter.out.jpa.QCategoryJpaEntity; | ||
| import konkuk.thip.user.adapter.out.jpa.QAliasJpaEntity; | ||
| import konkuk.thip.user.adapter.out.jpa.QFollowingJpaEntity; | ||
|
|
@@ -227,7 +227,6 @@ public List<TagCategoryQueryDto> findAllTags() { | |
| .orderBy(c.categoryId.asc(), t.tagId.asc()) //Id 순 정렬 | ||
| .fetch(); | ||
| } | ||
|
|
||
| private List<Long> fetchMyFeedIdsByCreatedAt(Long userId, LocalDateTime lastCreatedAt, int size) { | ||
| return jpaQueryFactory | ||
| .select(feed.postId) | ||
|
|
@@ -283,4 +282,105 @@ private FeedQueryDto toDto(FeedJpaEntity e, Integer priority) { | |
| .isPriorityFeed(isPriorityFeed) | ||
| .build(); | ||
| } | ||
|
|
||
| /** | ||
| * 책 ISBN으로 피드를 조회하고, 좋아요 수 기준으로 정렬하여 페이징 처리 | ||
| */ | ||
| @Override | ||
| public List<FeedQueryDto> findFeedsByBookIsbnOrderByLikeCount( | ||
| String isbn, | ||
| Long userId, | ||
| @Nullable LocalDateTime lastCreatedAt, | ||
| @Nullable Integer lastLikeCount, | ||
| int size | ||
| ) { | ||
| BooleanExpression where = feedByBooksFilter(isbn, userId); | ||
| if (lastLikeCount != null && lastCreatedAt != null) { | ||
| // likeCount DESC → createdAt DESC | ||
| where = where.and( | ||
| feed.likeCount.lt(lastLikeCount) | ||
| .or(feed.likeCount.eq(lastLikeCount) | ||
| .and(feed.createdAt.lt(lastCreatedAt))) | ||
| ); | ||
| } | ||
|
|
||
| return jpaQueryFactory | ||
| .select(toQueryDto()) | ||
| .from(feed) | ||
| .join(feed.userJpaEntity, user) | ||
| .join(feed.bookJpaEntity, book) | ||
| .where(where) | ||
| .orderBy(feed.likeCount.desc(), feed.createdAt.desc()) | ||
| .limit(size + 1) | ||
| .fetch(); | ||
| } | ||
|
|
||
| /** | ||
| * 책 ISBN으로 피드를 조회하고, 최신순 정렬하여 페이징 처리 | ||
| */ | ||
| @Override | ||
| public List<FeedQueryDto> findFeedsByBookIsbnOrderByCreatedAt( | ||
| String isbn, | ||
| Long userId, | ||
| @Nullable LocalDateTime lastCreatedAt, | ||
| int size | ||
| ) { | ||
| BooleanExpression where = feedByBooksFilter(isbn, userId); | ||
| if (lastCreatedAt != null) { | ||
| // createdAt DESC | ||
| where = where.and( | ||
| feed.createdAt.lt(lastCreatedAt) | ||
| ); | ||
| } | ||
|
|
||
| return jpaQueryFactory | ||
| .select(toQueryDto()) | ||
| .from(feed) | ||
| .join(feed.userJpaEntity, user) | ||
| .join(feed.bookJpaEntity, book) | ||
| .where(where) | ||
| .orderBy(feed.createdAt.desc()) | ||
| .limit(size + 1) | ||
| .fetch(); | ||
| } | ||
|
|
||
| private QFeedQueryDto toQueryDto() { | ||
| return new QFeedQueryDto( | ||
| feed.postId, | ||
| feed.userJpaEntity.userId, | ||
| user.nickname, | ||
| user.aliasForUserJpaEntity.imageUrl, | ||
| user.aliasForUserJpaEntity.value, | ||
| feed.createdAt, | ||
| book.isbn, | ||
| book.title, | ||
| book.authorName, | ||
| feed.content, | ||
| // 서브쿼리로 N:1 방지 | ||
| JPAExpressions | ||
| .select(contentUrlAggExpr()) | ||
| .from(content) | ||
| .where(content.postJpaEntity.postId.eq(feed.postId)), | ||
|
Comment on lines
+359
to
+363
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호 이렇게 n+1 문제를 해결하면서 QueryProjection을 사용할 수 있군요!! 좋습니다 |
||
| feed.likeCount, | ||
| feed.commentCount, | ||
| feed.isPublic, | ||
| Expressions.nullExpression() | ||
| ); | ||
| } | ||
|
|
||
| // contentUrl을 GROUP_CONCAT으로 묶어서 반환하는 표현식 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호 좋습니다 굿굿👍🏻👍🏻 |
||
| private StringExpression contentUrlAggExpr() { | ||
| return Expressions.stringTemplate( | ||
| "group_concat({0})", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 반환할 url 순서를 보장할 수도 있다고 하는데, 이건 저희가 곧 contents 테이블을 제거할거니 고려하지 않아도 될 것 같네요!!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 얍얍 |
||
| content.contentUrl | ||
| ); | ||
| } | ||
|
Comment on lines
+372
to
+377
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainGROUP_CONCAT 사용 시 주의사항을 고려하세요. GROUP_CONCAT은 MySQL의 다음 스크립트로 현재 MySQL 설정값과 실제 데이터를 확인해보세요: 필요시 다음과 같은 방법으로 개선할 수 있습니다:
🏁 Script executed: #!/bin/bash
# MySQL GROUP_CONCAT 설정 확인 및 콘텐츠 URL 길이 검증
# application.yml 또는 properties 파일에서 DB 설정 정보 확인
echo "=== 데이터베이스 설정 파일 확인 ==="
fd -e yml -e yaml -e properties | xargs rg -n "group_concat|GROUP_CONCAT" || echo "GROUP_CONCAT 관련 설정 없음"
# 테스트 코드에서 많은 콘텐츠를 가진 피드 테스트 케이스 확인
echo -e "\n=== 콘텐츠 URL 관련 테스트 확인 ==="
fd -e java -g "*Test*.java" | xargs rg -n "contentUrl|content.*add|addContent" -A 3 -B 3Length of output: 52422 group_concat_max_len 기본값 설정 누락 확인됨 application.yml/properties에서 • 대상 위치
• 해결 방안 제안
운영 DB 및 테스트 환경에서 최대 길이를 확인 후 적절히 설정을 적용해주세요. |
||
|
|
||
| // 필터링 조건: 책 ISBN과 사용자 ID를 제외한 다른 사용자 공개 피드 | ||
| private BooleanExpression feedByBooksFilter(String isbn, Long userId) { | ||
| return feed.status.eq(StatusType.ACTIVE) | ||
| .and(feed.bookJpaEntity.isbn.eq(isbn)) | ||
| .and(feed.userJpaEntity.userId.ne(userId)) | ||
| .and(feed.isPublic.eq(true)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package konkuk.thip.feed.application.port.in; | ||
|
|
||
| import konkuk.thip.feed.adapter.in.web.response.FeedRelatedWithBookResponse; | ||
| import konkuk.thip.feed.application.port.in.dto.FeedRelatedWithBookQuery; | ||
|
|
||
| public interface FeedRelatedWithBookUseCase { | ||
|
|
||
| FeedRelatedWithBookResponse getFeedsByBook(FeedRelatedWithBookQuery query); | ||
| } |
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.
p3 : 좋아요 수가 동일할 경우, feedId가 아니라 createdAt 을 두번째 커서 기준으로 잡으신 이유가 있을까요?? 단순 궁금
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.
두번째 정렬 기준과 너무 다른 순서를 보여줄 필요가 없지 않나 라는 생각에 그냥 createdAt을 두번째 커서로 잡았던 것 같아요!