diff --git a/build.gradle b/build.gradle index 8ede13a4..75eb6d8f 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-websocket' + implementation 'org.webjars:stomp-websocket:2.3.3-1' + implementation 'org.webjars:sockjs-client:1.1.2' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' compileOnly 'org.projectlombok:lombok' diff --git a/src/main/java/kr/co/fastcampus/yanabada/common/config/WebSockConfig.java b/src/main/java/kr/co/fastcampus/yanabada/common/config/WebSockConfig.java new file mode 100644 index 00000000..1e4b3313 --- /dev/null +++ b/src/main/java/kr/co/fastcampus/yanabada/common/config/WebSockConfig.java @@ -0,0 +1,23 @@ +package kr.co.fastcampus.yanabada.common.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +public class WebSockConfig implements WebSocketMessageBrokerConfigurer { + + @Override + public void configureMessageBroker(MessageBrokerRegistry config) { + config.enableSimpleBroker("/sub"); + config.setApplicationDestinationPrefixes("/pub"); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws-stomp").setAllowedOrigins("*").withSockJS(); + } +} diff --git a/src/main/java/kr/co/fastcampus/yanabada/domain/chat/controller/ChatController.java b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/controller/ChatController.java index 48ff55f0..d3dd7f7e 100644 --- a/src/main/java/kr/co/fastcampus/yanabada/domain/chat/controller/ChatController.java +++ b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/controller/ChatController.java @@ -2,6 +2,7 @@ import java.util.List; import kr.co.fastcampus.yanabada.common.response.ResponseBody; +import kr.co.fastcampus.yanabada.domain.chat.dto.ReceivedChatMessage; import kr.co.fastcampus.yanabada.domain.chat.dto.request.ChatRoomModifyRequest; import kr.co.fastcampus.yanabada.domain.chat.dto.request.ChatRoomSaveRequest; import kr.co.fastcampus.yanabada.domain.chat.dto.response.ChatMessageInfoResponse; @@ -10,6 +11,8 @@ import kr.co.fastcampus.yanabada.domain.chat.dto.response.ChatRoomSummaryResponse; import kr.co.fastcampus.yanabada.domain.chat.service.ChatService; import lombok.RequiredArgsConstructor; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -26,6 +29,15 @@ public class ChatController { private final ChatService chatService; + private final SimpMessagingTemplate messagingTemplate; + + @MessageMapping("/message") + public void message(ReceivedChatMessage message) { + messagingTemplate.convertAndSend( + "/sub/chatroom/" + message.chatRoomCode(), chatService.saveChatMessage(message) + ); + } + @PostMapping public ResponseBody getOrAddChatRoom( @RequestBody ChatRoomSaveRequest request @@ -35,7 +47,6 @@ public ResponseBody getOrAddChatRoom( @GetMapping public ResponseBody> getChatRooms( - //Principal 을 통해 로그인한 멤버 id를 가져올 예정 ) { Long memberId = 1L; return ResponseBody.ok(chatService.getChatRooms(memberId)); diff --git a/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/ReceivedChatMessage.java b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/ReceivedChatMessage.java new file mode 100644 index 00000000..75a0940f --- /dev/null +++ b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/ReceivedChatMessage.java @@ -0,0 +1,37 @@ +package kr.co.fastcampus.yanabada.domain.chat.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; +import java.time.LocalDateTime; +import kr.co.fastcampus.yanabada.domain.chat.entity.ChatMessage; +import kr.co.fastcampus.yanabada.domain.chat.entity.ChatRoom; +import kr.co.fastcampus.yanabada.domain.member.entity.Member; + +public record ReceivedChatMessage( + @Size(min = 18, max = 18, message = "유효하지 않은 채팅방 코드입니다.") + @Pattern(regexp = "^[0-9a-f]+$", message = "유효하지 않은 채팅방 코드입니다.") + String chatRoomCode, + @NotNull(message = "전송자 ID는 필수로 입력하셔야 합니다.") + @Positive(message = "전송자 ID는 양수이어야 합니다.") + Long sendId, + @NotBlank(message = "메세지 내용이 없습니다.") + @Size(max = 255, message = "메세지 내용은 255자를 넘을 수 없습니다.") + String content +) { + + public ChatMessage toEntity( + ChatRoom chatRoom, + Member sender, + LocalDateTime sendDateTime + ) { + return ChatMessage.create( + chatRoom, + sender, + content, + sendDateTime + ); + } +} diff --git a/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/SendChatMessage.java b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/SendChatMessage.java new file mode 100644 index 00000000..76e04a74 --- /dev/null +++ b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/SendChatMessage.java @@ -0,0 +1,35 @@ +package kr.co.fastcampus.yanabada.domain.chat.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import java.time.LocalDateTime; +import kr.co.fastcampus.yanabada.domain.chat.entity.ChatRoom; +import kr.co.fastcampus.yanabada.domain.member.entity.Member; +import lombok.Builder; + +@Builder +public record SendChatMessage( + String chatRoomCode, + Long sendId, + String senderNickname, + String senderProfileImage, + String content, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + LocalDateTime sendTime +) { + + public static SendChatMessage from( + ChatRoom chatRoom, + Member sender, + String content, + LocalDateTime sendTime + ) { + return SendChatMessage.builder() + .chatRoomCode(chatRoom.getCode()) + .sendId(sender.getId()) + .senderNickname(sender.getNickName()) + .senderProfileImage(sender.getImageUrl()) + .content(content) + .sendTime(sendTime) + .build(); + } +} diff --git a/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/response/ChatMessageInfoResponse.java b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/response/ChatMessageInfoResponse.java index 8a1b803b..67e824b1 100644 --- a/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/response/ChatMessageInfoResponse.java +++ b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/response/ChatMessageInfoResponse.java @@ -14,7 +14,7 @@ public record ChatMessageInfoResponse( LocalDateTime sendDateTime ) { - public static ChatMessageInfoResponse create( + public static ChatMessageInfoResponse from( Member sender, String content, LocalDateTime sendDateTime ) { return ChatMessageInfoResponse.builder() diff --git a/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/response/ChatRoomModifyResponse.java b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/response/ChatRoomModifyResponse.java index a7c336b6..ae3e5b7c 100644 --- a/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/response/ChatRoomModifyResponse.java +++ b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/response/ChatRoomModifyResponse.java @@ -8,7 +8,7 @@ public record ChatRoomModifyResponse( Long updatedMemberId ) { - public static ChatRoomModifyResponse create(String chatRoomCode, Long updatedMemberId) { + public static ChatRoomModifyResponse from(String chatRoomCode, Long updatedMemberId) { return ChatRoomModifyResponse.builder() .chatRoomCode(chatRoomCode) .updatedMemberId(updatedMemberId) diff --git a/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/response/ChatRoomSummaryResponse.java b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/response/ChatRoomSummaryResponse.java index c1ba0712..7c52b406 100644 --- a/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/response/ChatRoomSummaryResponse.java +++ b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/dto/response/ChatRoomSummaryResponse.java @@ -20,7 +20,7 @@ public record ChatRoomSummaryResponse( Integer unreadMessageCount ) { - public static ChatRoomSummaryResponse create( + public static ChatRoomSummaryResponse from( String chatRoomCode, Member partner, ChatMessage message, diff --git a/src/main/java/kr/co/fastcampus/yanabada/domain/chat/service/ChatService.java b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/service/ChatService.java index f9f58665..f1e25636 100644 --- a/src/main/java/kr/co/fastcampus/yanabada/domain/chat/service/ChatService.java +++ b/src/main/java/kr/co/fastcampus/yanabada/domain/chat/service/ChatService.java @@ -4,9 +4,12 @@ import java.util.List; import java.util.Objects; import java.util.Optional; + import kr.co.fastcampus.yanabada.common.exception.CannotNegotiateOwnProductException; import kr.co.fastcampus.yanabada.common.exception.IncorrectChatRoomMember; import kr.co.fastcampus.yanabada.common.exception.NegotiationNotPossibleException; +import kr.co.fastcampus.yanabada.domain.chat.dto.ReceivedChatMessage; +import kr.co.fastcampus.yanabada.domain.chat.dto.SendChatMessage; import kr.co.fastcampus.yanabada.domain.chat.dto.request.ChatRoomModifyRequest; import kr.co.fastcampus.yanabada.domain.chat.dto.request.ChatRoomSaveRequest; import kr.co.fastcampus.yanabada.domain.chat.dto.response.ChatMessageInfoResponse; @@ -113,7 +116,7 @@ private ChatRoomSummaryResponse createChatRoomSummaryResponse( List messages, int unreadCount ) { - return ChatRoomSummaryResponse.create( + return ChatRoomSummaryResponse.from( chatRoom.getCode(), partner, messages.get(messages.size() - 1), @@ -153,7 +156,7 @@ public List getChatRoomMessages(Long memberId, String c Member member = memberRepository.getMember(memberId); checkChatRoomMember(chatRoom, member); return chatRoom.getMessages().stream() - .map(message -> ChatMessageInfoResponse.create( + .map(message -> ChatMessageInfoResponse.from( message.getSender(), message.getContent(), message.getSendDateTime() )) .toList(); @@ -170,7 +173,7 @@ public ChatRoomModifyResponse updateChatRoom(Long memberId, ChatRoomModifyReques ChatRoom chatRoom = chatRoomRepository.getChatroom(request.chatRoomCode()); Member member = memberRepository.getMember(memberId); updateLastCheckTime(chatRoom, member); - return ChatRoomModifyResponse.create(chatRoom.getCode(), memberId); + return ChatRoomModifyResponse.from(chatRoom.getCode(), memberId); } private void updateLastCheckTime(ChatRoom chatRoom, Member member) { @@ -196,13 +199,17 @@ public ChatRoomModifyResponse modifyOrDeleteChatRoom( } else { handleBuyerAction(chatRoom); } - return ChatRoomModifyResponse.create(chatRoom.getCode(), memberId); + return ChatRoomModifyResponse.from(chatRoom.getCode(), memberId); } private boolean isSeller(Member member, ChatRoom chatRoom) { return member.equals(chatRoom.getSeller()); } + private boolean isBuyer(Member member, ChatRoom chatRoom) { + return member.equals(chatRoom.getBuyer()); + } + private void handleSellerAction(ChatRoom chatRoom) { if (chatRoom.getHasBuyerLeft()) { chatRoomRepository.delete(chatRoom); @@ -218,4 +225,35 @@ private void handleBuyerAction(ChatRoom chatRoom) { chatRoom.updateHasBuyerLeft(true); } } + + @Transactional + public SendChatMessage saveChatMessage(ReceivedChatMessage message) { + ChatRoom chatRoom = chatRoomRepository.getChatroom(message.chatRoomCode()); + Member sender = memberRepository.getMember(message.sendId()); + LocalDateTime sendTime = LocalDateTime.now(); + checkChatRoomMember(chatRoom, sender); + updateMemberPresenceStatus(chatRoom, sender); + addMessageToChatRoom(chatRoom, message, sender, sendTime); + return createSendChatMessage(chatRoom, sender, message, sendTime); + } + + private void addMessageToChatRoom( + ChatRoom chatRoom, ReceivedChatMessage message, Member sender, LocalDateTime sendTime + ) { + chatRoom.addChatMessage(message.toEntity(chatRoom, sender, sendTime)); + } + + private void updateMemberPresenceStatus(ChatRoom chatRoom, Member sender) { + if (isSeller(sender, chatRoom) && chatRoom.getHasSellerLeft()) { + chatRoom.updateHasSellerLeft(false); + } else if (isBuyer(sender, chatRoom) && chatRoom.getHasBuyerLeft()) { + chatRoom.updateHasBuyerLeft(false); + } + } + + private SendChatMessage createSendChatMessage( + ChatRoom chatRoom, Member sender, ReceivedChatMessage message, LocalDateTime sendTime + ) { + return SendChatMessage.from(chatRoom, sender, message.content(), sendTime); + } }