From 448840c908097d0f76ebdfc1c18a912703b4ba88 Mon Sep 17 00:00:00 2001 From: Ssamssamukja <109636635+Ssamssamukja@users.noreply.github.com> Date: Tue, 27 May 2025 14:08:57 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20chat=20=EA=B4=80=EB=A0=A8=20entit?= =?UTF-8?q?y=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/domain/entity/ChatMessage.java | 21 ++++++++++++++++ .../chat/domain/entity/ChatNotification.java | 24 ++++++++++++++++++ .../domain/chat/domain/entity/ChatRoom.java | 25 +++++++++++++++++++ .../repository/ChatMessageRepository.java | 7 ++++++ 4 files changed, 77 insertions(+) create mode 100644 src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatMessage.java create mode 100644 src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatNotification.java create mode 100644 src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatRoom.java create mode 100644 src/main/java/study/spring_boot_c/domain/chat/domain/repository/ChatMessageRepository.java diff --git a/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatMessage.java b/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatMessage.java new file mode 100644 index 0000000..145c1ba --- /dev/null +++ b/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatMessage.java @@ -0,0 +1,21 @@ +package study.spring_boot_c.domain.chat.domain.entity; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.mongodb.core.mapping.Document; +import jakarta.persistence.*; + +import java.time.LocalDateTime; + +@Builder +@Getter +@Document(collection = "chat_messages") +public class ChatMessage { + @Id @GeneratedValue + private String id; + private Long roomId; + private Long senderId; + private String message; + private LocalDateTime timestamp; +} + diff --git a/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatNotification.java b/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatNotification.java new file mode 100644 index 0000000..1fe62e1 --- /dev/null +++ b/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatNotification.java @@ -0,0 +1,24 @@ +package study.spring_boot_c.domain.chat.domain.entity; + +import lombok.*; +import jakarta.persistence.*; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class ChatNotification { + @Id @GeneratedValue + private Long id; + + private Long memberId; + private Long chatRoomId; + private String previewMessage; + + private boolean isRead; + private LocalDateTime notifiedAt; +} + diff --git a/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatRoom.java b/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatRoom.java new file mode 100644 index 0000000..3450e32 --- /dev/null +++ b/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatRoom.java @@ -0,0 +1,25 @@ +package study.spring_boot_c.domain.chat.domain.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class ChatRoom { + @Id @GeneratedValue + private Long id; + + private String name; + + private LocalDateTime createdAt; + + @OneToMany(mappedBy = "chatRoom") + private List notifications; +} + diff --git a/src/main/java/study/spring_boot_c/domain/chat/domain/repository/ChatMessageRepository.java b/src/main/java/study/spring_boot_c/domain/chat/domain/repository/ChatMessageRepository.java new file mode 100644 index 0000000..a0bec12 --- /dev/null +++ b/src/main/java/study/spring_boot_c/domain/chat/domain/repository/ChatMessageRepository.java @@ -0,0 +1,7 @@ +package study.spring_boot_c.domain.chat.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import study.spring_boot_c.domain.chat.domain.entity.ChatMessage; + +public interface ChatMessageRepository extends JpaRepository { +} From 804e38fcb36facc576529f3f895ca098a24ede8d Mon Sep 17 00:00:00 2001 From: Ssamssamukja <109636635+Ssamssamukja@users.noreply.github.com> Date: Tue, 27 May 2025 14:09:22 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=EC=9B=B9=EC=86=8C=EC=BC=93,=20re?= =?UTF-8?q?dis=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 12 ++++++ .../global/config/RedisConfig.java | 38 ++++++++++++++++++ .../global/config/WebSocketConfig.java | 30 ++++++++++++++ .../global/redis/RedisPublisher.java | 17 ++++++++ .../global/redis/RedisSubscriber.java | 40 +++++++++++++++++++ 5 files changed, 137 insertions(+) create mode 100644 src/main/java/study/spring_boot_c/global/config/RedisConfig.java create mode 100644 src/main/java/study/spring_boot_c/global/config/WebSocketConfig.java create mode 100644 src/main/java/study/spring_boot_c/global/redis/RedisPublisher.java create mode 100644 src/main/java/study/spring_boot_c/global/redis/RedisSubscriber.java diff --git a/build.gradle b/build.gradle index 210ac79..64d943c 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,18 @@ dependencies { //swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' + + // WebSocket + implementation 'org.springframework.boot:spring-boot-starter-websocket' + + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + // MongoDB + implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' + + // JSON 파싱 + implementation 'com.fasterxml.jackson.core:jackson-databind' } tasks.named('test') { diff --git a/src/main/java/study/spring_boot_c/global/config/RedisConfig.java b/src/main/java/study/spring_boot_c/global/config/RedisConfig.java new file mode 100644 index 0000000..301002d --- /dev/null +++ b/src/main/java/study/spring_boot_c/global/config/RedisConfig.java @@ -0,0 +1,38 @@ +package study.spring_boot_c.global.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.PatternTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import study.spring_boot_c.global.redis.RedisSubscriber; + +@Configuration +@RequiredArgsConstructor +public class RedisConfig { + + private final RedisConnectionFactory redisConnectionFactory; + private final RedisSubscriber redisSubscriber; + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new StringRedisSerializer()); // JSON 문자열로 전송 + return template; + } + + @Bean + public RedisMessageListenerContainer redisMessageListenerContainer() { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(redisConnectionFactory); + container.addMessageListener(new MessageListenerAdapter(redisSubscriber, "onMessage"), new PatternTopic("chatroom:*")); + return container; + } +} + diff --git a/src/main/java/study/spring_boot_c/global/config/WebSocketConfig.java b/src/main/java/study/spring_boot_c/global/config/WebSocketConfig.java new file mode 100644 index 0000000..9a49f8a --- /dev/null +++ b/src/main/java/study/spring_boot_c/global/config/WebSocketConfig.java @@ -0,0 +1,30 @@ +package study.spring_boot_c.global.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 WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + @Override + public void configureMessageBroker(MessageBrokerRegistry config) { + // 구독용 prefix (서버 → 클라이언트) + config.enableSimpleBroker("/topic"); + + // 클라이언트가 메시지 보낼 때 사용하는 prefix + config.setApplicationDestinationPrefixes("/app"); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + // WebSocket 엔드포인트 설정 (SockJS 지원) + registry.addEndpoint("/ws-chat") + .setAllowedOriginPatterns("*") + .withSockJS(); + } +} + diff --git a/src/main/java/study/spring_boot_c/global/redis/RedisPublisher.java b/src/main/java/study/spring_boot_c/global/redis/RedisPublisher.java new file mode 100644 index 0000000..a49299b --- /dev/null +++ b/src/main/java/study/spring_boot_c/global/redis/RedisPublisher.java @@ -0,0 +1,17 @@ +package study.spring_boot_c.global.redis; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class RedisPublisher { + + private final RedisTemplate redisTemplate; + + public void publish(String channel, String messageJson) { + redisTemplate.convertAndSend(channel, messageJson); + } +} + diff --git a/src/main/java/study/spring_boot_c/global/redis/RedisSubscriber.java b/src/main/java/study/spring_boot_c/global/redis/RedisSubscriber.java new file mode 100644 index 0000000..1989141 --- /dev/null +++ b/src/main/java/study/spring_boot_c/global/redis/RedisSubscriber.java @@ -0,0 +1,40 @@ +package study.spring_boot_c.global.redis; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.connection.Message; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Component; +import study.spring_boot_c.domain.chat.converter.ChatMessageConverter; +import study.spring_boot_c.domain.chat.domain.entity.ChatMessage; +import study.spring_boot_c.domain.chat.domain.repository.ChatMessageRepository; +import study.spring_boot_c.domain.chat.dto.ChatMessageDTO; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; + +@Component +@RequiredArgsConstructor +public class RedisSubscriber { + + private final SimpMessagingTemplate messagingTemplate; + private final ChatMessageRepository chatMessageRepository; + private final ObjectMapper objectMapper; + + public void onMessage(Message message, byte[] pattern) { + try { + String body = new String(message.getBody(), StandardCharsets.UTF_8); + ChatMessageDTO.MessageReceive chatMessage = objectMapper.readValue(body, ChatMessageDTO.MessageReceive.class); + + // 1. WebSocket으로 클라이언트에게 전송 + messagingTemplate.convertAndSend("/topic/chatroom/" + chatMessage.getRoomId(), chatMessage); + + // 2. MongoDB 저장 + chatMessageRepository.save(ChatMessageConverter.toChatMessage(chatMessage)); + + } catch (Exception e) { + e.printStackTrace(); + } + } +} + From 408bc74234b1f88ced67cb0e33dbdf99682f60a2 Mon Sep 17 00:00:00 2001 From: Ssamssamukja <109636635+Ssamssamukja@users.noreply.github.com> Date: Tue, 27 May 2025 14:09:40 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20chatService=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/application/ChatService.java | 7 ++++ .../chat/application/ChatServiceImpl.java | 36 +++++++++++++++++++ .../chat/converter/ChatMessageConverter.java | 20 +++++++++++ .../domain/chat/dto/ChatMessageDTO.java | 22 ++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 src/main/java/study/spring_boot_c/domain/chat/application/ChatService.java create mode 100644 src/main/java/study/spring_boot_c/domain/chat/application/ChatServiceImpl.java create mode 100644 src/main/java/study/spring_boot_c/domain/chat/converter/ChatMessageConverter.java create mode 100644 src/main/java/study/spring_boot_c/domain/chat/dto/ChatMessageDTO.java diff --git a/src/main/java/study/spring_boot_c/domain/chat/application/ChatService.java b/src/main/java/study/spring_boot_c/domain/chat/application/ChatService.java new file mode 100644 index 0000000..44d1fd3 --- /dev/null +++ b/src/main/java/study/spring_boot_c/domain/chat/application/ChatService.java @@ -0,0 +1,7 @@ +package study.spring_boot_c.domain.chat.application; + +import study.spring_boot_c.domain.chat.dto.ChatMessageDTO; + +public interface ChatService { + void sendMessage(ChatMessageDTO.MessageReceive dto); +} diff --git a/src/main/java/study/spring_boot_c/domain/chat/application/ChatServiceImpl.java b/src/main/java/study/spring_boot_c/domain/chat/application/ChatServiceImpl.java new file mode 100644 index 0000000..a61b977 --- /dev/null +++ b/src/main/java/study/spring_boot_c/domain/chat/application/ChatServiceImpl.java @@ -0,0 +1,36 @@ +package study.spring_boot_c.domain.chat.application; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import study.spring_boot_c.domain.chat.converter.ChatMessageConverter; +import study.spring_boot_c.domain.chat.domain.repository.ChatMessageRepository; +import study.spring_boot_c.domain.chat.dto.ChatMessageDTO; +import study.spring_boot_c.global.redis.RedisPublisher; + +@Service +@RequiredArgsConstructor +public class ChatServiceImpl implements ChatService{ + private final RedisPublisher redisPublisher; + private final ChatMessageRepository chatMessageRepository; // MongoDB 저장용 + private final ObjectMapper objectMapper; + + /** + * 채팅 메시지를 Redis로 발행하고 MongoDB에 저장한다 + */ + public void sendMessage(ChatMessageDTO.MessageReceive dto) { + try { + // 1. MongoDB에 저장 + chatMessageRepository.save(ChatMessageConverter.toChatMessage(dto)); + + // 2. Redis 채널에 메시지 발행 + String json = objectMapper.writeValueAsString(dto); + String channel = "chatroom:" + dto.getRoomId(); // 예: chatroom:1 + redisPublisher.publish(channel, json); + + } catch (JsonProcessingException e) { + throw new RuntimeException("메시지 직렬화 실패", e); + } + } +} diff --git a/src/main/java/study/spring_boot_c/domain/chat/converter/ChatMessageConverter.java b/src/main/java/study/spring_boot_c/domain/chat/converter/ChatMessageConverter.java new file mode 100644 index 0000000..61b80eb --- /dev/null +++ b/src/main/java/study/spring_boot_c/domain/chat/converter/ChatMessageConverter.java @@ -0,0 +1,20 @@ +package study.spring_boot_c.domain.chat.converter; + +import org.springframework.stereotype.Component; +import study.spring_boot_c.domain.chat.domain.entity.ChatMessage; +import study.spring_boot_c.domain.chat.dto.ChatMessageDTO; + +import java.time.LocalDateTime; + +@Component +public class ChatMessageConverter { + + public static ChatMessage toChatMessage(ChatMessageDTO.MessageReceive dto) { + return ChatMessage.builder() + .roomId(dto.getRoomId()) + .senderId(dto.getSenderId()) + .message(dto.getMessage()) + .timestamp(LocalDateTime.now()) + .build(); + } +} diff --git a/src/main/java/study/spring_boot_c/domain/chat/dto/ChatMessageDTO.java b/src/main/java/study/spring_boot_c/domain/chat/dto/ChatMessageDTO.java new file mode 100644 index 0000000..7a88cf7 --- /dev/null +++ b/src/main/java/study/spring_boot_c/domain/chat/dto/ChatMessageDTO.java @@ -0,0 +1,22 @@ +package study.spring_boot_c.domain.chat.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class ChatMessageDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class MessageReceive { + private Long roomId; + private Long senderId; + private String message; + private LocalDateTime timestamp; + } +} From dee08fadf34627c3c3771086f919855affee0bfb Mon Sep 17 00:00:00 2001 From: Ssamssamukja <109636635+Ssamssamukja@users.noreply.github.com> Date: Wed, 28 May 2025 12:26:57 +0900 Subject: [PATCH 04/10] =?UTF-8?q?chore:=20=EC=8B=A4=ED=96=89=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring_boot_c/domain/member/domain/entity/Manner.java | 4 ++-- .../spring_boot_c/domain/member/domain/entity/SaleReview.java | 4 ++-- .../domain/notification/domain/entity/CarrotNotification.java | 4 ++-- .../domain/repository/CarrotNotificationRepository.java | 3 ++- .../spring_boot_c/domain/product/domain/entity/Product.java | 4 ++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/study/spring_boot_c/domain/member/domain/entity/Manner.java b/src/main/java/study/spring_boot_c/domain/member/domain/entity/Manner.java index 78b71ca..652241f 100644 --- a/src/main/java/study/spring_boot_c/domain/member/domain/entity/Manner.java +++ b/src/main/java/study/spring_boot_c/domain/member/domain/entity/Manner.java @@ -19,11 +19,11 @@ public class Manner extends BaseEntity { private int score; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = false) + @JoinColumn(name = "evaluatee_id", nullable = false) private Member evaluatee; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = false) + @JoinColumn(name = "evaluator_id", nullable = false) private Member evaluator; } diff --git a/src/main/java/study/spring_boot_c/domain/member/domain/entity/SaleReview.java b/src/main/java/study/spring_boot_c/domain/member/domain/entity/SaleReview.java index 2b8bfba..8178487 100644 --- a/src/main/java/study/spring_boot_c/domain/member/domain/entity/SaleReview.java +++ b/src/main/java/study/spring_boot_c/domain/member/domain/entity/SaleReview.java @@ -22,11 +22,11 @@ public class SaleReview extends BaseEntity { private String content; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = false) + @JoinColumn(name = "reviewer_id", nullable = false) private Member reviewer; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = false) + @JoinColumn(name = "reviewee_id", nullable = false) private Member reviewee; @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/study/spring_boot_c/domain/notification/domain/entity/CarrotNotification.java b/src/main/java/study/spring_boot_c/domain/notification/domain/entity/CarrotNotification.java index c152b62..107bc2e 100644 --- a/src/main/java/study/spring_boot_c/domain/notification/domain/entity/CarrotNotification.java +++ b/src/main/java/study/spring_boot_c/domain/notification/domain/entity/CarrotNotification.java @@ -19,11 +19,11 @@ public class CarrotNotification extends BaseEntity { private int score; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = false) + @JoinColumn(name = "evaluatee_id", nullable = false) private Member evaluatee; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = false) + @JoinColumn(name = "evaluator_id", nullable = false) private Member evaluator; } diff --git a/src/main/java/study/spring_boot_c/domain/notification/domain/repository/CarrotNotificationRepository.java b/src/main/java/study/spring_boot_c/domain/notification/domain/repository/CarrotNotificationRepository.java index e7253b6..b343496 100644 --- a/src/main/java/study/spring_boot_c/domain/notification/domain/repository/CarrotNotificationRepository.java +++ b/src/main/java/study/spring_boot_c/domain/notification/domain/repository/CarrotNotificationRepository.java @@ -1,6 +1,7 @@ package study.spring_boot_c.domain.notification.domain.repository; import org.springframework.data.jpa.repository.JpaRepository; +import study.spring_boot_c.domain.notification.domain.entity.CarrotNotification; -public interface CarrotNotificationRepository extends JpaRepository { +public interface CarrotNotificationRepository extends JpaRepository { } diff --git a/src/main/java/study/spring_boot_c/domain/product/domain/entity/Product.java b/src/main/java/study/spring_boot_c/domain/product/domain/entity/Product.java index 8816a6f..2de5c11 100644 --- a/src/main/java/study/spring_boot_c/domain/product/domain/entity/Product.java +++ b/src/main/java/study/spring_boot_c/domain/product/domain/entity/Product.java @@ -41,11 +41,11 @@ public class Product extends BaseEntity { private Category category; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = false) + @JoinColumn(name = "seller_id", nullable = false) private Member seller; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = false) + @JoinColumn(name = "buyer_id", nullable = false) private Member buyer; } From f585996ef31e09c512724da3f726dc6721cc8b8b Mon Sep 17 00:00:00 2001 From: Ssamssamukja <109636635+Ssamssamukja@users.noreply.github.com> Date: Wed, 28 May 2025 12:27:32 +0900 Subject: [PATCH 05/10] =?UTF-8?q?refactor:=20redisSubscriber=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../study/spring_boot_c/global/redis/RedisSubscriber.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/study/spring_boot_c/global/redis/RedisSubscriber.java b/src/main/java/study/spring_boot_c/global/redis/RedisSubscriber.java index 1989141..ad5c4bc 100644 --- a/src/main/java/study/spring_boot_c/global/redis/RedisSubscriber.java +++ b/src/main/java/study/spring_boot_c/global/redis/RedisSubscriber.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Component; import study.spring_boot_c.domain.chat.converter.ChatMessageConverter; @@ -15,7 +16,7 @@ @Component @RequiredArgsConstructor -public class RedisSubscriber { +public class RedisSubscriber implements MessageListener { private final SimpMessagingTemplate messagingTemplate; private final ChatMessageRepository chatMessageRepository; @@ -29,9 +30,6 @@ public void onMessage(Message message, byte[] pattern) { // 1. WebSocket으로 클라이언트에게 전송 messagingTemplate.convertAndSend("/topic/chatroom/" + chatMessage.getRoomId(), chatMessage); - // 2. MongoDB 저장 - chatMessageRepository.save(ChatMessageConverter.toChatMessage(chatMessage)); - } catch (Exception e) { e.printStackTrace(); } From a9522b60b55b2547f7c9bb3efd8df6478c95e76d Mon Sep 17 00:00:00 2001 From: Ssamssamukja <109636635+Ssamssamukja@users.noreply.github.com> Date: Wed, 28 May 2025 12:27:43 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20SecurityConfig=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SecurityConfig.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/study/spring_boot_c/global/config/SecurityConfig.java diff --git a/src/main/java/study/spring_boot_c/global/config/SecurityConfig.java b/src/main/java/study/spring_boot_c/global/config/SecurityConfig.java new file mode 100644 index 0000000..960f878 --- /dev/null +++ b/src/main/java/study/spring_boot_c/global/config/SecurityConfig.java @@ -0,0 +1,24 @@ +package study.spring_boot_c.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/ws-chat/**").permitAll() // WebSocket 허용 + .anyRequest().permitAll() + ); + return http.build(); + } +} + From d616ae0562bf85a76ee4763d6848ad4b0e44bb90 Mon Sep 17 00:00:00 2001 From: Ssamssamukja <109636635+Ssamssamukja@users.noreply.github.com> Date: Wed, 28 May 2025 12:28:26 +0900 Subject: [PATCH 07/10] =?UTF-8?q?refactor:=20entity=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/domain/entity/ChatMessage.java | 5 +++-- .../domain/chat/domain/entity/ChatNotification.java | 12 ++++++++---- .../domain/chat/domain/entity/ChatRoom.java | 5 +++-- .../domain/repository/ChatMessageRepository.java | 4 ++-- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatMessage.java b/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatMessage.java index 145c1ba..4620215 100644 --- a/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatMessage.java +++ b/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatMessage.java @@ -2,8 +2,9 @@ import lombok.Builder; import lombok.Getter; +import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; -import jakarta.persistence.*; +import study.spring_boot_c.domain.model.entity.BaseEntity; import java.time.LocalDateTime; @@ -11,7 +12,7 @@ @Getter @Document(collection = "chat_messages") public class ChatMessage { - @Id @GeneratedValue + @Id private String id; private Long roomId; private Long senderId; diff --git a/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatNotification.java b/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatNotification.java index 1fe62e1..a61f1d7 100644 --- a/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatNotification.java +++ b/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatNotification.java @@ -2,23 +2,27 @@ import lombok.*; import jakarta.persistence.*; +import study.spring_boot_c.domain.model.entity.BaseEntity; import java.time.LocalDateTime; +import java.util.List; @Entity @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -public class ChatNotification { - @Id @GeneratedValue +public class ChatNotification extends BaseEntity { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long memberId; - private Long chatRoomId; private String previewMessage; - private boolean isRead; private LocalDateTime notifiedAt; + + @ManyToOne + @JoinColumn(name = "chat_room_id") + private ChatRoom chatRoom; } diff --git a/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatRoom.java b/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatRoom.java index 3450e32..571f7e5 100644 --- a/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatRoom.java +++ b/src/main/java/study/spring_boot_c/domain/chat/domain/entity/ChatRoom.java @@ -2,6 +2,7 @@ import jakarta.persistence.*; import lombok.*; +import study.spring_boot_c.domain.model.entity.BaseEntity; import java.time.LocalDateTime; import java.util.List; @@ -11,8 +12,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -public class ChatRoom { - @Id @GeneratedValue +public class ChatRoom extends BaseEntity { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; diff --git a/src/main/java/study/spring_boot_c/domain/chat/domain/repository/ChatMessageRepository.java b/src/main/java/study/spring_boot_c/domain/chat/domain/repository/ChatMessageRepository.java index a0bec12..f65c43f 100644 --- a/src/main/java/study/spring_boot_c/domain/chat/domain/repository/ChatMessageRepository.java +++ b/src/main/java/study/spring_boot_c/domain/chat/domain/repository/ChatMessageRepository.java @@ -1,7 +1,7 @@ package study.spring_boot_c.domain.chat.domain.repository; -import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.mongodb.repository.MongoRepository; import study.spring_boot_c.domain.chat.domain.entity.ChatMessage; -public interface ChatMessageRepository extends JpaRepository { +public interface ChatMessageRepository extends MongoRepository { } From 76e3ff5a86eaf00777019694b0ba8cd1a4fbab61 Mon Sep 17 00:00:00 2001 From: Ssamssamukja <109636635+Ssamssamukja@users.noreply.github.com> Date: Wed, 28 May 2025 12:28:44 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20Service=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/application/ChatServiceImpl.java | 22 +++++++++++++------ .../domain/chat/exception/ChatException.java | 11 ++++++++++ .../global/error/code/status/ErrorStatus.java | 6 ++++- .../error/code/status/SuccessStatus.java | 5 ++++- 4 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 src/main/java/study/spring_boot_c/domain/chat/exception/ChatException.java diff --git a/src/main/java/study/spring_boot_c/domain/chat/application/ChatServiceImpl.java b/src/main/java/study/spring_boot_c/domain/chat/application/ChatServiceImpl.java index a61b977..7752f2e 100644 --- a/src/main/java/study/spring_boot_c/domain/chat/application/ChatServiceImpl.java +++ b/src/main/java/study/spring_boot_c/domain/chat/application/ChatServiceImpl.java @@ -5,8 +5,11 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import study.spring_boot_c.domain.chat.converter.ChatMessageConverter; +import study.spring_boot_c.domain.chat.domain.entity.ChatMessage; import study.spring_boot_c.domain.chat.domain.repository.ChatMessageRepository; import study.spring_boot_c.domain.chat.dto.ChatMessageDTO; +import study.spring_boot_c.domain.chat.exception.ChatException; +import study.spring_boot_c.global.error.code.status.ErrorStatus; import study.spring_boot_c.global.redis.RedisPublisher; @Service @@ -20,17 +23,22 @@ public class ChatServiceImpl implements ChatService{ * 채팅 메시지를 Redis로 발행하고 MongoDB에 저장한다 */ public void sendMessage(ChatMessageDTO.MessageReceive dto) { + // 멤버 체크 + 방 체크 구현 로직 필요 + ChatMessage message = ChatMessageConverter.toChatMessage(dto); + String channel = "chatroom:" + dto.getRoomId(); + try { - // 1. MongoDB에 저장 - chatMessageRepository.save(ChatMessageConverter.toChatMessage(dto)); + chatMessageRepository.save(message); + } catch (Exception e) { + throw new ChatException(ErrorStatus.DB_ERROR); + } - // 2. Redis 채널에 메시지 발행 - String json = objectMapper.writeValueAsString(dto); - String channel = "chatroom:" + dto.getRoomId(); // 예: chatroom:1 + try { + String json = objectMapper.writeValueAsString(message); redisPublisher.publish(channel, json); - } catch (JsonProcessingException e) { - throw new RuntimeException("메시지 직렬화 실패", e); + throw new ChatException(ErrorStatus.REDIS_ERROR); } + } } diff --git a/src/main/java/study/spring_boot_c/domain/chat/exception/ChatException.java b/src/main/java/study/spring_boot_c/domain/chat/exception/ChatException.java new file mode 100644 index 0000000..3137ae5 --- /dev/null +++ b/src/main/java/study/spring_boot_c/domain/chat/exception/ChatException.java @@ -0,0 +1,11 @@ +package study.spring_boot_c.domain.chat.exception; + +import study.spring_boot_c.global.error.code.BaseErrorCode; +import study.spring_boot_c.global.error.exception.GeneralException; + +public class ChatException extends GeneralException { + + public ChatException(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/study/spring_boot_c/global/error/code/status/ErrorStatus.java b/src/main/java/study/spring_boot_c/global/error/code/status/ErrorStatus.java index 51b9727..cb01846 100644 --- a/src/main/java/study/spring_boot_c/global/error/code/status/ErrorStatus.java +++ b/src/main/java/study/spring_boot_c/global/error/code/status/ErrorStatus.java @@ -16,9 +16,13 @@ public enum ErrorStatus implements BaseErrorCode { _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), //Member - NO_SUCH_MEMBER(HttpStatus.BAD_REQUEST, "MEMBER_4001","멤버가 존재하지 않습니다.") + NO_SUCH_MEMBER(HttpStatus.BAD_REQUEST, "MEMBER_4001","멤버가 존재하지 않습니다."), + //DB + DB_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "DB_5001", "데이터베이스 오류가 발생했습니다."), + //Redis + REDIS_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "REDIS_5001", "Redis 서버 오류가 발생했습니다.") ; private final HttpStatus httpStatus; diff --git a/src/main/java/study/spring_boot_c/global/error/code/status/SuccessStatus.java b/src/main/java/study/spring_boot_c/global/error/code/status/SuccessStatus.java index d939632..d638d6c 100644 --- a/src/main/java/study/spring_boot_c/global/error/code/status/SuccessStatus.java +++ b/src/main/java/study/spring_boot_c/global/error/code/status/SuccessStatus.java @@ -13,7 +13,10 @@ public enum SuccessStatus implements BaseCode { _CREATED(HttpStatus.CREATED, "COMMON201", "요청 성공 및 리소스 생성됨"), //member - MEMBER_EXAMPLE_SUCCESS(HttpStatus.OK,"MEMBER_200","성공적으로 조회되었습니다.") + MEMBER_EXAMPLE_SUCCESS(HttpStatus.OK,"MEMBER_200","성공적으로 조회되었습니다."), + + //chat + CHAT_SEND_SUCCESS(HttpStatus.OK,"CHAT_200","성공적으로 처리되었습니다.") ; From 075f0efa809599850523cf1a49ca9e971594b698 Mon Sep 17 00:00:00 2001 From: Ssamssamukja <109636635+Ssamssamukja@users.noreply.github.com> Date: Wed, 28 May 2025 12:29:08 +0900 Subject: [PATCH 09/10] =?UTF-8?q?feat:=20ChatController=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/api/ChatController.java | 48 +++++++++++++++++++ .../chat/api/ChatWebSocketController.java | 20 ++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/main/java/study/spring_boot_c/domain/chat/api/ChatController.java create mode 100644 src/main/java/study/spring_boot_c/domain/chat/api/ChatWebSocketController.java diff --git a/src/main/java/study/spring_boot_c/domain/chat/api/ChatController.java b/src/main/java/study/spring_boot_c/domain/chat/api/ChatController.java new file mode 100644 index 0000000..f06ad22 --- /dev/null +++ b/src/main/java/study/spring_boot_c/domain/chat/api/ChatController.java @@ -0,0 +1,48 @@ +package study.spring_boot_c.domain.chat.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import study.spring_boot_c.domain.chat.application.ChatService; +import study.spring_boot_c.domain.chat.dto.ChatMessageDTO; +import study.spring_boot_c.global.common.response.BaseResponse; +import study.spring_boot_c.global.error.code.status.SuccessStatus; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/chat") +@Validated +public class ChatController { + + private final ChatService chatService; + + @PostMapping("/send") + @Operation(summary = "채팅 메시지 전송 API", description = "클라이언트가 채팅 메시지를 전송할 때 사용하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "CHAT_200", description = "메시지 전송 성공") + }) + @Parameters({ + @Parameter(name = "roomId", description = "채팅방 ID"), + @Parameter(name = "senderId", description = "메시지를 보낸 사용자 ID"), + @Parameter(name = "message", description = "보낼 메시지 내용") + }) + public BaseResponse sendChatMessage(@Valid @RequestBody ChatMessageDTO.MessageReceive request) { + + chatService.sendMessage(request); + + return BaseResponse.onSuccess(SuccessStatus.CHAT_SEND_SUCCESS, null); + } +} + diff --git a/src/main/java/study/spring_boot_c/domain/chat/api/ChatWebSocketController.java b/src/main/java/study/spring_boot_c/domain/chat/api/ChatWebSocketController.java new file mode 100644 index 0000000..299b07c --- /dev/null +++ b/src/main/java/study/spring_boot_c/domain/chat/api/ChatWebSocketController.java @@ -0,0 +1,20 @@ +package study.spring_boot_c.domain.chat.api; + +import lombok.RequiredArgsConstructor; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.stereotype.Controller; +import study.spring_boot_c.domain.chat.application.ChatService; +import study.spring_boot_c.domain.chat.dto.ChatMessageDTO; + +@Controller +@RequiredArgsConstructor +public class ChatWebSocketController { + + private final ChatService chatService; + + @MessageMapping("/chat/send") + public void handleWebSocketMessage(ChatMessageDTO.MessageReceive message) { + chatService.sendMessage(message); + } +} + From 04bef6e4d1669b6a844001080dc2caf76e52b297 Mon Sep 17 00:00:00 2001 From: Ssamssamukja <109636635+Ssamssamukja@users.noreply.github.com> Date: Wed, 28 May 2025 14:01:41 +0900 Subject: [PATCH 10/10] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9id?= =?UTF-8?q?=EB=A1=9C=20=EC=B1=84=ED=8C=85=EB=82=B4=EC=97=AD=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/api/ChatController.java | 30 ++++++++++++++----- .../domain/chat/application/ChatService.java | 4 +++ .../chat/application/ChatServiceImpl.java | 16 +++++++--- .../chat/converter/ChatMessageConverter.java | 8 +++++ .../repository/ChatMessageRepository.java | 3 ++ .../domain/chat/dto/ChatMessageDTO.java | 10 +++++++ 6 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/main/java/study/spring_boot_c/domain/chat/api/ChatController.java b/src/main/java/study/spring_boot_c/domain/chat/api/ChatController.java index f06ad22..436ca34 100644 --- a/src/main/java/study/spring_boot_c/domain/chat/api/ChatController.java +++ b/src/main/java/study/spring_boot_c/domain/chat/api/ChatController.java @@ -1,25 +1,24 @@ package study.spring_boot_c.domain.chat.api; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.stereotype.Controller; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import study.spring_boot_c.domain.chat.application.ChatService; import study.spring_boot_c.domain.chat.dto.ChatMessageDTO; import study.spring_boot_c.global.common.response.BaseResponse; import study.spring_boot_c.global.error.code.status.SuccessStatus; +import java.util.List; + @RestController @RequiredArgsConstructor @RequestMapping("/chat") @@ -44,5 +43,20 @@ public BaseResponse sendChatMessage(@Valid @RequestBody ChatMessageDTO.Mes return BaseResponse.onSuccess(SuccessStatus.CHAT_SEND_SUCCESS, null); } + + @GetMapping("/room/{roomId}/message") + @Operation(summary = "채팅방 메시지 조회 API", description = "클라이언트가 채팅 메시지를 전송할 때 사용하는 API입니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "CHAT_200", description = "메시지 조회 성공") + }) + public BaseResponse> getMessagesByRoomId(@Valid @PathVariable Long roomId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size) { + + Pageable pageable = PageRequest.of(page, size, Sort.by("timestamp").ascending()); + Page result = chatService.getMessagesByRoomId(roomId, pageable); + + return BaseResponse.onSuccess(SuccessStatus.CHAT_SEND_SUCCESS, result); + } } diff --git a/src/main/java/study/spring_boot_c/domain/chat/application/ChatService.java b/src/main/java/study/spring_boot_c/domain/chat/application/ChatService.java index 44d1fd3..2876420 100644 --- a/src/main/java/study/spring_boot_c/domain/chat/application/ChatService.java +++ b/src/main/java/study/spring_boot_c/domain/chat/application/ChatService.java @@ -1,7 +1,11 @@ package study.spring_boot_c.domain.chat.application; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import study.spring_boot_c.domain.chat.dto.ChatMessageDTO; public interface ChatService { void sendMessage(ChatMessageDTO.MessageReceive dto); + + Page getMessagesByRoomId(Long roomId, Pageable pageable); } diff --git a/src/main/java/study/spring_boot_c/domain/chat/application/ChatServiceImpl.java b/src/main/java/study/spring_boot_c/domain/chat/application/ChatServiceImpl.java index 7752f2e..088c9c6 100644 --- a/src/main/java/study/spring_boot_c/domain/chat/application/ChatServiceImpl.java +++ b/src/main/java/study/spring_boot_c/domain/chat/application/ChatServiceImpl.java @@ -3,7 +3,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import study.spring_boot_c.domain.chat.converter.ChatMessageConverter; import study.spring_boot_c.domain.chat.domain.entity.ChatMessage; import study.spring_boot_c.domain.chat.domain.repository.ChatMessageRepository; @@ -19,9 +22,8 @@ public class ChatServiceImpl implements ChatService{ private final ChatMessageRepository chatMessageRepository; // MongoDB 저장용 private final ObjectMapper objectMapper; - /** - * 채팅 메시지를 Redis로 발행하고 MongoDB에 저장한다 - */ + @Transactional + @Override public void sendMessage(ChatMessageDTO.MessageReceive dto) { // 멤버 체크 + 방 체크 구현 로직 필요 ChatMessage message = ChatMessageConverter.toChatMessage(dto); @@ -36,9 +38,15 @@ public void sendMessage(ChatMessageDTO.MessageReceive dto) { try { String json = objectMapper.writeValueAsString(message); redisPublisher.publish(channel, json); - } catch (JsonProcessingException e) { + } catch (Exception e) { throw new ChatException(ErrorStatus.REDIS_ERROR); } } + + @Override + public Page getMessagesByRoomId(Long roomId, Pageable pageable) { + return chatMessageRepository.findByRoomIdOrderByTimestampAsc(roomId, pageable) + .map(ChatMessageConverter::toRoomMessages); + } } diff --git a/src/main/java/study/spring_boot_c/domain/chat/converter/ChatMessageConverter.java b/src/main/java/study/spring_boot_c/domain/chat/converter/ChatMessageConverter.java index 61b80eb..744592d 100644 --- a/src/main/java/study/spring_boot_c/domain/chat/converter/ChatMessageConverter.java +++ b/src/main/java/study/spring_boot_c/domain/chat/converter/ChatMessageConverter.java @@ -17,4 +17,12 @@ public static ChatMessage toChatMessage(ChatMessageDTO.MessageReceive dto) { .timestamp(LocalDateTime.now()) .build(); } + + public static ChatMessageDTO.RoomMessage toRoomMessages(ChatMessage entity) { + return ChatMessageDTO.RoomMessage.builder() + .senderId(entity.getSenderId()) + .message(entity.getMessage()) + .timestamp(entity.getTimestamp()) + .build(); + } } diff --git a/src/main/java/study/spring_boot_c/domain/chat/domain/repository/ChatMessageRepository.java b/src/main/java/study/spring_boot_c/domain/chat/domain/repository/ChatMessageRepository.java index f65c43f..d9db20a 100644 --- a/src/main/java/study/spring_boot_c/domain/chat/domain/repository/ChatMessageRepository.java +++ b/src/main/java/study/spring_boot_c/domain/chat/domain/repository/ChatMessageRepository.java @@ -1,7 +1,10 @@ package study.spring_boot_c.domain.chat.domain.repository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; import study.spring_boot_c.domain.chat.domain.entity.ChatMessage; public interface ChatMessageRepository extends MongoRepository { + Page findByRoomIdOrderByTimestampAsc(Long roomId, Pageable pageable); } diff --git a/src/main/java/study/spring_boot_c/domain/chat/dto/ChatMessageDTO.java b/src/main/java/study/spring_boot_c/domain/chat/dto/ChatMessageDTO.java index 7a88cf7..24aa402 100644 --- a/src/main/java/study/spring_boot_c/domain/chat/dto/ChatMessageDTO.java +++ b/src/main/java/study/spring_boot_c/domain/chat/dto/ChatMessageDTO.java @@ -19,4 +19,14 @@ public static class MessageReceive { private String message; private LocalDateTime timestamp; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class RoomMessage { + private Long senderId; + private String message; + private LocalDateTime timestamp; + } }