Skip to content

Commit

Permalink
[feat] 채팅방 목록 조회시 가장 최근에 전송된 채팅방이 위쪽으로 오도록 코드 개선 (#144)
Browse files Browse the repository at this point in the history
* #141 feat: 가장 최근에 보낸 메시지를 받은 채팅방이 가장 위에 오도록 정렬

* #141 fix: 오타 수정
  • Loading branch information
yonghwankim-dev authored Oct 5, 2023
1 parent 6c3b17d commit 235dacc
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package codesquard.app.api.chat;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
Expand Down Expand Up @@ -34,12 +32,8 @@
@RestController
public class ChatLogRestController {

private static final int DEFAULT_READ_MESSAGE_SIZE = 10;

private final Map<DeferredResult<ApiResponse<ChatLogListResponse>>, Long> chatRequests =
new ConcurrentHashMap<>();

private final ChatLogService chatLogService;
private final ChatService chatService;

@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/chats/{chatRoomId}")
Expand All @@ -48,20 +42,10 @@ public ApiResponse<ChatLogSendResponse> sendMessage(
@RequestBody ChatLogSendRequest request,
@AuthPrincipal Principal sender) {
ChatLogSendResponse response = chatLogService.sendMessage(request, chatRoomId, sender);

onMessage(chatRoomId, sender);
chatService.onMessage(chatRoomId, sender);
return ApiResponse.created("메시지 전송이 완료되었습니다.", response);
}

private void onMessage(Long chatRoomId, Principal sender) {
for (Map.Entry<DeferredResult<ApiResponse<ChatLogListResponse>>, Long> entry : this.chatRequests.entrySet()) {
DeferredResult<ApiResponse<ChatLogListResponse>> key = entry.getKey();
Long cursor = entry.getValue();
key.setResult(ApiResponse.ok("채팅 메시지 목록 조회가 완료되었습니다.",
chatLogService.readMessages(chatRoomId, sender, cursor, Pageable.ofSize(DEFAULT_READ_MESSAGE_SIZE))));
}
}

@GetMapping("/chats/{chatRoomId}")
public DeferredResult<ApiResponse<ChatLogListResponse>> readMessages(
@PathVariable Long chatRoomId,
Expand All @@ -72,9 +56,9 @@ public DeferredResult<ApiResponse<ChatLogListResponse>> readMessages(
principal.getLoginId());

DeferredResult<ApiResponse<ChatLogListResponse>> deferredResult = new DeferredResult<>(10000L);
this.chatRequests.put(deferredResult, messageIndex);
chatService.putMessageIndex(deferredResult, messageIndex);

deferredResult.onCompletion(() -> chatRequests.remove(deferredResult));
deferredResult.onCompletion(() -> chatService.removeMessageIndex(deferredResult));
deferredResult.onTimeout(() -> deferredResult.setErrorResult(
ApiResponse.of(HttpStatus.REQUEST_TIMEOUT, "새로운 채팅 메시지가 존재하지 않습니다.", Collections.emptyList())));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public ChatLogListResponse readMessages(Long chatRoomId, Principal principal, Lo
private Long getNextCursor(List<ChatLogMessageResponse> contents, boolean hasNext) {
Long nextCursor = null;
if (hasNext) {
nextCursor = contents.get(contents.size() - 1).getChatLogId();
nextCursor = contents.get(contents.size() - 1).getMessageIndex();
}
return nextCursor;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package codesquard.app.api.chat;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
Expand Down Expand Up @@ -89,6 +90,7 @@ public ChatRoomListResponse readAllChatRoom(Principal principal, Pageable pageab

List<ChatRoomItemResponse> contents = slice.getContent().stream()
.map(getChatRoomItemResponseMapper(newMessageMap, principal))
.sorted(Comparator.comparing(ChatRoomItemResponse::getLastSendTime).reversed())
.collect(Collectors.toUnmodifiableList());
boolean hasNext = slice.hasNext();
Long nextCursor = getNextCursor(contents, hasNext);
Expand Down Expand Up @@ -136,6 +138,7 @@ public ChatRoomListResponse readAllChatRoomByItem(Long itemId, Principal princip

List<ChatRoomItemResponse> contents = slice.getContent().stream()
.map(getChatRoomItemResponseMapper(newMessageMap, principal))
.sorted(Comparator.comparing(ChatRoomItemResponse::getLastSendTime).reversed())
.collect(Collectors.toUnmodifiableList());
boolean hasNext = slice.hasNext();
Long nextCursor = getNextCursor(contents, hasNext);
Expand Down
42 changes: 42 additions & 0 deletions backend/src/main/java/codesquard/app/api/chat/ChatService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package codesquard.app.api.chat;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;

import codesquard.app.api.chat.response.ChatLogListResponse;
import codesquard.app.api.response.ApiResponse;
import codesquard.app.domain.oauth.support.Principal;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class ChatService {

private static final int DEFAULT_READ_MESSAGE_SIZE = 10;

private final Map<DeferredResult<ApiResponse<ChatLogListResponse>>, Long> chatRequests =
new ConcurrentHashMap<>();

private final ChatLogService chatLogService;

public void onMessage(Long chatRoomId, Principal sender) {
for (Map.Entry<DeferredResult<ApiResponse<ChatLogListResponse>>, Long> entry : this.chatRequests.entrySet()) {
DeferredResult<ApiResponse<ChatLogListResponse>> key = entry.getKey();
Long cursor = entry.getValue();
key.setResult(ApiResponse.ok("채팅 메시지 목록 조회가 완료되었습니다.",
chatLogService.readMessages(chatRoomId, sender, cursor, Pageable.ofSize(DEFAULT_READ_MESSAGE_SIZE))));
}
}

public void putMessageIndex(DeferredResult<ApiResponse<ChatLogListResponse>> deferredResult, Long messageIndex) {
chatRequests.put(deferredResult, messageIndex);
}

public void removeMessageIndex(DeferredResult<ApiResponse<ChatLogListResponse>> deferredResult) {
chatRequests.remove(deferredResult);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ChatLogMessageResponse {
private Long chatLogId;
private Long messageIndex;
private Boolean isMe;
private String message;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ public ChatRoomListResponse(List<ChatRoomItemResponse> contents, boolean hasNext
this.contents = contents;
this.paging = ItemResponses.Paging.create(nextCursor, hasNext);
}

public boolean isEmptyChatRooms() {
return contents.isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ public MemberProfileResponse modifyProfileImage(String loginId, MultipartFile up
member.changeAvatarUrl(avatarUrl);
return new MemberProfileResponse(avatarUrl);
}

public Member findMemberByLoginId(String loginId) {
return memberRepository.findMemberByLoginId(loginId)
.orElseThrow(() -> new NotFoundResourceException(ErrorCode.NOT_FOUND_MEMBER));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.anyLong;
import static org.mockito.BDDMockito.anyString;
import static org.mockito.BDDMockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
Expand Down Expand Up @@ -41,6 +42,7 @@
import codesquard.app.api.chat.response.ChatLogListResponse;
import codesquard.app.api.chat.response.ChatLogMessageResponse;
import codesquard.app.api.chat.response.ChatLogSendResponse;
import codesquard.app.api.member.MemberService;
import codesquard.app.domain.category.Category;
import codesquard.app.domain.chat.ChatLog;
import codesquard.app.domain.chat.ChatRoom;
Expand All @@ -60,6 +62,12 @@ class ChatLogRestControllerTest extends ControllerTestSupport {
@MockBean
private ChatLogService chatLogService;

@MockBean
private ChatService chatService;

@MockBean
private MemberService memberService;

@Autowired
private PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver;

Expand Down Expand Up @@ -99,6 +107,11 @@ public void sendMessage() throws Exception {
any(Principal.class)))
.willReturn(response);

Member receiver = createMember("avatarUrl", "[email protected]", "bruni");
given(memberService.findMemberByLoginId(
anyString()
)).willReturn(receiver);

// when & then
mockMvc.perform(post("/api/chats/1")
.content(objectMapper.writeValueAsString(requestBody))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import static org.mockito.BDDMockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.util.HashMap;
Expand All @@ -26,6 +27,7 @@
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import codesquard.app.ControllerTestSupport;
Expand All @@ -51,6 +53,9 @@ class ChatRoomRestControllerTest extends ControllerTestSupport {
@MockBean
private ChatRoomService chatRoomService;

@MockBean
private ChatService chatService;

@Autowired
private PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver;

Expand Down Expand Up @@ -115,7 +120,11 @@ public void readAllChatRoom() throws Exception {
.willReturn(response);

// when & then
mockMvc.perform(get("/api/chats"))
MvcResult asyncListener = mockMvc.perform(get("/api/chats"))
.andExpect(request().asyncStarted())
.andReturn();

mockMvc.perform(asyncDispatch(asyncListener))
.andExpect(status().isOk())
.andExpect(jsonPath("statusCode").value(equalTo(200)))
.andExpect(jsonPath("message").value(equalTo("채팅방 목록 조회를 완료하였습니다.")))
Expand Down

0 comments on commit 235dacc

Please sign in to comment.