From 4530265a544c52a4977425435a28b3efb730f679 Mon Sep 17 00:00:00 2001 From: Eunjin3395 Date: Mon, 9 Sep 2024 01:12:26 +0900 Subject: [PATCH] =?UTF-8?q?:sparkles:=20[Feat]=20=EC=B1=84=ED=8C=85?= =?UTF-8?q?=EB=B0=A9=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/chat/ChatController.java | 7 +- .../com/gamegoo/dto/chat/ChatResponse.java | 13 ++++ .../chat/MemberChatroomRepositoryCustom.java | 6 +- .../MemberChatroomRepositoryCustomImpl.java | 64 +++++++++++++++++-- .../service/chat/ChatQueryService.java | 30 ++++++--- 5 files changed, 102 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/gamegoo/controller/chat/ChatController.java b/src/main/java/com/gamegoo/controller/chat/ChatController.java index 9da9723f..d0cd0c32 100644 --- a/src/main/java/com/gamegoo/controller/chat/ChatController.java +++ b/src/main/java/com/gamegoo/controller/chat/ChatController.java @@ -45,11 +45,14 @@ public ApiResponse> getChatroomUuid() { } @Operation(summary = "채팅방 목록 조회 API", description = "회원이 속한 채팅방 목록을 조회하는 API 입니다.") + @Parameter(name = "cursor", description = "페이징을 위한 커서, 이전 페이지의 마지막 채팅방의 lastMsgTimestamp입니다. 13자리 timestamp integer를 보내주세요.") @GetMapping("/member/chatroom") - public ApiResponse> getChatroom() { + public ApiResponse getChatroom( + @RequestParam(name = "cursor", required = false) Long cursor + ) { Long memberId = JWTUtil.getCurrentUserId(); - return ApiResponse.onSuccess(chatQueryService.getChatroomList(memberId)); + return ApiResponse.onSuccess(chatQueryService.getChatroomList(memberId, cursor)); } @Operation(summary = "특정 회원과 채팅방 시작 API", description = "특정 대상 회원과의 채팅방을 시작하는 API 입니다.\n\n" + diff --git a/src/main/java/com/gamegoo/dto/chat/ChatResponse.java b/src/main/java/com/gamegoo/dto/chat/ChatResponse.java index f246fd01..c1e54d03 100644 --- a/src/main/java/com/gamegoo/dto/chat/ChatResponse.java +++ b/src/main/java/com/gamegoo/dto/chat/ChatResponse.java @@ -10,6 +10,18 @@ public class ChatResponse { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ChatroomViewListDTO { + + List chatroomViewDTOList; + Integer list_size; + Boolean has_next; + Long next_cursor; + } + @Builder @Getter @NoArgsConstructor @@ -28,6 +40,7 @@ public static class ChatroomViewDTO { String lastMsg; String lastMsgAt; Integer notReadMsgCnt; + Long lastMsgTimestamp; } @Builder diff --git a/src/main/java/com/gamegoo/repository/chat/MemberChatroomRepositoryCustom.java b/src/main/java/com/gamegoo/repository/chat/MemberChatroomRepositoryCustom.java index 5c82dec6..17fa8551 100644 --- a/src/main/java/com/gamegoo/repository/chat/MemberChatroomRepositoryCustom.java +++ b/src/main/java/com/gamegoo/repository/chat/MemberChatroomRepositoryCustom.java @@ -2,10 +2,12 @@ import com.gamegoo.domain.chat.MemberChatroom; import java.util.List; +import org.springframework.data.domain.Slice; public interface MemberChatroomRepositoryCustom { - List findActiveMemberChatroomOrderByLastChat(Long memberId); - + Slice findActiveMemberChatroomByCursorOrderByLastChat(Long memberId, + Long cursor, Integer pageSize); + List findAllActiveMemberChatroom(Long memberId); } diff --git a/src/main/java/com/gamegoo/repository/chat/MemberChatroomRepositoryCustomImpl.java b/src/main/java/com/gamegoo/repository/chat/MemberChatroomRepositoryCustomImpl.java index c3f4f086..cad1bfab 100644 --- a/src/main/java/com/gamegoo/repository/chat/MemberChatroomRepositoryCustomImpl.java +++ b/src/main/java/com/gamegoo/repository/chat/MemberChatroomRepositoryCustomImpl.java @@ -5,8 +5,10 @@ import static com.gamegoo.domain.chat.QMemberChatroom.memberChatroom; import com.gamegoo.domain.chat.MemberChatroom; +import com.gamegoo.domain.chat.QChatroom; import com.querydsl.core.types.Order; import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Coalesce; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -14,6 +16,9 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; @Slf4j @RequiredArgsConstructor @@ -21,14 +26,24 @@ public class MemberChatroomRepositoryCustomImpl implements MemberChatroomReposit private final JPAQueryFactory queryFactory; - + /** + * 해당 회원의 모든 입장 상태인 채팅방 조회, 해당 채팅방의 마지막 채팅 시각을 기준으로 내림차순 정렬하고, 페이징 포함 + * + * @param memberId + * @param cursor + * @param pageSize + * @return + */ @Override - public List findActiveMemberChatroomOrderByLastChat(Long memberId) { - return queryFactory.selectFrom(memberChatroom) + public Slice findActiveMemberChatroomByCursorOrderByLastChat(Long memberId, + Long cursor, Integer pageSize) { + + List result = queryFactory.selectFrom(memberChatroom) .join(memberChatroom.chatroom, chatroom) .where( memberChatroom.member.id.eq(memberId), - memberChatroom.lastJoinDate.isNotNull() + memberChatroom.lastJoinDate.isNotNull(), + lastMsgLessThanCursor(cursor, chatroom) ) .orderBy(new OrderSpecifier<>( Order.DESC, @@ -41,6 +56,47 @@ public List findActiveMemberChatroomOrderByLastChat(Long memberI .add(memberChatroom.lastJoinDate) // 해당 채팅방에 대화 내역이 없는 경우, lastJoinDate를 기준으로 정렬 ) ) + .limit(pageSize + 1) // 다음 페이지가 있는지 확인하기 위해 +1 .fetch(); + + boolean hasNext = false; + if (result.size() > pageSize) { + result.remove(pageSize.intValue()); + hasNext = true; + } + + PageRequest pageRequest = PageRequest.of(0, pageSize); + + return new SliceImpl<>(result, pageRequest, hasNext); + } + + /** + * 해당 회원의 모든 입장 상태인 채팅방 조회, 정렬 및 페이징 미포함 + * + * @param memberId + * @return + */ + @Override + public List findAllActiveMemberChatroom(Long memberId) { + return queryFactory.selectFrom(memberChatroom) + .join(memberChatroom.chatroom, chatroom) + .where( + memberChatroom.member.id.eq(memberId), + memberChatroom.lastJoinDate.isNotNull() + ) + .fetch(); + } + + //--- BooleanExpression ---// + private BooleanExpression lastMsgLessThanCursor(Long cursor, QChatroom chatroom) { + if (cursor == null) { + return null; // null 처리 + } + + return JPAExpressions.select(chat.timestamp.max()) + .from(chat) + .where( + chat.chatroom.eq(chatroom) + ).lt(cursor); } } diff --git a/src/main/java/com/gamegoo/service/chat/ChatQueryService.java b/src/main/java/com/gamegoo/service/chat/ChatQueryService.java index 3d1e039d..37b8c8d2 100644 --- a/src/main/java/com/gamegoo/service/chat/ChatQueryService.java +++ b/src/main/java/com/gamegoo/service/chat/ChatQueryService.java @@ -34,7 +34,8 @@ public class ChatQueryService { private final ChatRepository chatRepository; private final ProfileService profileService; private final FriendService friendService; - private final static int PAGE_SIZE = 20; + private final static int CHAT_PAGE_SIZE = 20; + private final static int PAGE_SIZE = 10; /** @@ -48,19 +49,19 @@ public List getChatroomUuids(Long memberId) { } /** - * 채팅방 목록 조회 + * 채팅방 목록 조회, 페이징 포함 * * @param memberId * @return */ - public List getChatroomList(Long memberId) { + public ChatResponse.ChatroomViewListDTO getChatroomList(Long memberId, Long cursor) { Member member = profileService.findMember(memberId); - // 현재 참여중인 memberChatroom을 각 memberChatroom에 속한 chat의 마지막 createdAt 기준 desc 정렬해 조회 - List activeMemberChatroom = memberChatroomRepository.findActiveMemberChatroomOrderByLastChat( - member.getId()); + // 현재 참여중인 memberChatroom을 각 memberChatroom에 속한 chat의 마지막 createdAt 기준 desc 정렬해 조회. 페이징 포함 + Slice activeMemberChatroom = memberChatroomRepository.findActiveMemberChatroomByCursorOrderByLastChat( + member.getId(), cursor, PAGE_SIZE); - List chatroomViewDtoList = activeMemberChatroom.stream() + List chatroomViewDTOList = activeMemberChatroom.stream() .map(memberChatroom -> { // 채팅 상대 회원 조회 Member targetMember = memberChatroomRepository.findTargetMemberByChatroomIdAndMemberId( @@ -92,12 +93,20 @@ public List getChatroomList(Long memberId) { lastChat.get().getCreatedAt()) : DatetimeUtil.toKSTString(memberChatroom.getLastJoinDate())) .notReadMsgCnt(unReadCnt) + .lastMsgTimestamp(lastChat.isPresent() ? lastChat.get().getTimestamp() : null) .build(); }) .collect(Collectors.toList()); - return chatroomViewDtoList; + // ChatroomViewListDTO 생성 + return ChatResponse.ChatroomViewListDTO.builder() + .chatroomViewDTOList(chatroomViewDTOList) + .list_size(chatroomViewDTOList.size()) + .has_next(activeMemberChatroom.hasNext()) + .next_cursor(activeMemberChatroom.hasNext() ? chatroomViewDTOList.get( + chatroomViewDTOList.size() - 1).getLastMsgTimestamp() : null) + .build(); } @@ -122,7 +131,7 @@ public Slice getChatMessagesByCursor(String chatroomUuid, Long memberId, L // 해당 회원이 퇴장한 채팅방은 아닌지도 나중에 검증 추가하기 - PageRequest pageRequest = PageRequest.of(0, PAGE_SIZE); + PageRequest pageRequest = PageRequest.of(0, CHAT_PAGE_SIZE); // requestParam으로 cursor가 넘어온 경우 if (cursor != null) { @@ -143,7 +152,8 @@ public Slice getChatMessagesByCursor(String chatroomUuid, Long memberId, L public List getUnreadChatroomUuids(Long memberId) { Member member = profileService.findMember(memberId); - List activeMemberChatroom = memberChatroomRepository.findActiveMemberChatroomOrderByLastChat( + // 해당 회원의 모든 입장 상태인 채팅방 조회, 정렬 필요 없음 + List activeMemberChatroom = memberChatroomRepository.findAllActiveMemberChatroom( member.getId()); List unreadChatroomUuids = activeMemberChatroom.stream().map(memberChatroom -> {