From 830e41dfa3576781f53baed80ea976d20d2624b8 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 8 Aug 2025 15:15:29 +0900 Subject: [PATCH] =?UTF-8?q?feat(chat):=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EC=84=B8=EC=85=98=20=EC=A1=B0=ED=9A=8C=20API=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 --- .../room/controller/ChatRoomController.kt | 18 ++++++ .../sodalive/chat/room/dto/ChatRoomDto.kt | 30 ++++++++-- .../chat/room/service/ChatRoomService.kt | 59 ++++++++++++++++++- 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/room/controller/ChatRoomController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/room/controller/ChatRoomController.kt index 20c6892..d36aa54 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/room/controller/ChatRoomController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/room/controller/ChatRoomController.kt @@ -7,6 +7,7 @@ import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.member.Member import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping @@ -60,4 +61,21 @@ class ChatRoomController( ApiResponse.ok(response) } } + + /** + * 세션 상태 조회 API + * - 채팅방 참여 여부 검증 + * - 외부 API로 세션 상태 조회 후 active면 true, 아니면 false 반환 + */ + @GetMapping("/{chatRoomId}/session") + fun getChatSessionStatus( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, + @PathVariable chatRoomId: Long + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.") + + val isActive = chatRoomService.isMyRoomSessionActive(member, chatRoomId) + ApiResponse.ok(isActive) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/room/dto/ChatRoomDto.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/room/dto/ChatRoomDto.kt index 030c52a..5768d7e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/room/dto/ChatRoomDto.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/room/dto/ChatRoomDto.kt @@ -34,18 +34,20 @@ data class ChatRoomListQueryDto( ) /** - * 외부 API 채팅 세션 응답 DTO + * 외부 API 채팅 세션 생성 응답 DTO */ -data class ExternalChatSessionResponse( +data class ExternalChatSessionCreateResponse( val success: Boolean, val message: String?, - val data: ExternalChatSessionData? + val data: ExternalChatSessionCreateData? ) /** - * 외부 API 채팅 세션 데이터 DTO + * 외부 API 채팅 세션 생성 데이터 DTO + * 공통: sessionId, status + * 생성 전용: userId, characterId, character, createdAt */ -data class ExternalChatSessionData( +data class ExternalChatSessionCreateData( val sessionId: String, val userId: String, val characterId: String, @@ -54,6 +56,24 @@ data class ExternalChatSessionData( val createdAt: String ) +/** + * 외부 API 채팅 세션 조회 응답 DTO + */ +data class ExternalChatSessionGetResponse( + val success: Boolean, + val message: String?, + val data: ExternalChatSessionGetData? +) + +/** + * 외부 API 채팅 세션 조회 데이터 DTO + * 세션 조회에서 사용하는 공통 필드만 포함 + */ +data class ExternalChatSessionGetData( + val sessionId: String, + val status: String +) + /** * 외부 API 캐릭터 데이터 DTO */ diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/room/service/ChatRoomService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/room/service/ChatRoomService.kt index 7983ebb..7aebc3b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/room/service/ChatRoomService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/room/service/ChatRoomService.kt @@ -8,7 +8,8 @@ import kr.co.vividnext.sodalive.chat.room.ParticipantType import kr.co.vividnext.sodalive.chat.room.dto.ChatRoomListItemDto import kr.co.vividnext.sodalive.chat.room.dto.ChatRoomListQueryDto import kr.co.vividnext.sodalive.chat.room.dto.CreateChatRoomResponse -import kr.co.vividnext.sodalive.chat.room.dto.ExternalChatSessionResponse +import kr.co.vividnext.sodalive.chat.room.dto.ExternalChatSessionCreateResponse +import kr.co.vividnext.sodalive.chat.room.dto.ExternalChatSessionGetResponse import kr.co.vividnext.sodalive.chat.room.repository.CharacterChatMessageRepository import kr.co.vividnext.sodalive.chat.room.repository.CharacterChatParticipantRepository import kr.co.vividnext.sodalive.chat.room.repository.CharacterChatRoomRepository @@ -150,7 +151,10 @@ class ChatRoomService( // 응답 파싱 val objectMapper = ObjectMapper() - val apiResponse = objectMapper.readValue(response.body, ExternalChatSessionResponse::class.java) + val apiResponse = objectMapper.readValue( + response.body, + ExternalChatSessionCreateResponse::class.java + ) // success가 false이면 throw if (!apiResponse.success) { @@ -195,4 +199,55 @@ class ChatRoomService( ) } } + + @Transactional(readOnly = true) + fun isMyRoomSessionActive(member: Member, chatRoomId: Long): Boolean { + val room = chatRoomRepository.findById(chatRoomId).orElseThrow { + SodaException("채팅방을 찾을 수 없습니다.") + } + val participant = participantRepository.findByChatRoomAndMemberAndIsActiveTrue(room, member) + if (participant == null) { + throw SodaException("잘못된 접근입니다") + } + return fetchSessionActive(room.sessionId) + } + + private fun fetchSessionActive(sessionId: String): Boolean { + try { + val factory = SimpleClientHttpRequestFactory() + factory.setConnectTimeout(20000) // 20초 + factory.setReadTimeout(20000) // 20초 + + val restTemplate = RestTemplate(factory) + + val headers = HttpHeaders() + headers.set("x-api-key", apiKey) + + val httpEntity = HttpEntity(null, headers) + + val response = restTemplate.exchange( + "$apiUrl/api/session/$sessionId", + HttpMethod.GET, + httpEntity, + String::class.java + ) + + val objectMapper = ObjectMapper() + val apiResponse = objectMapper.readValue( + response.body, + ExternalChatSessionGetResponse::class.java + ) + + // success가 false이면 throw + if (!apiResponse.success) { + throw SodaException("오류가 발생했습니다. 다시 시도해 주세요.") + } + + val status = apiResponse.data?.status + return status == "active" + } catch (e: Exception) { + e.printStackTrace() + throw SodaException("오류가 발생했습니다. 다시 시도해 주세요.") + } + } }