diff --git a/src/main/java/space/space_spring/config/WebSocketConfig.java b/src/main/java/space/space_spring/config/WebSocketConfig.java index b9458da4..62534fb5 100644 --- a/src/main/java/space/space_spring/config/WebSocketConfig.java +++ b/src/main/java/space/space_spring/config/WebSocketConfig.java @@ -1,14 +1,21 @@ package space.space_spring.config; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.ChannelRegistration; 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; +import space.space_spring.interceptor.jwtSocket.JwtChannelInterceptor; @Configuration @EnableWebSocketMessageBroker +@RequiredArgsConstructor public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + private final JwtChannelInterceptor jwtChannelInterceptor; + @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // stomp 접속 주소 url = ws://localhost:8080/ws @@ -23,6 +30,11 @@ public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic"); // 메세지 발행(송신) 요청의 엔드 포인트 - registry.setApplicationDestinationPrefixes("/app"); + registry.setApplicationDestinationPrefixes("/app", "/topic"); + } + + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + registration.interceptors(jwtChannelInterceptor); } } diff --git a/src/main/java/space/space_spring/controller/ChattingController.java b/src/main/java/space/space_spring/controller/ChattingController.java new file mode 100644 index 00000000..ef8f54fa --- /dev/null +++ b/src/main/java/space/space_spring/controller/ChattingController.java @@ -0,0 +1,56 @@ +package space.space_spring.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.messaging.handler.annotation.*; +import org.springframework.messaging.simp.annotation.SubscribeMapping; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.socket.messaging.SessionDisconnectEvent; +import space.space_spring.dto.chat.request.ChatMessageRequest; +import space.space_spring.dto.chat.response.ChatMessageLogResponse; +import space.space_spring.dto.chat.response.ChatMessageResponse; +import space.space_spring.service.ChattingService; +import space.space_spring.service.UserChatRoomService; + +import java.util.Map; + +@Slf4j +@RestController +@RequiredArgsConstructor +public class ChattingController { + + private final ChattingService chattingService; + + private final UserChatRoomService userChatRoomService; + + @MessageMapping("/chat/{chatRoomId}") // {chatRoomId} 채팅방으로 보낸 메세지 매핑 + @SendTo("/topic/chat/{chatRoomId}") // {chatRoomId} 채팅방을 구독한 곳들로 메세지 전송 + public ChatMessageResponse sendChatMessage (@Payload ChatMessageRequest chatMessageRequest, @DestinationVariable Long chatRoomId, + @Header("simpSessionAttributes") Map sessionAttributes) { + Long senderId = (Long) sessionAttributes.get("userId"); +// log.info(senderId + " 님이 " + chatRoomId + " 채팅방으로 " + chatMessageRequest.getContent() + " 전송"); + + return chattingService.sendChatMessage(senderId, chatMessageRequest, chatRoomId); + } + + @SubscribeMapping("/chat/{chatRoomId}") // {chatRoomId} 채팅방을 구독 + public ChatMessageLogResponse subscribeChatRoom (@DestinationVariable Long chatRoomId, @Header("simpSessionAttributes") Map sessionAttributes) { +// log.info(chatRoomId + " 채팅방 구독"); + sessionAttributes.put("chatRoomId", chatRoomId); + return chattingService.readChatMessageLog(chatRoomId); + } + + // socket disconnect 시 호출 + @EventListener + public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) { + StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage()); + Map sessionAttributes = headerAccessor.getSessionAttributes(); + + Long userId = (Long) sessionAttributes.get("userId"); + Long chatRoomId = (Long) sessionAttributes.get("chatRoomId"); + + userChatRoomService.saveLastReadTime(userId, chatRoomId); + } +} diff --git a/src/main/java/space/space_spring/controller/TestController.java b/src/main/java/space/space_spring/controller/TestController.java index 00e1ac0b..4f62a6c4 100644 --- a/src/main/java/space/space_spring/controller/TestController.java +++ b/src/main/java/space/space_spring/controller/TestController.java @@ -1,18 +1,10 @@ package space.space_spring.controller; import lombok.extern.slf4j.Slf4j; -import org.springframework.messaging.handler.annotation.DestinationVariable; -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.messaging.handler.annotation.Payload; -import org.springframework.messaging.handler.annotation.SendTo; -import org.springframework.messaging.simp.annotation.SubscribeMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import space.space_spring.argument_resolver.jwtLogin.JwtLoginAuth; -import space.space_spring.dao.PayDao; -import space.space_spring.dto.chat.request.ChatTestRequest; -import space.space_spring.dto.chat.response.ChatTestResponse; import space.space_spring.response.BaseResponse; @RestController @@ -30,16 +22,4 @@ public BaseResponse jwtLoginTest(@JwtLoginAuth Long userId) { return new BaseResponse<>("jwt login test 성공"); } - @MessageMapping("/chat/{spaceChatId}") // {spaceChatId} 채팅방으로 보낸 메세지 매핑 - @SendTo("/topic/chat/{spaceChatId}") // {spaceChatId} 채팅방을 구독한 곳들로 메세지 전송 - public ChatTestResponse sendMsgTest(@Payload ChatTestRequest chat, @DestinationVariable String spaceChatId) { - log.info(spaceChatId + " 채팅방으로 " + chat.getMsg() + " 전송"); - return ChatTestResponse.of(chat.getMsg()); - } - - @SubscribeMapping("/topic/chat/{spaceChatId}") // {spaceChatId} 채팅방을 구독 - public void subscribeTest(@DestinationVariable String spaceChatId) { - log.info(spaceChatId + " 채팅방 구독"); - } - } diff --git a/src/main/java/space/space_spring/dao/chat/ChattingDao.java b/src/main/java/space/space_spring/dao/chat/ChattingDao.java new file mode 100644 index 00000000..321997da --- /dev/null +++ b/src/main/java/space/space_spring/dao/chat/ChattingDao.java @@ -0,0 +1,17 @@ +package space.space_spring.dao.chat; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; +import space.space_spring.entity.document.ChatMessage; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface ChattingDao extends MongoRepository { + List findByChatRoomId(Long chatRoomId); + + ChatMessage findTopByChatRoomIdOrderByCreatedAtDesc(Long chatRoomId); + + int countByChatRoomIdAndCreatedAtBetween(Long chatRoomId, LocalDateTime lastReadTime, LocalDateTime lastUpdateTime); +} diff --git a/src/main/java/space/space_spring/dao/chat/UserChatRoomDao.java b/src/main/java/space/space_spring/dao/chat/UserChatRoomDao.java index e082353d..e14531ea 100644 --- a/src/main/java/space/space_spring/dao/chat/UserChatRoomDao.java +++ b/src/main/java/space/space_spring/dao/chat/UserChatRoomDao.java @@ -1,8 +1,11 @@ package space.space_spring.dao.chat; import org.springframework.data.jpa.repository.JpaRepository; +import space.space_spring.entity.ChatRoom; +import space.space_spring.entity.User; import space.space_spring.entity.UserChatRoom; public interface UserChatRoomDao extends JpaRepository { + UserChatRoom findByUserAndChatRoom(User userByUserId, ChatRoom chatRoomByChatRoomId); } diff --git a/src/main/java/space/space_spring/dto/chat/request/ChatMessageRequest.java b/src/main/java/space/space_spring/dto/chat/request/ChatMessageRequest.java new file mode 100644 index 00000000..bc88346b --- /dev/null +++ b/src/main/java/space/space_spring/dto/chat/request/ChatMessageRequest.java @@ -0,0 +1,22 @@ +package space.space_spring.dto.chat.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.NoArgsConstructor; +import space.space_spring.entity.enumStatus.ChatMessageType; + +import java.util.HashMap; + +@Getter +@NoArgsConstructor +public class ChatMessageRequest { + + @NotBlank(message = "메시지 내용은 공백일 수 없습니다.") + private HashMap content; + + @NotBlank(message = "스페이스 아이디는 공백일 수 없습니다.") + private Long spaceId; + + @NotBlank(message = "메시지 타입은 공백일 수 없습니다.") + private ChatMessageType messageType; +} diff --git a/src/main/java/space/space_spring/dto/chat/request/ChatTestRequest.java b/src/main/java/space/space_spring/dto/chat/request/ChatTestRequest.java deleted file mode 100644 index 883f4e88..00000000 --- a/src/main/java/space/space_spring/dto/chat/request/ChatTestRequest.java +++ /dev/null @@ -1,10 +0,0 @@ -package space.space_spring.dto.chat.request; - -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -public class ChatTestRequest { - private String msg; -} diff --git a/src/main/java/space/space_spring/dto/chat/request/CreateChatRoomRequest.java b/src/main/java/space/space_spring/dto/chat/request/CreateChatRoomRequest.java index ff593d04..bc941218 100644 --- a/src/main/java/space/space_spring/dto/chat/request/CreateChatRoomRequest.java +++ b/src/main/java/space/space_spring/dto/chat/request/CreateChatRoomRequest.java @@ -20,6 +20,5 @@ public class CreateChatRoomRequest { private MultipartFile img; - // TODO: member 조회 API 개발 시 수정 예정 - private List memberList; + private List memberList; } diff --git a/src/main/java/space/space_spring/dto/chat/response/ChatMessageLogResponse.java b/src/main/java/space/space_spring/dto/chat/response/ChatMessageLogResponse.java new file mode 100644 index 00000000..29f60a91 --- /dev/null +++ b/src/main/java/space/space_spring/dto/chat/response/ChatMessageLogResponse.java @@ -0,0 +1,18 @@ +package space.space_spring.dto.chat.response; + +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +public class ChatMessageLogResponse { + private List chatMessageLog; + + public static ChatMessageLogResponse of(List chatMessageList) { + return ChatMessageLogResponse.builder() + .chatMessageLog(chatMessageList) + .build(); + } +} diff --git a/src/main/java/space/space_spring/dto/chat/response/ChatMessageResponse.java b/src/main/java/space/space_spring/dto/chat/response/ChatMessageResponse.java new file mode 100644 index 00000000..1e283d4c --- /dev/null +++ b/src/main/java/space/space_spring/dto/chat/response/ChatMessageResponse.java @@ -0,0 +1,40 @@ +package space.space_spring.dto.chat.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import space.space_spring.entity.document.ChatMessage; +import space.space_spring.entity.enumStatus.ChatMessageType; + +import java.util.HashMap; + + +@Builder +@Getter +@AllArgsConstructor +public class ChatMessageResponse { + + private HashMap content; + + private String createdAt; + + private ChatMessageType messageType; + + private Long senderId; + + private String senderName; + + private String senderImg; + + public static ChatMessageResponse of(ChatMessage chatMessage) { + return ChatMessageResponse.builder() + .content(chatMessage.getContent()) + .createdAt(String.valueOf(chatMessage.getCreatedAt())) + .messageType(chatMessage.getMessageType()) + .senderId(chatMessage.getSenderId()) + .senderName(chatMessage.getSenderName()) + .senderImg(chatMessage.getSenderImg()) + .build(); + } + +} diff --git a/src/main/java/space/space_spring/dto/chat/response/ChatTestResponse.java b/src/main/java/space/space_spring/dto/chat/response/ChatTestResponse.java deleted file mode 100644 index 9466fad0..00000000 --- a/src/main/java/space/space_spring/dto/chat/response/ChatTestResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package space.space_spring.dto.chat.response; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -@Builder -@Getter -@AllArgsConstructor -public class ChatTestResponse { - private String msg; - - public static ChatTestResponse of(String msg) { - return ChatTestResponse.builder() - .msg(msg) - .build(); - } -} diff --git a/src/main/java/space/space_spring/dto/chat/response/CreateChatRoomResponse.java b/src/main/java/space/space_spring/dto/chat/response/CreateChatRoomResponse.java index f7892912..79c73d5b 100644 --- a/src/main/java/space/space_spring/dto/chat/response/CreateChatRoomResponse.java +++ b/src/main/java/space/space_spring/dto/chat/response/CreateChatRoomResponse.java @@ -8,11 +8,11 @@ @AllArgsConstructor @Builder public class CreateChatRoomResponse { - private Long id; + private Long chatRoomId; public static CreateChatRoomResponse of(Long id) { return CreateChatRoomResponse.builder() - .id(id) + .chatRoomId(id) .build(); } } diff --git a/src/main/java/space/space_spring/entity/ChatRoom.java b/src/main/java/space/space_spring/entity/ChatRoom.java index 5acc3701..f19a7b40 100644 --- a/src/main/java/space/space_spring/entity/ChatRoom.java +++ b/src/main/java/space/space_spring/entity/ChatRoom.java @@ -9,6 +9,9 @@ import org.hibernate.annotations.Comment; import space.space_spring.dto.chat.request.CreateChatRoomRequest; +import java.time.LocalDateTime; +import java.time.ZoneId; + @Entity @Getter @Builder @@ -37,22 +40,15 @@ public class ChatRoom extends BaseEntity{ @Column(name = "chat_room_img") private String img; - @Comment("마지막으로 전송된 메시지 ID") - @Nullable - @Column(name = "last_msg_id") - private int lastMsgId; - public static ChatRoom of(Space space, CreateChatRoomRequest createChatRoomRequest, String chatRoomImgUrl) { return ChatRoom.builder() .space(space) .name(createChatRoomRequest.getName()) .img(chatRoomImgUrl) - // TODO: 메시지 관련 처리 예정 - .lastMsgId(0) .build(); } -// // 양방향 매핑 + // // 양방향 매핑 // @OneToMany(mappedBy = "chatRoom", cascade = CascadeType.ALL) // private List userChatRooms; } diff --git a/src/main/java/space/space_spring/entity/UserChatRoom.java b/src/main/java/space/space_spring/entity/UserChatRoom.java index 889a17b7..4b36bbfc 100644 --- a/src/main/java/space/space_spring/entity/UserChatRoom.java +++ b/src/main/java/space/space_spring/entity/UserChatRoom.java @@ -8,6 +8,8 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.Comment; +import java.time.LocalDateTime; + @Entity @Getter @NoArgsConstructor @@ -32,16 +34,20 @@ public class UserChatRoom extends BaseEntity{ @JoinColumn(name = "user_id") private User user; - @Comment("마지막으로 읽은 메시지 ID") + @Comment("마지막으로 읽은 시간") @Nullable - @Column(name = "last_read_msg_id") - private Long lastReadMsgId; + @Column(name = "last_read_time") + private LocalDateTime lastReadTime; - public static UserChatRoom of(ChatRoom chatRoom, User user, Long lastReadMsgId) { + public static UserChatRoom of(ChatRoom chatRoom, User user, LocalDateTime lastReadTime) { return UserChatRoom.builder() .chatRoom(chatRoom) .user(user) - .lastReadMsgId(lastReadMsgId) + .lastReadTime(lastReadTime) .build(); } + + public void setLastReadTime(LocalDateTime lastReadTime) { + this.lastReadTime = lastReadTime; + } } diff --git a/src/main/java/space/space_spring/entity/document/ChatMessage.java b/src/main/java/space/space_spring/entity/document/ChatMessage.java new file mode 100644 index 00000000..d55a8128 --- /dev/null +++ b/src/main/java/space/space_spring/entity/document/ChatMessage.java @@ -0,0 +1,51 @@ +package space.space_spring.entity.document; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.mongodb.core.mapping.Document; +import space.space_spring.dto.chat.request.ChatMessageRequest; +import space.space_spring.entity.enumStatus.ChatMessageType; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.HashMap; + +@Document(collection = "chat_message") +@Getter +@Builder +@TypeAlias("ChatMessage") +public class ChatMessage { + @Id + private String id; + + private HashMap content; + + private Long chatRoomId; + + private Long spaceId; + + private Long senderId; + + private String senderName; + + private String senderImg; + + private ChatMessageType messageType; + + private LocalDateTime createdAt; + + public static ChatMessage of(ChatMessageRequest chatMessageRequest, Long chatRoomId, Long senderId, String senderName, String senderImg) { + return ChatMessage.builder() + .content(chatMessageRequest.getContent()) + .chatRoomId(chatRoomId) + .spaceId(chatMessageRequest.getSpaceId()) + .senderId(senderId) + .senderName(senderName) + .senderImg(senderImg) + .messageType(chatMessageRequest.getMessageType()) + .createdAt(LocalDateTime.now(ZoneId.of("Asia/Seoul"))) + .build(); + } +} diff --git a/src/main/java/space/space_spring/interceptor/jwtSocket/JwtChannelInterceptor.java b/src/main/java/space/space_spring/interceptor/jwtSocket/JwtChannelInterceptor.java new file mode 100644 index 00000000..38bad767 --- /dev/null +++ b/src/main/java/space/space_spring/interceptor/jwtSocket/JwtChannelInterceptor.java @@ -0,0 +1,60 @@ +package space.space_spring.interceptor.jwtSocket; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.stereotype.Component; +import space.space_spring.exception.jwt.bad_request.JwtNoTokenException; +import space.space_spring.exception.jwt.bad_request.JwtUnsupportedTokenException; +import space.space_spring.exception.jwt.unauthorized.JwtExpiredTokenException; +import space.space_spring.jwt.JwtLoginProvider; + +import static space.space_spring.response.status.BaseExceptionResponseStatus.*; + +@Slf4j +@Component +@RequiredArgsConstructor +public class JwtChannelInterceptor implements ChannelInterceptor { + private static final String JWT_TOKEN_PREFIX = "Bearer "; + + private final JwtLoginProvider jwtLoginProvider; + + @Override + public Message preSend(Message message, MessageChannel channel) { + StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); + + if (StompCommand.CONNECT.equals(accessor.getCommand())) { + String jwtToken = accessor.getFirstNativeHeader("Authorization"); + String validatedToken = validateAccessToken(jwtToken); + + // 검증 후 사용자 정보를 세션에 저장 + Long userId = jwtLoginProvider.getUserIdFromToken(validatedToken); + accessor.getSessionAttributes().put("userId", userId); + } + return message; + } + + private String validateAccessToken(String token) { + // token 존재 유무 validate + if (token == null) { + throw new JwtNoTokenException(TOKEN_NOT_FOUND); + } + if (!token.startsWith(JWT_TOKEN_PREFIX)) { + throw new JwtUnsupportedTokenException(UNSUPPORTED_TOKEN_TYPE); + } + + // prefix 제거 + String tokenWithoutPrefix = token.substring(JWT_TOKEN_PREFIX.length()); + + // access token 값 validate + if (jwtLoginProvider.isExpiredToken(tokenWithoutPrefix)) { + throw new JwtExpiredTokenException(EXPIRED_TOKEN); + } + + return tokenWithoutPrefix; + } +} diff --git a/src/main/java/space/space_spring/service/ChatRoomService.java b/src/main/java/space/space_spring/service/ChatRoomService.java index 13e05587..10d7df7c 100644 --- a/src/main/java/space/space_spring/service/ChatRoomService.java +++ b/src/main/java/space/space_spring/service/ChatRoomService.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Service; import space.space_spring.dao.UserDao; import space.space_spring.dao.chat.ChatRoomDao; +import space.space_spring.dao.chat.ChattingDao; import space.space_spring.dao.chat.UserChatRoomDao; import space.space_spring.dto.chat.response.ChatRoomResponse; import space.space_spring.dto.chat.request.CreateChatRoomRequest; @@ -15,9 +16,12 @@ import space.space_spring.entity.Space; import space.space_spring.entity.User; import space.space_spring.entity.UserChatRoom; +import space.space_spring.entity.document.ChatMessage; +import space.space_spring.entity.enumStatus.ChatMessageType; import space.space_spring.util.space.SpaceUtils; import space.space_spring.util.user.UserUtils; +import java.time.LocalDateTime; import java.util.List; @Service @@ -28,9 +32,11 @@ public class ChatRoomService { private final UserDao userDao; private final UserUtils userUtils; private final SpaceUtils spaceUtils; + private final ChattingDao chattingDao; private final ChatRoomDao chatRoomDao; private final UserChatRoomDao userChatRoomDao; + @Transactional public ReadChatRoomResponse readChatRooms(Long userId, Long spaceId) { // TODO 1: userId에 해당하는 user find User userByUserId = userUtils.findUserByUserId(userId); @@ -40,13 +46,34 @@ public ReadChatRoomResponse readChatRooms(Long userId, Long spaceId) { // TODO 3: 해당 user의 해당 space 내의 채팅방 리스트 return List result = chatRoomDao.findByUserAndSpace(userByUserId, spaceBySpaceId); + return ReadChatRoomResponse.of(result.stream() .map(cr -> { - // TODO: chatting message 처리 - String lastMsg = "메시지 관련 처리 예정"; - String lastTime = "메시지 관련 처리 예정"; - int unreadMsgCount = 1; - return ChatRoomResponse.of(cr, lastMsg, lastTime, unreadMsgCount); + // TODO 4: 각 채팅방의 마지막으로 업데이트된 메시지 정보 find + ChatMessage lastMsg = chattingDao.findTopByChatRoomIdOrderByCreatedAtDesc(cr.getId()); + LocalDateTime lastUpdateTime = lastMsg.getCreatedAt(); + String lastContent = switch (lastMsg.getMessageType()) { + case TEXT -> lastMsg.getContent().get("text"); + /** + * TODO: 메시지 타입 관련하여 미리보기 뷰에 따라 변경 가능 + */ +// case IMG -> "img"; +// case FILE -> "file"; +// case POST -> "post"; +// case PAY -> "pay"; + default -> lastMsg.getMessageType().toString(); + }; + + // TODO 5: 각 채팅방의 안읽은 메시지 개수 계산 + UserChatRoom userChatRoom = userChatRoomDao.findByUserAndChatRoom(userByUserId, cr); + LocalDateTime lastReadTime = userChatRoom.getLastReadTime(); + int unreadMsgCount = chattingDao.countByChatRoomIdAndCreatedAtBetween( + cr.getId(), + lastReadTime, + lastUpdateTime + ); + + return ChatRoomResponse.of(cr, lastContent, String.valueOf(lastUpdateTime), unreadMsgCount); }) .toList() ); @@ -64,8 +91,7 @@ public CreateChatRoomResponse createChatRoom(Long userId, Long spaceId, CreateCh ChatRoom chatRoom = chatRoomDao.save(ChatRoom.of(spaceBySpaceId, createChatRoomRequest, chatRoomImgUrl)); // TODO 4: user_chatRoom 매핑 정보 저장 - // TODO: 메시지 관련 처리 예정 - UserChatRoom userChatRoom = userChatRoomDao.save(UserChatRoom.of(chatRoom, userByUserId, null)); + UserChatRoom userChatRoom = userChatRoomDao.save(UserChatRoom.of(chatRoom, userByUserId, LocalDateTime.now())); // TODO 5: chatroom id 반환 return CreateChatRoomResponse.of(chatRoom.getId()); diff --git a/src/main/java/space/space_spring/service/ChattingService.java b/src/main/java/space/space_spring/service/ChattingService.java new file mode 100644 index 00000000..60c52c12 --- /dev/null +++ b/src/main/java/space/space_spring/service/ChattingService.java @@ -0,0 +1,60 @@ +package space.space_spring.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import space.space_spring.dao.chat.ChattingDao; +import space.space_spring.dto.chat.request.ChatMessageRequest; +import space.space_spring.dto.chat.response.ChatMessageLogResponse; +import space.space_spring.dto.chat.response.ChatMessageResponse; +import space.space_spring.entity.UserSpace; +import space.space_spring.entity.document.ChatMessage; +import space.space_spring.util.userSpace.UserSpaceUtils; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + + +@Service +@RequiredArgsConstructor +public class ChattingService { + + private final UserSpaceUtils userSpaceUtils; + private final ChattingDao chattingDao; + + @Transactional + public ChatMessageResponse sendChatMessage(Long senderId, ChatMessageRequest chatMessageRequest, Long chatRoomId) { + + // TODO 1: 메시지 메타데이터 저장 위해 전송자 찾기 + Optional senderInSpace = userSpaceUtils.isUserInSpace(senderId, chatMessageRequest.getSpaceId()); + + // TODO 2: validation 후 전송자 이름 및 프로필 사진 get + String senderName = ""; + String senderProfileImg = ""; + if (senderInSpace.isPresent()) { + senderName = senderInSpace.get().getUserName(); + senderProfileImg = senderInSpace.get().getUserProfileImg(); + } + + // TODO 3: DB에 메시지 저장 + ChatMessage message = chattingDao.insert(ChatMessage.of( + chatMessageRequest, + chatRoomId, + senderId, + senderName, + senderProfileImg + )); + + return ChatMessageResponse.of(message); + } + + public ChatMessageLogResponse readChatMessageLog(Long chatRoomId) { + List chatMessageList = chattingDao.findByChatRoomId(chatRoomId); + return ChatMessageLogResponse.of(chatMessageList.stream() + .map(ChatMessageResponse::of) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/space/space_spring/service/UserChatRoomService.java b/src/main/java/space/space_spring/service/UserChatRoomService.java new file mode 100644 index 00000000..d803e0be --- /dev/null +++ b/src/main/java/space/space_spring/service/UserChatRoomService.java @@ -0,0 +1,38 @@ +package space.space_spring.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import space.space_spring.dao.chat.ChatRoomDao; +import space.space_spring.dao.chat.UserChatRoomDao; +import space.space_spring.entity.ChatRoom; +import space.space_spring.entity.User; +import space.space_spring.entity.UserChatRoom; +import space.space_spring.util.user.UserUtils; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class UserChatRoomService { + + private final UserUtils userUtils; + + private final ChatRoomDao chatRoomDao; + private final UserChatRoomDao userChatRoomDao; + + @Transactional + public void saveLastReadTime(Long userId, Long chatRoomId) { + User userByUserId = userUtils.findUserByUserId(userId); + Optional chatRoomByChatRoomId = chatRoomDao.findById(chatRoomId); + + chatRoomByChatRoomId.ifPresent(chatRoom -> { + UserChatRoom targetChatRoom = userChatRoomDao.findByUserAndChatRoom(userByUserId, chatRoom); + targetChatRoom.setLastReadTime(LocalDateTime.now(ZoneId.of("Asia/Seoul"))); + }); + } +}