채팅 메시지 다국어 분리
This commit is contained in:
@@ -42,8 +42,8 @@ class ChatRoomController(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
@RequestBody request: CreateChatRoomRequest
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.")
|
||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
if (member.auth == null) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
|
||||
val response = chatRoomService.createOrGetChatRoom(member, request.characterId)
|
||||
ApiResponse.ok(response)
|
||||
@@ -77,8 +77,8 @@ class ChatRoomController(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
@PathVariable chatRoomId: Long
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.")
|
||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
if (member.auth == null) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
|
||||
val isActive = chatRoomService.isMyRoomSessionActive(member, chatRoomId)
|
||||
ApiResponse.ok(isActive)
|
||||
@@ -95,8 +95,8 @@ class ChatRoomController(
|
||||
@PathVariable chatRoomId: Long,
|
||||
@RequestParam(required = false) characterImageId: Long?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.")
|
||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
if (member.auth == null) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
|
||||
val response = chatRoomService.enterChatRoom(member, chatRoomId, characterImageId)
|
||||
ApiResponse.ok(response)
|
||||
@@ -114,8 +114,8 @@ class ChatRoomController(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
@PathVariable chatRoomId: Long
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.")
|
||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
if (member.auth == null) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
|
||||
chatRoomService.leaveChatRoom(member, chatRoomId)
|
||||
ApiResponse.ok(true)
|
||||
@@ -134,8 +134,8 @@ class ChatRoomController(
|
||||
@RequestParam(defaultValue = "20") limit: Int,
|
||||
@RequestParam(required = false) cursor: Long?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.")
|
||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
if (member.auth == null) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
|
||||
val response = chatRoomService.getChatMessages(member, chatRoomId, cursor, limit)
|
||||
ApiResponse.ok(response)
|
||||
@@ -153,8 +153,8 @@ class ChatRoomController(
|
||||
@PathVariable chatRoomId: Long,
|
||||
@RequestBody request: SendChatMessageRequest
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.")
|
||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
if (member.auth == null) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
|
||||
if (request.message.isBlank()) {
|
||||
ApiResponse.error()
|
||||
@@ -176,8 +176,8 @@ class ChatRoomController(
|
||||
@PathVariable messageId: Long,
|
||||
@RequestBody request: ChatMessagePurchaseRequest
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.")
|
||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
if (member.auth == null) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
|
||||
val result = chatRoomService.purchaseMessage(member, chatRoomId, messageId, request.container)
|
||||
ApiResponse.ok(result)
|
||||
@@ -195,8 +195,8 @@ class ChatRoomController(
|
||||
@PathVariable chatRoomId: Long,
|
||||
@RequestBody request: ChatRoomResetRequest
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.")
|
||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
if (member.auth == null) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
|
||||
val response = chatRoomService.resetChatRoom(member, chatRoomId, request.container)
|
||||
ApiResponse.ok(response)
|
||||
|
||||
@@ -29,6 +29,7 @@ import kr.co.vividnext.sodalive.chat.room.repository.ChatRoomRepository
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
@@ -54,6 +55,7 @@ class ChatRoomService(
|
||||
private val characterService: ChatCharacterService,
|
||||
private val characterImageService: CharacterImageService,
|
||||
private val langContext: LangContext,
|
||||
private val messageSource: SodaMessageSource,
|
||||
private val aiCharacterTranslationRepository: AiCharacterTranslationRepository,
|
||||
private val canPaymentService: kr.co.vividnext.sodalive.can.payment.CanPaymentService,
|
||||
private val imageCloudFront: kr.co.vividnext.sodalive.aws.cloudfront.ImageContentCloudFront,
|
||||
@@ -77,19 +79,19 @@ class ChatRoomService(
|
||||
@Transactional
|
||||
fun purchaseMessage(member: Member, chatRoomId: Long, messageId: Long, container: String): ChatMessageItemDto {
|
||||
val room = chatRoomRepository.findByIdAndIsActiveTrue(chatRoomId)
|
||||
?: throw SodaException("채팅방을 찾을 수 없습니다.")
|
||||
?: throw SodaException(messageKey = "chat.error.room_not_found")
|
||||
// 참여 여부 검증
|
||||
participantRepository.findByChatRoomAndMemberAndIsActiveTrue(room, member)
|
||||
?: throw SodaException("잘못된 접근입니다")
|
||||
?: throw SodaException(messageKey = "chat.room.invalid_access")
|
||||
|
||||
val message = messageRepository.findById(messageId).orElseThrow {
|
||||
SodaException("메시지를 찾을 수 없습니다.")
|
||||
SodaException(messageKey = "chat.message.not_found")
|
||||
}
|
||||
if (!message.isActive) throw SodaException("비활성화된 메시지입니다.")
|
||||
if (message.chatRoom.id != room.id) throw SodaException("잘못된 접근입니다")
|
||||
if (!message.isActive) throw SodaException(messageKey = "chat.message.inactive")
|
||||
if (message.chatRoom.id != room.id) throw SodaException(messageKey = "chat.room.invalid_access")
|
||||
|
||||
val price = message.price ?: throw SodaException("구매할 수 없는 메시지입니다.")
|
||||
if (price <= 0) throw SodaException("구매 가격이 잘못되었습니다.")
|
||||
val price = message.price ?: throw SodaException(messageKey = "chat.message.not_purchasable")
|
||||
if (price <= 0) throw SodaException(messageKey = "chat.purchase.invalid_price")
|
||||
|
||||
// 이미지 메시지인 경우: 이미 소유했다면 결제 생략하고 DTO 반환
|
||||
if (message.messageType == ChatMessageType.IMAGE) {
|
||||
@@ -124,7 +126,7 @@ class ChatRoomService(
|
||||
fun createOrGetChatRoom(member: Member, characterId: Long): CreateChatRoomResponse {
|
||||
// 1. 캐릭터 조회
|
||||
val character = characterService.findById(characterId)
|
||||
?: throw SodaException("해당 ID의 캐릭터를 찾을 수 없습니다: $characterId")
|
||||
?: throw SodaException(messageKey = "chat.room.character_not_found")
|
||||
|
||||
// 2. 이미 참여 중인 채팅방이 있는지 확인
|
||||
val existingChatRoom = chatRoomRepository.findActiveChatRoomByMemberAndCharacter(member, character)
|
||||
@@ -225,21 +227,21 @@ class ChatRoomService(
|
||||
|
||||
// success가 false이면 throw
|
||||
if (!apiResponse.success) {
|
||||
throw SodaException("채팅방 생성에 실패했습니다. 다시 시도해 주세요.")
|
||||
throw SodaException(messageKey = "chat.room.create_failed_retry")
|
||||
}
|
||||
|
||||
// success가 true이면 파라미터로 넘긴 값과 일치하는지 확인
|
||||
val data = apiResponse.data ?: throw SodaException("채팅방 생성에 실패했습니다. 다시 시도해 주세요.")
|
||||
val data = apiResponse.data ?: throw SodaException(messageKey = "chat.room.create_failed_retry")
|
||||
|
||||
if (data.userId != userId && data.character.id != characterUUID && data.status != "active") {
|
||||
throw SodaException("채팅방 생성에 실패했습니다. 다시 시도해 주세요.")
|
||||
throw SodaException(messageKey = "chat.room.create_failed_retry")
|
||||
}
|
||||
|
||||
// 세션 ID 반환
|
||||
return data.sessionId
|
||||
} catch (e: Exception) {
|
||||
log.error(e.message)
|
||||
throw SodaException("채팅방 생성에 실패했습니다. 다시 시도해 주세요.")
|
||||
throw SodaException(messageKey = "chat.room.create_failed_retry")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +266,7 @@ class ChatRoomService(
|
||||
}
|
||||
} else {
|
||||
if (latest?.message.isNullOrBlank() && latest?.characterImage != null) {
|
||||
"[이미지]"
|
||||
messageSource.getMessage("chat.room.last_message_image", langContext.lang).orEmpty()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
@@ -304,11 +306,19 @@ class ChatRoomService(
|
||||
val now = LocalDateTime.now()
|
||||
val duration = Duration.between(time, now)
|
||||
val seconds = duration.seconds
|
||||
if (seconds <= 60) return "방금"
|
||||
if (seconds <= 60) {
|
||||
return messageSource.getMessage("chat.room.time.just_now", langContext.lang).orEmpty()
|
||||
}
|
||||
val minutes = duration.toMinutes()
|
||||
if (minutes < 60) return "${minutes}분 전"
|
||||
if (minutes < 60) {
|
||||
val template = messageSource.getMessage("chat.room.time.minutes_ago", langContext.lang).orEmpty()
|
||||
return String.format(template, minutes)
|
||||
}
|
||||
val hours = duration.toHours()
|
||||
if (hours < 24) return "${hours}시간 전"
|
||||
if (hours < 24) {
|
||||
val template = messageSource.getMessage("chat.room.time.hours_ago", langContext.lang).orEmpty()
|
||||
return String.format(template, hours)
|
||||
}
|
||||
// 그 외: 날짜 (yyyy-MM-dd)
|
||||
return time.toLocalDate().toString()
|
||||
}
|
||||
@@ -510,23 +520,23 @@ class ChatRoomService(
|
||||
|
||||
// success가 false이면 throw
|
||||
if (!apiResponse.success) {
|
||||
throw SodaException("오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
throw SodaException(messageKey = "chat.error.retry")
|
||||
}
|
||||
|
||||
val status = apiResponse.data?.status
|
||||
return status == "active"
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw SodaException("오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
throw SodaException(messageKey = "chat.error.retry")
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun leaveChatRoom(member: Member, chatRoomId: Long, throwOnSessionEndFailure: Boolean = false) {
|
||||
val room = chatRoomRepository.findByIdAndIsActiveTrue(chatRoomId)
|
||||
?: throw SodaException("채팅방을 찾을 수 없습니다.")
|
||||
?: throw SodaException(messageKey = "chat.error.room_not_found")
|
||||
val participant = participantRepository.findByChatRoomAndMemberAndIsActiveTrue(room, member)
|
||||
?: throw SodaException("잘못된 접근입니다")
|
||||
?: throw SodaException(messageKey = "chat.room.invalid_access")
|
||||
|
||||
// 1) 나가기 처리
|
||||
participant.isActive = false
|
||||
@@ -589,10 +599,9 @@ class ChatRoomService(
|
||||
}
|
||||
}
|
||||
// 최종 실패 처리
|
||||
val message = "채팅방 세션 종료에 실패했습니다. 다시 시도해 주세요."
|
||||
if (throwOnFailure) {
|
||||
log.error("[chat] 외부 세션 종료 최종 실패(예외 전파): sessionId={}, attempts={}", sessionId, maxAttempts)
|
||||
throw SodaException(message)
|
||||
throw SodaException(messageKey = "chat.room.session_end_failed")
|
||||
} else {
|
||||
log.error("[chat] 외부 세션 종료 최종 실패: sessionId={}, attempts={}", sessionId, maxAttempts)
|
||||
}
|
||||
@@ -601,9 +610,9 @@ class ChatRoomService(
|
||||
@Transactional(readOnly = true)
|
||||
fun getChatMessages(member: Member, chatRoomId: Long, cursor: Long?, limit: Int = 20): ChatMessagesPageResponse {
|
||||
val room = chatRoomRepository.findByIdAndIsActiveTrue(chatRoomId)
|
||||
?: throw SodaException("채팅방을 찾을 수 없습니다.")
|
||||
?: throw SodaException(messageKey = "chat.error.room_not_found")
|
||||
participantRepository.findByChatRoomAndMemberAndIsActiveTrue(room, member)
|
||||
?: throw SodaException("잘못된 접근입니다")
|
||||
?: throw SodaException(messageKey = "chat.room.invalid_access")
|
||||
|
||||
val pageable = PageRequest.of(0, limit)
|
||||
val fetched = if (cursor != null) {
|
||||
@@ -636,18 +645,18 @@ class ChatRoomService(
|
||||
fun sendMessage(member: Member, chatRoomId: Long, message: String): SendChatMessageResponse {
|
||||
// 1) 방 존재 확인
|
||||
val room = chatRoomRepository.findByIdAndIsActiveTrue(chatRoomId)
|
||||
?: throw SodaException("채팅방을 찾을 수 없습니다.")
|
||||
?: throw SodaException(messageKey = "chat.error.room_not_found")
|
||||
// 2) 참여 여부 확인 (USER)
|
||||
val myParticipant = participantRepository.findByChatRoomAndMemberAndIsActiveTrue(room, member)
|
||||
?: throw SodaException("잘못된 접근입니다")
|
||||
?: throw SodaException(messageKey = "chat.room.invalid_access")
|
||||
// 3) 캐릭터 참여자 조회
|
||||
val characterParticipant = participantRepository.findByChatRoomAndParticipantTypeAndIsActiveTrue(
|
||||
room,
|
||||
ParticipantType.CHARACTER
|
||||
) ?: throw SodaException("잘못된 접근입니다")
|
||||
) ?: throw SodaException(messageKey = "chat.room.invalid_access")
|
||||
|
||||
val character = characterParticipant.character
|
||||
?: throw SodaException("오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
?: throw SodaException(messageKey = "chat.error.retry")
|
||||
|
||||
// 4) 외부 API 호출 준비
|
||||
val userId = generateUserId(member.id!!)
|
||||
@@ -833,7 +842,7 @@ class ChatRoomService(
|
||||
}
|
||||
}
|
||||
log.error("[chat] 외부 채팅 전송 최종 실패 attempts={}", maxAttempts)
|
||||
throw SodaException("메시지 전송을 실패했습니다.")
|
||||
throw SodaException(messageKey = "chat.message.send_failed")
|
||||
}
|
||||
|
||||
private fun callExternalApiForChatSend(
|
||||
@@ -875,12 +884,12 @@ class ChatRoomService(
|
||||
)
|
||||
|
||||
if (!apiResponse.success) {
|
||||
throw SodaException("메시지 전송을 실패했습니다.")
|
||||
throw SodaException(messageKey = "chat.message.send_failed")
|
||||
}
|
||||
val data = apiResponse.data ?: throw SodaException("메시지 전송을 실패했습니다.")
|
||||
val data = apiResponse.data ?: throw SodaException(messageKey = "chat.message.send_failed")
|
||||
val characterContent = data.characterResponse.content
|
||||
if (characterContent.isBlank()) {
|
||||
throw SodaException("메시지 전송을 실패했습니다.")
|
||||
throw SodaException(messageKey = "chat.message.send_failed")
|
||||
}
|
||||
return characterContent
|
||||
}
|
||||
@@ -903,16 +912,16 @@ class ChatRoomService(
|
||||
fun resetChatRoom(member: Member, chatRoomId: Long, container: String): CreateChatRoomResponse {
|
||||
// 0) 방 존재 및 내 참여 여부 확인
|
||||
val room = chatRoomRepository.findByIdAndIsActiveTrue(chatRoomId)
|
||||
?: throw SodaException("채팅방을 찾을 수 없습니다.")
|
||||
?: throw SodaException(messageKey = "chat.error.room_not_found")
|
||||
participantRepository.findByChatRoomAndMemberAndIsActiveTrue(room, member)
|
||||
?: throw SodaException("잘못된 접근입니다")
|
||||
?: throw SodaException(messageKey = "chat.room.invalid_access")
|
||||
|
||||
// 1) AI 캐릭터 채팅방인지 확인 (CHARACTER 타입의 활성 참여자 존재 확인)
|
||||
val characterParticipant = participantRepository
|
||||
.findByChatRoomAndParticipantTypeAndIsActiveTrue(room, ParticipantType.CHARACTER)
|
||||
?: throw SodaException("AI 캐릭터 채팅방이 아닙니다.")
|
||||
?: throw SodaException(messageKey = "chat.room.not_ai_room")
|
||||
val character = characterParticipant.character
|
||||
?: throw SodaException("AI 캐릭터 채팅방이 아닙니다.")
|
||||
?: throw SodaException(messageKey = "chat.room.not_ai_room")
|
||||
|
||||
// 2) 30캔 결제 (채팅방 초기화 전용 CanUsage 사용)
|
||||
canPaymentService.spendCan(
|
||||
|
||||
Reference in New Issue
Block a user