예외 메시지 다국어 처리를 위한 키 기반 구조 도입
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user