From 258943535cb263cc6e87716f2f3e94d0a59d1252 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 27 Aug 2025 15:18:24 +0900 Subject: [PATCH] =?UTF-8?q?feat(chat-room):=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=20=EC=9E=85=EC=9E=A5=20=EC=8B=9C=20=EC=84=A0=ED=83=9D=EC=A0=81?= =?UTF-8?q?=20=EC=BA=90=EB=A6=AD=ED=84=B0=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=84=9C=EB=AA=85=20URL=20=EB=B0=98=ED=99=98=20=EB=B0=8F=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit enter API에 characterImageId 선택 파라미터 추가 동일 캐릭터/활성 여부/보유 여부 검증 후 5분 만료의 CloudFront 서명 URL 생성 ChatRoomEnterResponse에 bgImageUrl 필드 추가해 응답 포함 서명 URL 생성 실패 시 warn 로그만 남기고 null 반환하여 사용자 흐름 유지 기존 호출은 그대로 동작하며, 파라미터와 응답 필드 추가는 하위 호환됨 --- .../room/controller/ChatRoomController.kt | 5 +-- .../sodalive/chat/room/dto/ChatRoomDto.kt | 3 +- .../chat/room/service/ChatRoomService.kt | 36 +++++++++++++++++-- 3 files changed, 39 insertions(+), 5 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 f5bd481..aa0f1f1 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 @@ -91,12 +91,13 @@ class ChatRoomController( @GetMapping("/{chatRoomId}/enter") fun enterChatRoom( @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, - @PathVariable chatRoomId: Long + @PathVariable chatRoomId: Long, + @RequestParam(required = false) characterImageId: Long? ) = run { if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.") - val response = chatRoomService.enterChatRoom(member, chatRoomId) + val response = chatRoomService.enterChatRoom(member, chatRoomId, characterImageId) ApiResponse.ok(response) } 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 b43d8a7..b09f230 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 @@ -182,7 +182,8 @@ data class ChatRoomEnterResponse( val messages: List, val hasMoreMessages: Boolean, val totalRemaining: Int, - val nextRechargeAtEpoch: Long? + val nextRechargeAtEpoch: Long?, + val bgImageUrl: String? = null ) /** 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 34d852e..50bd17b 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 @@ -298,7 +298,7 @@ class ChatRoomService( } @Transactional - fun enterChatRoom(member: Member, chatRoomId: Long): ChatRoomEnterResponse { + fun enterChatRoom(member: Member, chatRoomId: Long, characterImageId: Long? = null): ChatRoomEnterResponse { val room = chatRoomRepository.findById(chatRoomId).orElseThrow { SodaException("채팅방을 찾을 수 없습니다.") } @@ -340,13 +340,45 @@ class ChatRoomService( // 입장 시 Lazy refill 적용 후 상태 반환 val quotaStatus = chatQuotaService.applyRefillOnEnterAndGetStatus(member.id!!) + // 선택적 캐릭터 이미지 서명 URL 생성 처리 + val signedUrl: String? = try { + if (characterImageId != null) { + val img = characterImageService.getById(characterImageId) + // 동일 캐릭터 소속 및 활성 검증 + if (img.chatCharacter.id == character.id && img.isActive) { + val owned = + (img.imagePriceCan == 0L) || characterImageService.isOwnedImageByMember(img.id!!, member.id!!) + if (owned) { + val expiration = 5L * 60L * 1000L // 5분 + imageCloudFront.generateSignedURL(img.imagePath, expiration) + } else { + null + } + } else { + null + } + } else { + null + } + } catch (e: Exception) { + // 문제가 있어도 입장 자체는 가능해야 하므로 로그만 남기고 null 반환 + log.warn( + "[chat] enter: signed url generation failed. roomId={}, imageId={}, reason={}", + room.id, + characterImageId, + e.message + ) + null + } + return ChatRoomEnterResponse( roomId = room.id!!, character = characterDto, messages = items, hasMoreMessages = hasMore, totalRemaining = quotaStatus.totalRemaining, - nextRechargeAtEpoch = quotaStatus.nextRechargeAtEpochMillis + nextRechargeAtEpoch = quotaStatus.nextRechargeAtEpochMillis, + bgImageUrl = signedUrl ) }