diff --git a/src/main/java/com/assu/server/domain/chat/controller/ChatController.java b/src/main/java/com/assu/server/domain/chat/controller/ChatController.java index 176b23a..53af1a4 100644 --- a/src/main/java/com/assu/server/domain/chat/controller/ChatController.java +++ b/src/main/java/com/assu/server/domain/chat/controller/ChatController.java @@ -4,15 +4,12 @@ import com.assu.server.domain.chat.repository.MessageRepository; import com.assu.server.domain.chat.service.BlockService; import com.assu.server.domain.chat.service.ChatService; -import com.assu.server.domain.common.enums.UserRole; -import com.assu.server.domain.member.entity.Member; import com.assu.server.domain.member.repository.MemberRepository; import com.assu.server.domain.notification.service.NotificationCommandService; import com.assu.server.global.apiPayload.code.status.SuccessStatus; import com.assu.server.global.util.PresenceTracker; import com.assu.server.global.util.PrincipalDetails; import io.swagger.v3.oas.annotations.Operation; -import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.messaging.handler.annotation.MessageMapping; @@ -31,11 +28,11 @@ public class ChatController { private final ChatService chatService; private final SimpMessagingTemplate simpMessagingTemplate; - private final PresenceTracker presenceTracker; - private final MessageRepository messageRepository; - private final MemberRepository memberRepository; +// private final PresenceTracker presenceTracker; +// private final MessageRepository messageRepository; +// private final MemberRepository memberRepository; private final BlockService blockService; - private final NotificationCommandService notificationCommandService; +// private final NotificationCommandService notificationCommandService; @Operation( summary = "채팅방을 생성하는 API", @@ -71,49 +68,68 @@ public BaseResponse> "- receiverId: Request Body, Long\n" + "- message: Request Body, String\n" ) - @Transactional @MessageMapping("/send") public void handleMessage(@Payload ChatRequestDTO.ChatMessageRequestDTO request) { - // 먼저 접속 여부 확인 후 unreadCount 계산 - boolean receiverInRoom = presenceTracker.isInRoom(request.getReceiverId(), request.getRoomId()); - int unreadForSender = receiverInRoom ? 0 : 1; - request.setUnreadCountForSender(unreadForSender); - - ChatResponseDTO.SendMessageResponseDTO saved = chatService.handleMessage(request); - simpMessagingTemplate.convertAndSend("/sub/chat/" + request.getRoomId(), saved); - - if (!receiverInRoom) { - Long totalUnreadCount = messageRepository.countUnreadMessagesByRoomAndReceiver( - request.getRoomId(), - request.getReceiverId() - ); - ChatRoomUpdateDTO updateDTO = ChatRoomUpdateDTO.builder() - .roomId(request.getRoomId()) - .lastMessage(saved.message()) - .lastMessageTime(saved.sentAt()) - .unreadCount(totalUnreadCount) - .build(); + // 1. 서비스 호출 (모든 비즈니스 로직 위임) + MessageHandlingResult result = chatService.handleMessage(request); + + // 2. [항상 전송] 채팅방 메시지 전송 + simpMessagingTemplate.convertAndSend("/sub/chat/" + request.getRoomId(), result.sendMessageResponseDTO()); + // 3. [조건부 전송] 채팅방 목록 업데이트 전송 + if (result.hasRoomUpdates()) { simpMessagingTemplate.convertAndSendToUser( - request.getReceiverId().toString(), + result.receiverId().toString(), "/queue/updates", - updateDTO + result.chatRoomUpdateDTO() ); - Member sender = memberRepository.findById(request.getSenderId()).orElse(null); - String senderName; - if (sender.getRole()== UserRole.ADMIN) { - senderName = sender.getAdminProfile().getName(); - } else { - senderName = sender.getPartnerProfile().getName(); - } - - log.info(">>>>>>>>메시지 전송은 될걸"); - notificationCommandService.sendChat(request.getReceiverId(), request.getRoomId(), senderName, request.getMessage()); - log.info(">>>>>>>>알림이 가나"); } } +// @Transactional +// @MessageMapping("/send") +// public void handleMessage(@Payload ChatRequestDTO.ChatMessageRequestDTO request) { +// // 먼저 접속 여부 확인 후 unreadCount 계산 +// boolean receiverInRoom = presenceTracker.isInRoom(request.getReceiverId(), request.getRoomId()); +// int unreadForSender = receiverInRoom ? 0 : 1; +// request.setUnreadCountForSender(unreadForSender); +// +// ChatResponseDTO.SendMessageResponseDTO saved = chatService.handleMessage(request); +// simpMessagingTemplate.convertAndSend("/sub/chat/" + request.getRoomId(), saved); +// +// if (!receiverInRoom) { +// Long totalUnreadCount = messageRepository.countUnreadMessagesByRoomAndReceiver( +// request.getRoomId(), +// request.getReceiverId() +// ); +// +// ChatRoomUpdateDTO updateDTO = ChatRoomUpdateDTO.builder() +// .roomId(request.getRoomId()) +// .lastMessage(saved.message()) +// .lastMessageTime(saved.sentAt()) +// .unreadCount(totalUnreadCount) +// .build(); +// +// simpMessagingTemplate.convertAndSendToUser( +// request.getReceiverId().toString(), +// "/queue/updates", +// updateDTO +// ); +// Member sender = memberRepository.findById(request.getSenderId()).orElse(null); +// String senderName; +// if (sender.getRole()== UserRole.ADMIN) { +// senderName = sender.getAdminProfile().getName(); +// } else { +// senderName = sender.getPartnerProfile().getName(); +// } +// +// log.info(">>>>>>>>메시지 전송은 될걸"); +// notificationCommandService.sendChat(request.getReceiverId(), request.getRoomId(), senderName, request.getMessage()); +// log.info(">>>>>>>>알림이 가나"); +// } +// } + @Operation( summary = "메시지 읽음 처리 API", description = "# [v1.0 (2025-08-05)](https://clumsy-seeder-416.notion.site/2241197c19ed800eab45c35073761c97?v=2241197c19ed8134b64f000cc26c5d31&p=2241197c19ed81ffa771cb18ab157b54&pm=s) 메시지를 읽음처리합니다.\n"+ diff --git a/src/main/java/com/assu/server/domain/chat/dto/MessageHandlingResult.java b/src/main/java/com/assu/server/domain/chat/dto/MessageHandlingResult.java new file mode 100644 index 0000000..56c142e --- /dev/null +++ b/src/main/java/com/assu/server/domain/chat/dto/MessageHandlingResult.java @@ -0,0 +1,26 @@ +package com.assu.server.domain.chat.dto; + +public record MessageHandlingResult( + ChatResponseDTO.SendMessageResponseDTO sendMessageResponseDTO, + ChatRoomUpdateDTO chatRoomUpdateDTO, + Long receiverId +) { + + // 정적 팩토리 메소드 1 + public static MessageHandlingResult of(ChatResponseDTO.SendMessageResponseDTO sendMessageDTO) { + // record의 기본 생성자를 호출합니다. + return new MessageHandlingResult(sendMessageDTO, null, null); + } + + // 정적 팩토리 메소드 2 + public static MessageHandlingResult withUpdates(ChatResponseDTO.SendMessageResponseDTO sendMessageDTO, ChatRoomUpdateDTO updateDTO, Long receiverId) { + // record의 기본 생성자를 호출합니다. + return new MessageHandlingResult(sendMessageDTO, updateDTO, receiverId); + } + + // 헬퍼(Helper) 메소드 + public boolean hasRoomUpdates() { + // record는 'get' 접두사 없는 접근자(chatRoomUpdateDTO())를 사용합니다. + return chatRoomUpdateDTO != null; + } +} \ No newline at end of file diff --git a/src/main/java/com/assu/server/domain/chat/service/ChatService.java b/src/main/java/com/assu/server/domain/chat/service/ChatService.java index de2e476..10c84fb 100644 --- a/src/main/java/com/assu/server/domain/chat/service/ChatService.java +++ b/src/main/java/com/assu/server/domain/chat/service/ChatService.java @@ -3,12 +3,15 @@ import com.assu.server.domain.chat.dto.ChatRequestDTO; import com.assu.server.domain.chat.dto.ChatResponseDTO; import com.assu.server.domain.chat.dto.ChatRoomListResultDTO; +import com.assu.server.domain.chat.dto.MessageHandlingResult; + import java.util.List; public interface ChatService { List getChatRoomList(Long memberId); ChatResponseDTO.CreateChatRoomResponseDTO createChatRoom(ChatRequestDTO.CreateChatRoomRequestDTO request, Long memberId); - ChatResponseDTO.SendMessageResponseDTO handleMessage(ChatRequestDTO.ChatMessageRequestDTO request); +// ChatResponseDTO.SendMessageResponseDTO handleMessage(ChatRequestDTO.ChatMessageRequestDTO request); + MessageHandlingResult handleMessage(ChatRequestDTO.ChatMessageRequestDTO request); ChatResponseDTO.ReadMessageResponseDTO readMessage(Long roomId, Long memberId); ChatResponseDTO.ChatHistoryResponseDTO readHistory(Long roomId, Long memberId); ChatResponseDTO.LeaveChattingRoomResponseDTO leaveChattingRoom(Long roomId, Long memberId); diff --git a/src/main/java/com/assu/server/domain/chat/service/ChatServiceImpl.java b/src/main/java/com/assu/server/domain/chat/service/ChatServiceImpl.java index a4f7ad0..c3e2558 100644 --- a/src/main/java/com/assu/server/domain/chat/service/ChatServiceImpl.java +++ b/src/main/java/com/assu/server/domain/chat/service/ChatServiceImpl.java @@ -3,14 +3,12 @@ import com.assu.server.domain.admin.entity.Admin; import com.assu.server.domain.admin.repository.AdminRepository; import com.assu.server.domain.chat.converter.ChatConverter; -import com.assu.server.domain.chat.dto.ChatMessageDTO; -import com.assu.server.domain.chat.dto.ChatRequestDTO; -import com.assu.server.domain.chat.dto.ChatResponseDTO; -import com.assu.server.domain.chat.dto.ChatRoomListResultDTO; +import com.assu.server.domain.chat.dto.*; import com.assu.server.domain.chat.entity.ChattingRoom; import com.assu.server.domain.chat.entity.Message; import com.assu.server.domain.chat.repository.ChatRepository; import com.assu.server.domain.chat.repository.MessageRepository; +import com.assu.server.domain.common.enums.UserRole; import com.assu.server.domain.member.entity.Member; import com.assu.server.domain.common.enums.ActivationStatus; import com.assu.server.domain.member.repository.MemberRepository; @@ -21,6 +19,7 @@ import com.assu.server.domain.store.repository.StoreRepository; import com.assu.server.global.apiPayload.code.status.ErrorStatus; import com.assu.server.global.exception.DatabaseException; +import com.assu.server.global.util.PresenceTracker; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -42,6 +41,7 @@ public class ChatServiceImpl implements ChatService { private final StoreRepository storeRepository; private final SimpMessagingTemplate simpMessagingTemplate; private final NotificationCommandService notificationCommandService; + private final PresenceTracker presenceTracker; @Override @@ -90,10 +90,31 @@ public ChatResponseDTO.CreateChatRoomResponseDTO createChatRoom(ChatRequestDTO.C } } +// @Override +// @Transactional +// public ChatResponseDTO.SendMessageResponseDTO handleMessage(ChatRequestDTO.ChatMessageRequestDTO request) { +// // 유효성 검사 +// ChattingRoom room = chatRepository.findById(request.getRoomId()) +// .orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_ROOM)); +// Member sender = memberRepository.findById(request.getSenderId()) +// .orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_MEMBER)); +// Member receiver = memberRepository.findById(request.getReceiverId()) +// .orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_MEMBER)); +// +// Message message = ChatConverter.toMessageEntity(request, room, sender, receiver); +// Message saved = messageRepository.saveAndFlush(message); +// log.info("saved message id={}, roomId={}, senderId={}, receiverId={}", +// saved.getId(), room.getId(), sender.getId(), receiver.getId()); +// +// return ChatConverter.toSendMessageDTO(saved); +// } + + // ChatService의 handleMessage 메서드 (수정) + @Override @Transactional - public ChatResponseDTO.SendMessageResponseDTO handleMessage(ChatRequestDTO.ChatMessageRequestDTO request) { - // 유효성 검사 + public MessageHandlingResult handleMessage(ChatRequestDTO.ChatMessageRequestDTO request) { + // 1. 유효성 검사 (기존 로직) ChattingRoom room = chatRepository.findById(request.getRoomId()) .orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_ROOM)); Member sender = memberRepository.findById(request.getSenderId()) @@ -101,14 +122,52 @@ public ChatResponseDTO.SendMessageResponseDTO handleMessage(ChatRequestDTO.ChatM Member receiver = memberRepository.findById(request.getReceiverId()) .orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_MEMBER)); + // 2. 컨트롤러에서 가져온 비즈니스 로직 (접속 확인) + boolean receiverInRoom = presenceTracker.isInRoom(request.getReceiverId(), request.getRoomId()); + int unreadForSender = receiverInRoom ? 0 : 1; + request.setUnreadCountForSender(unreadForSender); + + // 3. 메시지 저장 (기존 로직) Message message = ChatConverter.toMessageEntity(request, room, sender, receiver); Message saved = messageRepository.saveAndFlush(message); log.info("saved message id={}, roomId={}, senderId={}, receiverId={}", saved.getId(), room.getId(), sender.getId(), receiver.getId()); -// boolean exists = messageRepository.existsById(saved.getId()); -// log.info("Saved? {}", exists); // true 아니면 트랜잭션/DB 문제 - return ChatConverter.toSendMessageDTO(saved); + ChatResponseDTO.SendMessageResponseDTO savedDTO = ChatConverter.toSendMessageDTO(saved); + + // 4. 컨트롤러에서 가져온 비즈니스 로직 (수신자 부재 시) + if (!receiverInRoom) { + // 4-1. 안 읽은 수 계산 + Long totalUnreadCount = messageRepository.countUnreadMessagesByRoomAndReceiver( + request.getRoomId(), + request.getReceiverId() + ); + + // 4-2. 채팅방 목록 업데이트 DTO 생성 + ChatRoomUpdateDTO updateDTO = ChatRoomUpdateDTO.builder() + .roomId(request.getRoomId()) + .lastMessage(savedDTO.message()) + .lastMessageTime(savedDTO.sentAt()) + .unreadCount(totalUnreadCount) + .build(); + + // 4-3. 발신자 이름 찾기 (기존 컨트롤러 로직) + String senderName; + if (sender.getRole() == UserRole.ADMIN) { // 이미 sender 객체가 있으므로 재활용 + senderName = sender.getAdminProfile().getName(); + } else { + senderName = sender.getPartnerProfile().getName(); + } + + // 4-4. 알림 전송 + notificationCommandService.sendChat(request.getReceiverId(), request.getRoomId(), senderName, request.getMessage()); + + // 5. [업데이트 포함] 결과 반환 + return MessageHandlingResult.withUpdates(savedDTO, updateDTO, request.getReceiverId()); + } + + // 5. [일반 메시지] 결과 반환 + return MessageHandlingResult.of(savedDTO); }