-
Notifications
You must be signed in to change notification settings - Fork 1
refactor: 메시지 조회 응답 조립 책임 분리 #607
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
+379
−294
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
fdd07da
refactor: 메시지 조회 응답 조립 책임 분리
dh2906 12716ef
merge: develop 병합과 메시지 조회 리뷰 반영
dh2906 2a3b87d
chore: 코드 포맷팅
dh2906 b0bc90b
Revert "chore: 코드 포맷팅"
dh2906 d748c01
refactor: SYSTEM_ADMIN 마스킹 반환 흐름 단순화
dh2906 5d16a47
fix: 문의방 메시지 조회 응답 조립 단일화
dh2906 2f9e6b6
fix: direct unread 기준에 가시 범위 반영
dh2906 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
286 changes: 286 additions & 0 deletions
286
src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.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,286 @@ | ||
| package gg.agit.konect.domain.chat.service; | ||
|
|
||
| import static gg.agit.konect.domain.chat.service.ChatRoomMembershipService.SYSTEM_ADMIN_ID; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.ArrayList; | ||
| import java.util.Comparator; | ||
| import java.util.List; | ||
|
|
||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.data.domain.PageRequest; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import gg.agit.konect.domain.chat.dto.ChatMessageDetailResponse; | ||
| import gg.agit.konect.domain.chat.dto.ChatMessagePageResponse; | ||
| import gg.agit.konect.domain.chat.model.ChatMessage; | ||
| import gg.agit.konect.domain.chat.model.ChatRoom; | ||
| import gg.agit.konect.domain.chat.model.ChatRoomMember; | ||
| import gg.agit.konect.domain.chat.repository.ChatMessageRepository; | ||
| import gg.agit.konect.domain.chat.repository.ChatRoomMemberRepository; | ||
| import gg.agit.konect.domain.user.model.User; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| @Transactional(readOnly = true) | ||
| public class ChatMessageReadService { | ||
|
|
||
| private final ChatMessageRepository chatMessageRepository; | ||
| private final ChatRoomMemberRepository chatRoomMemberRepository; | ||
| private final ChatRoomSystemAdminService chatRoomSystemAdminService; | ||
| private final ChatDirectRoomAccessService chatDirectRoomAccessService; | ||
|
|
||
| @Transactional | ||
| public ChatMessagePageResponse getDirectChatRoomMessages( | ||
| User user, | ||
| ChatRoom chatRoom, | ||
| Integer page, | ||
| Integer limit, | ||
| LocalDateTime readAt | ||
| ) { | ||
| Integer roomId = chatRoom.getId(); | ||
| List<ChatRoomMember> members = chatRoomMemberRepository.findByChatRoomId(roomId); | ||
| LocalDateTime visibleMessageFrom = | ||
| chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user); | ||
|
|
||
| List<LocalDateTime> sortedReadBaselines = toSortedReadBaselines(members); | ||
| Integer maskedAdminId = getMaskedAdminId(user, members); | ||
|
|
||
| return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, | ||
| visibleMessageFrom, sortedReadBaselines, maskedAdminId); | ||
| } | ||
|
|
||
| public ChatMessagePageResponse getAdminSystemDirectChatRoomMessages( | ||
| User user, | ||
| ChatRoom chatRoom, | ||
| Integer page, | ||
| Integer limit, | ||
| LocalDateTime readAt | ||
| ) { | ||
| Integer roomId = chatRoom.getId(); | ||
| List<ChatRoomMember> members = chatRoomMemberRepository.findByChatRoomId(roomId); | ||
| LocalDateTime visibleMessageFrom = resolveAdminSystemRoomVisibleMessageFrom(members); | ||
|
|
||
| List<LocalDateTime> sortedReadBaselines = toAdminChatReadBaselines(members); | ||
| Integer maskedAdminId = getMaskedAdminId(user, members); | ||
|
|
||
| return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, | ||
| visibleMessageFrom, sortedReadBaselines, maskedAdminId); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| public ChatMessagePageResponse getClubMessagesByRoom( | ||
| ChatRoom room, | ||
| Integer userId, | ||
| Integer page, | ||
| Integer limit | ||
| ) { | ||
| Integer roomId = room.getId(); | ||
| PageRequest pageable = PageRequest.of(page - 1, limit); | ||
| long totalCount = chatMessageRepository.countByChatRoomId(roomId, null); | ||
| Page<ChatMessage> messagePage = chatMessageRepository.findByChatRoomId(roomId, null, pageable); | ||
| List<ChatMessage> messages = messagePage.getContent(); | ||
| List<ChatRoomMember> members = chatRoomMemberRepository.findByChatRoomId(roomId); | ||
| List<LocalDateTime> sortedReadBaselines = toSortedReadBaselines(members); | ||
|
|
||
| List<ChatMessageDetailResponse> responseMessages = messages.stream() | ||
| .map(message -> { | ||
| int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); | ||
| return new ChatMessageDetailResponse( | ||
| message.getId(), | ||
| message.getSender().getId(), | ||
| message.getSender().getName(), | ||
| message.getContent(), | ||
| message.getCreatedAt(), | ||
| null, | ||
| unreadCount, | ||
| message.isSentBy(userId) | ||
| ); | ||
| }) | ||
| .toList(); | ||
|
|
||
| int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; | ||
| return new ChatMessagePageResponse( | ||
| totalCount, | ||
| responseMessages.size(), | ||
| totalPage, | ||
| page, | ||
| room.getClub().getId(), | ||
| responseMessages | ||
| ); | ||
| } | ||
|
|
||
| public ChatMessagePageResponse getGroupMessagesByRoom( | ||
| Integer roomId, | ||
| Integer userId, | ||
| Integer page, | ||
| Integer limit | ||
| ) { | ||
| PageRequest pageable = PageRequest.of(page - 1, limit); | ||
| long totalCount = chatMessageRepository.countByChatRoomId(roomId, null); | ||
| Page<ChatMessage> messagePage = chatMessageRepository.findByChatRoomId(roomId, null, pageable); | ||
| List<ChatMessage> messages = messagePage.getContent(); | ||
| List<ChatRoomMember> members = chatRoomMemberRepository.findByChatRoomId(roomId); | ||
| List<LocalDateTime> sortedReadBaselines = toSortedReadBaselines(members); | ||
|
|
||
| List<ChatMessageDetailResponse> responseMessages = messages.stream() | ||
| .map(message -> { | ||
| int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); | ||
| return new ChatMessageDetailResponse( | ||
| message.getId(), | ||
| message.getSender().getId(), | ||
| message.getSender().getName(), | ||
| message.getContent(), | ||
| message.getCreatedAt(), | ||
| null, | ||
| unreadCount, | ||
| message.isSentBy(userId) | ||
| ); | ||
| }) | ||
| .toList(); | ||
|
|
||
| int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; | ||
| return new ChatMessagePageResponse( | ||
| totalCount, | ||
| responseMessages.size(), | ||
| totalPage, | ||
| page, | ||
| null, | ||
| responseMessages | ||
| ); | ||
| } | ||
|
|
||
| private ChatMessagePageResponse buildDirectChatRoomMessages( | ||
| User user, | ||
| Integer roomId, | ||
| Integer page, | ||
| Integer limit, | ||
| LocalDateTime readAt, | ||
| LocalDateTime visibleMessageFrom, | ||
| List<LocalDateTime> sortedReadBaselines, | ||
| Integer maskedAdminId | ||
| ) { | ||
| PageRequest pageable = PageRequest.of(page - 1, limit); | ||
| Page<ChatMessage> messages = chatMessageRepository.findByChatRoomId(roomId, visibleMessageFrom, pageable); | ||
|
|
||
| List<ChatMessageDetailResponse> responseMessages = messages.getContent().stream() | ||
| .map(message -> { | ||
| Integer senderId = maskedAdminId != null | ||
| ? resolveDirectSenderId(message, maskedAdminId) | ||
| : message.getSender().getId(); | ||
| boolean isMine = message.isSentBy(user.getId()); | ||
| boolean isRead = isMine || !message.getCreatedAt().isAfter(readAt); | ||
| int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); | ||
| return new ChatMessageDetailResponse( | ||
| message.getId(), | ||
| senderId, | ||
| null, | ||
| message.getContent(), | ||
| message.getCreatedAt(), | ||
| isRead, | ||
| unreadCount, | ||
| isMine | ||
| ); | ||
| }) | ||
| .toList(); | ||
|
|
||
| return new ChatMessagePageResponse( | ||
| messages.getTotalElements(), | ||
| messages.getNumberOfElements(), | ||
| messages.getTotalPages(), | ||
| messages.getNumber() + 1, | ||
| null, | ||
| responseMessages | ||
| ); | ||
| } | ||
|
|
||
| private List<LocalDateTime> toSortedReadBaselines(List<ChatRoomMember> members) { | ||
| return members.stream() | ||
| .map(this::resolveUnreadBaseline) | ||
| .sorted() | ||
| .toList(); | ||
| } | ||
|
|
||
| private List<LocalDateTime> toAdminChatReadBaselines(List<ChatRoomMember> members) { | ||
| LocalDateTime adminLastReadAt = null; | ||
| LocalDateTime userLastReadAt = null; | ||
|
|
||
| for (ChatRoomMember member : members) { | ||
| LocalDateTime unreadBaseline = resolveUnreadBaseline(member); | ||
| if (member.getUser().isAdmin()) { | ||
| if (adminLastReadAt == null || unreadBaseline.isAfter(adminLastReadAt)) { | ||
| adminLastReadAt = unreadBaseline; | ||
| } | ||
| } else { | ||
| userLastReadAt = unreadBaseline; | ||
| } | ||
| } | ||
|
|
||
| List<LocalDateTime> baselines = new ArrayList<>(); | ||
| if (adminLastReadAt != null) { | ||
| baselines.add(adminLastReadAt); | ||
| } | ||
| if (userLastReadAt != null) { | ||
| baselines.add(userLastReadAt); | ||
| } | ||
| baselines.sort(Comparator.naturalOrder()); | ||
| return baselines; | ||
|
dh2906 marked this conversation as resolved.
|
||
| } | ||
|
|
||
| private LocalDateTime resolveUnreadBaseline(ChatRoomMember member) { | ||
| LocalDateTime lastReadAt = member.getLastReadAt(); | ||
| LocalDateTime visibleMessageFrom = member.getVisibleMessageFrom(); | ||
|
|
||
| // direct 방에서 다시 보이기 시작한 시각 이전 메시지는 unreadCount에도 포함하지 않는다. | ||
| if (visibleMessageFrom == null) { | ||
| return lastReadAt; | ||
| } | ||
| if (lastReadAt == null) { | ||
| return visibleMessageFrom; | ||
| } | ||
| return lastReadAt.isAfter(visibleMessageFrom) ? lastReadAt : visibleMessageFrom; | ||
| } | ||
|
|
||
| private int countUnreadSince(LocalDateTime messageCreatedAt, List<LocalDateTime> sortedReadBaselines) { | ||
| int left = 0; | ||
| int right = sortedReadBaselines.size(); | ||
|
|
||
| while (left < right) { | ||
| int mid = (left + right) >>> 1; | ||
| LocalDateTime baseline = sortedReadBaselines.get(mid); | ||
|
|
||
| if (baseline.isBefore(messageCreatedAt)) { | ||
| left = mid + 1; | ||
| } else { | ||
| right = mid; | ||
| } | ||
| } | ||
|
|
||
| return left; | ||
| } | ||
|
|
||
| private LocalDateTime resolveAdminSystemRoomVisibleMessageFrom(List<ChatRoomMember> members) { | ||
| ChatRoomMember systemAdminMember = chatRoomSystemAdminService.findSystemAdminMember(members); | ||
| return systemAdminMember != null ? systemAdminMember.getVisibleMessageFrom() : null; | ||
| } | ||
|
|
||
| private Integer resolveDirectSenderId(ChatMessage message, Integer maskedAdminId) { | ||
| if (maskedAdminId != null && message.getSender().isAdmin()) { | ||
| return maskedAdminId; | ||
| } | ||
| return message.getSender().getId(); | ||
| } | ||
|
|
||
| private Integer getMaskedAdminId(User user, List<ChatRoomMember> members) { | ||
| if (user.isAdmin()) { | ||
| return null; | ||
| } | ||
|
|
||
| boolean hasSystemAdmin = members.stream() | ||
| .map(ChatRoomMember::getUserId) | ||
| .anyMatch(memberUserId -> memberUserId.equals(SYSTEM_ADMIN_ID)); | ||
|
|
||
| return hasSystemAdmin ? SYSTEM_ADMIN_ID : null; | ||
| } | ||
| } | ||
Oops, something went wrong.
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.