diff --git a/idle-application/src/main/kotlin/com/swm/idle/application/chat/domain/ChatRoomService.kt b/idle-application/src/main/kotlin/com/swm/idle/application/chat/domain/ChatRoomService.kt index 7b7587d4..1cae784e 100644 --- a/idle-application/src/main/kotlin/com/swm/idle/application/chat/domain/ChatRoomService.kt +++ b/idle-application/src/main/kotlin/com/swm/idle/application/chat/domain/ChatRoomService.kt @@ -37,22 +37,4 @@ class ChatRoomService (val chatroomRepository: ChatRoomRepository){ count = projection.getUnreadCount(), opponentId = projection.getOpponentId() ) - - fun getByCenterWithCarer(centerId: UUID, carerId: UUID, isCarer: Boolean): ChatRoomSummaryInfo { - val projections: ChatRoomSummaryInfoProjection - - if(isCarer) { - projections = chatroomRepository.carerFindSingleChatRoom( - centerId = centerId, - carerId = carerId - ) - }else { - projections = chatroomRepository.centerFindSingleChatRoom( - centerId = centerId, - carerId = carerId - ) - } - - return mappingChatRoomSummaryInfo(projections) - } } \ No newline at end of file diff --git a/idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt b/idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt index 616cd8c9..30e524a8 100644 --- a/idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt +++ b/idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt @@ -6,7 +6,7 @@ import com.swm.idle.application.common.security.getUserAuthentication import com.swm.idle.application.notification.domain.DeviceTokenService import com.swm.idle.application.user.carer.domain.CarerService import com.swm.idle.application.user.center.service.domain.CenterService -import com.swm.idle.domain.chat.event.ChatRedisPublisher +import com.swm.idle.domain.chat.event.ChatRedisTemplate import com.swm.idle.domain.chat.vo.ChatRoomSummaryInfo import com.swm.idle.domain.chat.vo.ReadMessage import com.swm.idle.infrastructure.fcm.chat.ChatNotificationService @@ -19,7 +19,7 @@ import java.util.* @Service @Transactional(readOnly = true) class ChatFacadeService( - private val redisPublisher: ChatRedisPublisher, + private val chatRedisTemplate: ChatRedisTemplate, private val messageService: ChatMessageService, private val notificationService: ChatNotificationService, private val deviceTokenService: DeviceTokenService, @@ -32,8 +32,9 @@ class ChatFacadeService( @Transactional fun sendMessage(request: SendChatMessageRequest, userId: UUID) { val message = messageService.save(request, userId) - redisPublisher.publish(message) + chatRedisTemplate.publish(message) + if (chatRedisTemplate.isChatting(message.receiverId)) return val token = deviceTokenService.findByUserId(message.receiverId) notificationService.send(message, request.senderName, token) } @@ -47,7 +48,7 @@ class ChatFacadeService( receiverId = request.opponentId, readUserId = userId ) - redisPublisher.publish(readMessage) + chatRedisTemplate.publish(readMessage) } @Transactional @@ -89,32 +90,4 @@ class ChatFacadeService( } } } - - fun getSingleChatRoomInfo(chatRoomId: UUID, opponentId: UUID,isCarer: Boolean): ChatRoomSummaryInfo { - val (carerId, centerId) = if (isCarer) { - getUserAuthentication().userId to opponentId - } else { - opponentId to getUserAuthentication().userId - } - - val chatRoomSummaryInfo = chatroomService.getByCenterWithCarer( - centerId = centerId, - carerId =carerId, - isCarer) - - return if (isCarer) { - val center = centerService.getById(centerId) - chatRoomSummaryInfo.also { - it.opponentName = center.centerName - it.opponentProfileImageUrl = center.profileImageUrl - } - - }else { - val carer = carerService.getById(carerId) - chatRoomSummaryInfo.also { - it.opponentName = carer.name - it.opponentProfileImageUrl = carer.profileImageUrl - } - } - } } \ No newline at end of file diff --git a/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/event/ChatRedisSubscriber.kt b/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/event/ChatRedisSubscriber.kt index 29807840..76286f35 100644 --- a/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/event/ChatRedisSubscriber.kt +++ b/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/event/ChatRedisSubscriber.kt @@ -21,17 +21,17 @@ class ChatRedisSubscriber( val actualJson = objectMapper.readTree(rawJson).asText() val jsonNode = objectMapper.readTree(actualJson) - when (jsonNode.get(ChatRedisPublisher.TYPE).asText()) { - ChatRedisPublisher.SEND_MESSAGE -> { + when (jsonNode.get(ChatRedisTemplate.TYPE).asText()) { + ChatRedisTemplate.SEND_MESSAGE -> { val chatMessage: ChatMessage = objectMapper.treeToValue( - jsonNode.get(ChatRedisPublisher.DATA), ChatMessage::class.java + jsonNode.get(ChatRedisTemplate.DATA), ChatMessage::class.java ) eventPublisher.publishEvent(chatMessage) } - ChatRedisPublisher.READ_MESSAGE -> { + ChatRedisTemplate.READ_MESSAGE -> { val readMessage: ReadMessage = objectMapper.treeToValue( - jsonNode.get(ChatRedisPublisher.DATA), ReadMessage::class.java + jsonNode.get(ChatRedisTemplate.DATA), ReadMessage::class.java ) eventPublisher.publishEvent(readMessage) diff --git a/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/event/ChatRedisPublisher.kt b/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/event/ChatRedisTemplate.kt similarity index 74% rename from idle-domain/src/main/kotlin/com/swm/idle/domain/chat/event/ChatRedisPublisher.kt rename to idle-domain/src/main/kotlin/com/swm/idle/domain/chat/event/ChatRedisTemplate.kt index e4e4dabf..afdf2c72 100644 --- a/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/event/ChatRedisPublisher.kt +++ b/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/event/ChatRedisTemplate.kt @@ -6,12 +6,25 @@ import com.swm.idle.domain.chat.entity.jpa.ChatMessage import com.swm.idle.domain.chat.vo.ReadMessage import org.springframework.data.redis.core.RedisTemplate import org.springframework.stereotype.Component +import java.time.Duration +import java.util.* @Component -class ChatRedisPublisher( +class ChatRedisTemplate( private val redisTemplate: RedisTemplate, private val objectMapper: ObjectMapper ) { + fun isChatting(userId: UUID): Boolean { + return redisTemplate.hasKey(userId.toString()) + } + + fun delete(userId: String) { + redisTemplate.delete(userId) + } + + fun setSession(userId: String, duration: Duration) { + redisTemplate.opsForValue().set(userId,"active",duration) + } fun publish(chatMessage: ChatMessage) { val message = objectMapper.writeValueAsString( diff --git a/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/repository/ChatMessageRepository.kt b/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/repository/ChatMessageRepository.kt index 880b22ca..bdc7b2ec 100644 --- a/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/repository/ChatMessageRepository.kt +++ b/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/repository/ChatMessageRepository.kt @@ -2,6 +2,7 @@ package com.swm.idle.domain.chat.repository import com.swm.idle.domain.chat.entity.jpa.ChatMessage import io.lettuce.core.dynamic.annotation.Param +import jakarta.transaction.Transactional import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query @@ -10,7 +11,8 @@ import java.util.* @Repository interface ChatMessageRepository : JpaRepository { - @Modifying + @Transactional + @Modifying(clearAutomatically = true) @Query(""" UPDATE ChatMessage cm SET cm.isRead = true diff --git a/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/repository/ChatRoomRepository.kt b/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/repository/ChatRoomRepository.kt index 05652ea2..7141d716 100644 --- a/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/repository/ChatRoomRepository.kt +++ b/idle-domain/src/main/kotlin/com/swm/idle/domain/chat/repository/ChatRoomRepository.kt @@ -13,7 +13,8 @@ interface ChatRoomRepository : JpaRepository { fun findByCarerIdAndCenterId(carerId: UUID, centerId: UUID): ChatRoom? - @Query(""" + @Query( + """ WITH FilteredChatRooms AS ( SELECT cr.id AS chat_room_id, @@ -29,6 +30,7 @@ interface ChatRoomRepository : JpaRepository { FROM chat_message cm WHERE cm.chat_room_id IN (SELECT chat_room_id FROM FilteredChatRooms) AND cm.is_read = false + AND cm.receiverId = :userId GROUP BY cm.chat_room_id ) @@ -47,10 +49,12 @@ interface ChatRoomRepository : JpaRepository { ORDER BY id DESC LIMIT 1 ) cm; -""", nativeQuery = true) +""", nativeQuery = true + ) fun carerFindChatRooms(@Param("userId") userId: UUID): List - @Query(""" + @Query( + """ WITH FilteredChatRooms AS ( SELECT cr.id AS chat_room_id, @@ -66,6 +70,7 @@ interface ChatRoomRepository : JpaRepository { FROM chat_message cm WHERE cm.chat_room_id IN (SELECT chat_room_id FROM FilteredChatRooms) AND cm.is_read = false + AND cm.receiverId = :userId GROUP BY cm.chat_room_id ) @@ -84,85 +89,7 @@ interface ChatRoomRepository : JpaRepository { ORDER BY id DESC LIMIT 1 ) cm; -""", nativeQuery = true) - fun centerFindChatRooms(@Param("userId") userId: UUID): List - - - @Query(""" - WITH FilteredChatRooms AS ( - SELECT - cr.id AS chat_room_id, - cr.center_id - FROM chat_room cr - WHERE cr.carer_id = :carerId - AND cr.center_id =:centerId - ), - - UnreadMessageCounts AS ( - SELECT - cm.chat_room_id, - COUNT(*) AS unread_count - FROM chat_message cm - WHERE cm.chat_room_id IN (SELECT chat_room_id FROM FilteredChatRooms) - AND cm.is_read = false - GROUP BY cm.chat_room_id +""", nativeQuery = true ) - - SELECT - fcr.chat_room_id AS chatRoomId, - fcr.center_id AS opponentId, - umc.unread_count AS unreadCount, - cm.content AS lastMessage, - cm.created_at AS lastMessageTime - FROM FilteredChatRooms fcr - JOIN UnreadMessageCounts umc ON fcr.chat_room_id = umc.chat_room_id - JOIN LATERAL ( - SELECT content, created_at - FROM chat_message - WHERE chat_room_id = fcr.chat_room_id AND is_read = false - ORDER BY id DESC - LIMIT 1 - ) cm; -""", nativeQuery = true) - fun carerFindSingleChatRoom(@Param("centerId") centerId: UUID, - @Param("carerId") carerId: UUID): ChatRoomSummaryInfoProjection - - @Query(""" - WITH FilteredChatRooms AS ( - SELECT - cr.id AS chat_room_id, - cr.carer_id - FROM chat_room cr - WHERE cr.carer_id = :carerId - AND cr.center_id =:centerId - ), - - UnreadMessageCounts AS ( - SELECT - cm.chat_room_id, - COUNT(*) AS unread_count - FROM chat_message cm - WHERE cm.chat_room_id IN (SELECT chat_room_id FROM FilteredChatRooms) - AND cm.is_read = false - GROUP BY cm.chat_room_id - ) - - SELECT - fcr.chat_room_id AS chatRoomId, - fcr.carer_id AS opponentId, - umc.unread_count AS unreadCount, - cm.content AS lastMessage, - cm.created_at AS lastMessageTime - FROM FilteredChatRooms fcr - JOIN UnreadMessageCounts umc ON fcr.chat_room_id = umc.chat_room_id - JOIN LATERAL ( - SELECT content, created_at - FROM chat_message - WHERE chat_room_id = fcr.chat_room_id AND is_read = false - ORDER BY id DESC - LIMIT 1 - ) cm; -""", nativeQuery = true) - fun centerFindSingleChatRoom(@Param("centerId") centerId: UUID, - @Param("carerId") carerId: UUID): ChatRoomSummaryInfoProjection -} + fun centerFindChatRooms(@Param("userId") userId: UUID): List +} \ No newline at end of file diff --git a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/api/ChatCarerApi.kt b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/api/ChatCarerApi.kt index 31138ee1..97f517fc 100644 --- a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/api/ChatCarerApi.kt +++ b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/api/ChatCarerApi.kt @@ -33,11 +33,4 @@ interface ChatCarerApi { @GetMapping("/chatrooms") @ResponseStatus(HttpStatus.OK) fun carerChatroomSummary(): List - - @Secured - @Operation(summary = "보호사의 단일 채팅방 정보 조회 API") - @GetMapping("/chatrooms/{chatroom-id}/opponent/{opponent-id}") - @ResponseStatus(HttpStatus.OK) - fun carerSingleChatroomSummary(@PathVariable(value = "chatroom-id") chatroomId: UUID, - @PathVariable(value = "opponent-id") opponentId: UUID,): ChatRoomSummaryInfo } diff --git a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/api/ChatCenterApi.kt b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/api/ChatCenterApi.kt index 3a4126c7..bf7dea12 100644 --- a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/api/ChatCenterApi.kt +++ b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/api/ChatCenterApi.kt @@ -33,11 +33,4 @@ interface ChatCenterApi { @GetMapping("/chatrooms") @ResponseStatus(HttpStatus.OK) fun centerChatroomSummary(): List - - @Secured - @Operation(summary = "센터장의 단일 채팅방 정보 조회 API") - @GetMapping("/chatrooms/{chatroom-id}/opponent/{opponent-id}") - @ResponseStatus(HttpStatus.OK) - fun carerSingleChatroomSummary(@PathVariable(value = "chatroom-id") chatroomId: UUID, - @PathVariable(value = "opponent-id") opponentId: UUID): ChatRoomSummaryInfo } diff --git a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/config/ChatHandshakeInterceptor.kt b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/config/ChatHandshakeInterceptor.kt index 9069515f..a0f7c4bd 100644 --- a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/config/ChatHandshakeInterceptor.kt +++ b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/config/ChatHandshakeInterceptor.kt @@ -1,6 +1,7 @@ package com.swm.idle.presentation.chat.config import com.swm.idle.application.common.properties.JwtTokenProperties +import com.swm.idle.domain.chat.event.ChatRedisTemplate import com.swm.idle.support.security.util.JwtTokenProvider import org.springframework.http.server.ServerHttpRequest import org.springframework.http.server.ServerHttpResponse @@ -8,10 +9,12 @@ import org.springframework.http.server.ServletServerHttpRequest import org.springframework.stereotype.Component import org.springframework.web.socket.WebSocketHandler import org.springframework.web.socket.server.HandshakeInterceptor +import java.time.Duration @Component class ChatHandshakeInterceptor( private val jwtTokenProperties: JwtTokenProperties, + private val redisTemplate : ChatRedisTemplate, ): HandshakeInterceptor { override fun beforeHandshake( @@ -29,7 +32,11 @@ class ChatHandshakeInterceptor( return false } - attributes["userId"] = claims.customClaims["userId"] as String + val userId = claims.customClaims["userId"] as String + attributes["userId"] = userId + + redisTemplate.setSession(userId, Duration.ofHours(24)) + return true } return false diff --git a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/config/WebSocketDisconnectListener.kt b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/config/WebSocketDisconnectListener.kt new file mode 100644 index 00000000..fad27f95 --- /dev/null +++ b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/config/WebSocketDisconnectListener.kt @@ -0,0 +1,23 @@ +package com.swm.idle.presentation.chat.config + +import com.swm.idle.domain.chat.event.ChatRedisTemplate +import org.springframework.context.event.EventListener +import org.springframework.messaging.simp.stomp.StompHeaderAccessor +import org.springframework.stereotype.Component +import org.springframework.web.socket.messaging.SessionDisconnectEvent + +@Component +class WebSocketDisconnectListener( + private val redisTemplate: ChatRedisTemplate +) { + + @EventListener + fun handleDisconnect(event: SessionDisconnectEvent) { + val headers = StompHeaderAccessor.wrap(event.message) + val userId = headers.sessionAttributes?.get("userId") as? String + + if (userId != null) { + redisTemplate.delete(userId) + } + } +} diff --git a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatCarerController.kt b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatCarerController.kt index 43825d53..6c58b93a 100644 --- a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatCarerController.kt +++ b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatCarerController.kt @@ -25,8 +25,4 @@ class ChatCarerController( override fun recentMessages(chatroomId: UUID, messageId: UUID?): List { return chatMessageService.getRecentMessages(chatroomId, messageId) } - - override fun carerSingleChatroomSummary(chatroomId: UUID, opponentId: UUID): ChatRoomSummaryInfo { - return chatMessageService.getSingleChatRoomInfo(chatroomId,opponentId, true) - } } \ No newline at end of file diff --git a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatCenterController.kt b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatCenterController.kt index 9fc81dbe..9abc605c 100644 --- a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatCenterController.kt +++ b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatCenterController.kt @@ -25,8 +25,4 @@ class ChatCenterController( override fun recentMessages(chatroomId: UUID, messageId: UUID?): List { return chatMessageService.getRecentMessages(chatroomId, messageId) } - - override fun carerSingleChatroomSummary(chatroomId: UUID, opponentId: UUID): ChatRoomSummaryInfo { - return chatMessageService.getSingleChatRoomInfo(chatroomId, opponentId, false) - } } \ No newline at end of file