From ff1b8aa4131cdeeac9616de0dee327cb5802cc53 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 22 Dec 2025 16:39:06 +0900 Subject: [PATCH] =?UTF-8?q?=EC=98=88=EC=99=B8=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=ED=82=A4=20=EA=B8=B0=EB=B0=98?= =?UTF-8?q?=20=EA=B5=AC=EC=A1=B0=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/room/service/ChatRoomService.kt | 22 ++++----- .../sodalive/common/SodaException.kt | 11 ++++- .../sodalive/common/SodaExceptionHandler.kt | 35 +++++++++---- .../sodalive/i18n/SodaMessageSource.kt | 49 +++++++++++++++++++ .../sodalive/member/MemberController.kt | 20 ++++---- 5 files changed, 104 insertions(+), 33 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt 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 bf745f2c..3a47432c 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 @@ -316,11 +316,9 @@ class ChatRoomService( @Transactional(readOnly = true) fun isMyRoomSessionActive(member: Member, chatRoomId: Long): Boolean { val room = chatRoomRepository.findByIdAndIsActiveTrue(chatRoomId) - ?: throw SodaException("채팅방을 찾을 수 없습니다.") - val participant = participantRepository.findByChatRoomAndMemberAndIsActiveTrue(room, member) - if (participant == null) { - throw SodaException("잘못된 접근입니다") - } + ?: throw SodaException(messageKey = "chat.error.room_not_found") + participantRepository.findByChatRoomAndMemberAndIsActiveTrue(room, member) + ?: throw SodaException(messageKey = "common.error.access_denied") return fetchSessionActive(room.sessionId) } @@ -328,7 +326,7 @@ class ChatRoomService( fun enterChatRoom(member: Member, chatRoomId: Long, characterImageId: Long? = null): ChatRoomEnterResponse { // 1) 활성 여부 무관하게 방 조회 val baseRoom = chatRoomRepository.findById(chatRoomId).orElseThrow { - SodaException("채팅방을 찾을 수 없습니다.") + SodaException(messageKey = "chat.error.room_not_found") } // 2) 기본 방 기준 참여/활성 여부 확인 @@ -342,10 +340,10 @@ class ChatRoomService( ParticipantType.CHARACTER ) ?: baseRoom.participants.firstOrNull { it.participantType == ParticipantType.CHARACTER - } ?: throw SodaException("잘못된 접근입니다") + } ?: throw SodaException(messageKey = "common.error.invalid_request") val baseCharacter = baseCharacterParticipant.character - ?: throw SodaException("오류가 발생했습니다. 다시 시도해 주세요.") + ?: throw SodaException(messageKey = "common.error.unknown") // 4) 유효한 입장 대상 방 결정 val effectiveRoom: ChatRoom = if (isActiveRoom && isMyActiveParticipation) { @@ -355,9 +353,9 @@ class ChatRoomService( val alt = chatRoomRepository.findActiveChatRoomByMemberAndCharacter(member, baseCharacter) alt ?: ( // 대체 방이 없으면 기존과 동일하게 예외 처리 if (!isActiveRoom) { - throw SodaException("채팅방을 찾을 수 없습니다.") + throw SodaException(messageKey = "chat.error.room_not_found") } else { - throw SodaException("잘못된 접근입니다") + throw SodaException(messageKey = "common.error.invalid_request") } ) } @@ -368,10 +366,10 @@ class ChatRoomService( ParticipantType.CHARACTER ) ?: effectiveRoom.participants.firstOrNull { it.participantType == ParticipantType.CHARACTER - } ?: throw SodaException("잘못된 접근입니다") + } ?: throw SodaException(messageKey = "common.error.invalid_request") val character = characterParticipant.character - ?: throw SodaException("오류가 발생했습니다. 다시 시도해 주세요.") + ?: throw SodaException(messageKey = "common.error.unknown") val imageUrl = "$imageHost/${character.imagePath ?: "profile/default-profile.png"}" val characterDto = ChatRoomEnterCharacterDto( diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/common/SodaException.kt b/src/main/kotlin/kr/co/vividnext/sodalive/common/SodaException.kt index 261e4812..86046fdc 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/common/SodaException.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/common/SodaException.kt @@ -1,5 +1,12 @@ package kr.co.vividnext.sodalive.common -class SodaException(message: String, val errorProperty: String? = null) : RuntimeException(message) +class SodaException( + message: String? = null, + val errorProperty: String? = null, + val messageKey: String? = null +) : RuntimeException(message) -class AdsChargeException(message: String) : RuntimeException(message) +class AdsChargeException( + message: String? = null, + val messageKey: String? = null +) : RuntimeException(message) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/common/SodaExceptionHandler.kt b/src/main/kotlin/kr/co/vividnext/sodalive/common/SodaExceptionHandler.kt index 74e1396f..443e58cc 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/common/SodaExceptionHandler.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/common/SodaExceptionHandler.kt @@ -1,5 +1,7 @@ package kr.co.vividnext.sodalive.common +import kr.co.vividnext.sodalive.i18n.LangContext +import kr.co.vividnext.sodalive.i18n.SodaMessageSource import org.slf4j.LoggerFactory import org.springframework.dao.DataIntegrityViolationException import org.springframework.http.HttpStatus @@ -13,14 +15,20 @@ import org.springframework.web.multipart.MaxUploadSizeExceededException import org.springframework.web.server.ResponseStatusException @RestControllerAdvice -class SodaExceptionHandler { +class SodaExceptionHandler( + private val langContext: LangContext, + private val messageSource: SodaMessageSource +) { private val logger = LoggerFactory.getLogger(this::class.java) @ExceptionHandler(SodaException::class) fun handleSodaException(e: SodaException) = run { logger.error("API error", e) + val message = e.messageKey?.takeIf { it.isNotBlank() }?.let { messageSource.getMessage(it, langContext.lang) } + ?: e.message?.takeIf { it.isNotBlank() } + ?: messageSource.getMessage("common.error.unknown", langContext.lang) ApiResponse.error( - message = e.message, + message = message, errorProperty = e.errorProperty ) } @@ -28,44 +36,53 @@ class SodaExceptionHandler { @ExceptionHandler(MaxUploadSizeExceededException::class) fun handleMaxUploadSizeExceededException(e: MaxUploadSizeExceededException) = run { logger.error("API error", e) - ApiResponse.error(message = "파일용량은 최대 1024MB까지 저장할 수 있습니다.") + val message = messageSource.getMessage("common.error.max_upload_size", langContext.lang) + ApiResponse.error(message = message) } @ExceptionHandler(AccessDeniedException::class) fun handleAccessDeniedException(e: AccessDeniedException) = run { logger.error("API error", e) - ApiResponse.error(message = "권한이 없습니다.") + val message = messageSource.getMessage("common.error.access_denied", langContext.lang) + ApiResponse.error(message = message) } @ExceptionHandler(InternalAuthenticationServiceException::class) fun handleInternalAuthenticationServiceException(e: InternalAuthenticationServiceException) = run { logger.error("API error", e) - ApiResponse.error("로그인 정보를 확인해주세요.") + val message = messageSource.getMessage("common.error.bad_credentials", langContext.lang) + ApiResponse.error(message) } @ExceptionHandler(BadCredentialsException::class) fun handleBadCredentialsException(e: BadCredentialsException) = run { logger.error("API error", e) - ApiResponse.error("로그인 정보를 확인해주세요.") + val message = messageSource.getMessage("common.error.bad_credentials", langContext.lang) + ApiResponse.error(message) } @ExceptionHandler(DataIntegrityViolationException::class) fun handleDataIntegrityViolationException(e: DataIntegrityViolationException) = run { logger.error("API error", e) - ApiResponse.error("이미 등록되어 있습니다.") + val message = messageSource.getMessage("common.error.already_registered", langContext.lang) + ApiResponse.error(message) } @ResponseStatus(value = HttpStatus.NOT_FOUND) @ExceptionHandler(AdsChargeException::class) fun handleAdsChargeException(e: AdsChargeException) = run { logger.error("API error - AdsChargeException ::: ", e) - ApiResponse.error("잘못된 요청입니다.") + val message = e.messageKey?.takeIf { it.isNotBlank() }?.let { messageSource.getMessage(it, langContext.lang) } + ?: e.message?.takeIf { it.isNotBlank() } + ?: messageSource.getMessage("common.error.invalid_request", langContext.lang) + ApiResponse.error(message) } @ExceptionHandler(Exception::class) fun handleException(e: Exception) = run { if (e is ResponseStatusException) throw e logger.error("API error", e) - ApiResponse.error("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + val message = messageSource.getMessage("common.error.unknown", langContext.lang) + ApiResponse.error(message) } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt new file mode 100644 index 00000000..2cf5da17 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt @@ -0,0 +1,49 @@ +package kr.co.vividnext.sodalive.i18n + +import org.springframework.stereotype.Component + +@Component +class SodaMessageSource { + private val messages = mapOf( + "common.error.unknown" to mapOf( + Lang.KO to "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.", + Lang.EN to "An unknown error occurred. Please try again.", + Lang.JA to "不明なエラーが発生しました。もう一度やり直してください。" + ), + "common.error.access_denied" to mapOf( + Lang.KO to "권한이 없습니다.", + Lang.EN to "You do not have permission.", + Lang.JA to "権限がありません。" + ), + "common.error.bad_credentials" to mapOf( + Lang.KO to "로그인 정보를 확인해주세요.", + Lang.EN to "Please check your login information.", + Lang.JA to "ログイン情報を確認してください。" + ), + "common.error.max_upload_size" to mapOf( + Lang.KO to "파일용량은 최대 1024MB까지 저장할 수 있습니다.", + Lang.EN to "The file size can be saved up to 1024MB.", + Lang.JA to "ファイル容量は最大1024MBまで保存できます。" + ), + "common.error.already_registered" to mapOf( + Lang.KO to "이미 등록되어 있습니다.", + Lang.EN to "It is already registered.", + Lang.JA to "すでに登録されています。" + ), + "common.error.invalid_request" to mapOf( + Lang.KO to "잘못된 요청입니다.", + Lang.EN to "Invalid request.", + Lang.JA to "不正なリクエストです。" + ), + "chat.error.room_not_found" to mapOf( + Lang.KO to "채팅방을 찾을 수 없습니다.", + Lang.EN to "Chat room not found.", + Lang.JA to "チャットルームが見つかりません。" + ) + ) + + fun getMessage(key: String, lang: Lang): String? { + val translations = messages[key] ?: return null + return translations[lang] ?: translations[Lang.KO] + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt index a2716e06..e6a094a5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt @@ -98,7 +98,7 @@ class MemberController( @RequestHeader("Authorization") token: String, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { - if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member == null) throw SodaException(messageKey = "common.error.bad_credentials") ApiResponse.ok(service.logout(token.removePrefix("Bearer "), member.id!!)) } @@ -107,7 +107,7 @@ class MemberController( fun logoutAll( @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { - if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member == null) throw SodaException(messageKey = "common.error.bad_credentials") ApiResponse.ok(service.logoutAll(member.id!!)) } @@ -117,7 +117,7 @@ class MemberController( @RequestParam container: String, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { - if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member == null) throw SodaException(messageKey = "common.error.bad_credentials") ApiResponse.ok(service.getMember(member.id!!, container)) } @@ -127,7 +127,7 @@ class MemberController( @RequestParam container: String?, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { - if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member == null) throw SodaException(messageKey = "common.error.bad_credentials") ApiResponse.ok(service.getMemberInfo(member, container ?: "web")) } @@ -137,7 +137,7 @@ class MemberController( @RequestBody request: UpdateNotificationSettingRequest, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { - if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member == null) throw SodaException(messageKey = "common.error.bad_credentials") ApiResponse.ok(service.updateNotificationSettings(request, member)) } @@ -147,7 +147,7 @@ class MemberController( @RequestBody pushTokenUpdateRequest: PushTokenUpdateRequest, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { - if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member == null) throw SodaException(messageKey = "common.error.bad_credentials") ApiResponse.ok( service.updatePushToken( @@ -163,7 +163,7 @@ class MemberController( @RequestBody request: MarketingInfoUpdateRequest, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { - if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member == null) throw SodaException(messageKey = "common.error.bad_credentials") val memberId = member.id!! val marketingPid = service.updateMarketingInfo( @@ -188,7 +188,7 @@ class MemberController( @RequestBody request: AdidUpdateRequest, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { - if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member == null) throw SodaException(messageKey = "common.error.bad_credentials") ApiResponse.ok( service.updateAdid( @@ -203,7 +203,7 @@ class MemberController( @RequestParam container: String, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { - if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member == null) throw SodaException(messageKey = "common.error.bad_credentials") ApiResponse.ok(service.getMyPage(member, container)) } @@ -213,7 +213,7 @@ class MemberController( @RequestBody request: CreatorFollowRequest, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { - if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member == null) throw SodaException(messageKey = "common.error.bad_credentials") ApiResponse.ok( service.creatorFollow(