회원 메시지 다국어 처리

회원/인증 API 응답 메시지를 다국어 키로 분리함.
This commit is contained in:
2025-12-23 13:26:15 +09:00
parent 4dcf9f6ed1
commit 67b909daed
12 changed files with 338 additions and 115 deletions

View File

@@ -471,6 +471,197 @@ class SodaMessageSource {
)
)
private val memberAuthMessages = mapOf(
"member.auth.blocked_policy" to mapOf(
Lang.KO to "운영정책을 위반하여 이용을 제한합니다.",
Lang.EN to "Your access is restricted due to policy violations.",
Lang.JA to "運営ポリシー違反のため利用が制限されています。"
),
"member.auth.already_verified" to mapOf(
Lang.KO to "이미 인증된 계정입니다.",
Lang.EN to "This account is already verified.",
Lang.JA to "既に認証済みのアカウントです。"
),
"member.auth.certificate_invalid_retry" to mapOf(
Lang.KO to "인증정보에 오류가 있습니다.\n다시 시도해 주세요.",
Lang.EN to "There is an error with the verification information.\nPlease try again.",
Lang.JA to "認証情報にエラーがあります。\nもう一度お試しください。"
),
"member.auth.max_accounts" to mapOf(
Lang.KO to "이미 본인인증한 계정 %s개 이용중입니다.\n" +
"소다라이브의 본인인증은 최대 3개의 계정만 이용할 수 있습니다.",
Lang.EN to "You are already using %s verified account(s).\n" +
"Identity verification is limited to up to 3 accounts on Sodalive.",
Lang.JA to "本人認証済みのアカウントを%s件利用中です。\n" +
"ソダライブの本人認証は最大3アカウントまでです。"
),
"member.auth.age_limit" to mapOf(
Lang.KO to "%s년 1월 1일 이전 출생자만 본인인증이 가능합니다.",
Lang.EN to "Only users born on or before January 1, %s can be verified.",
Lang.JA to "%s年1月1日以前に生まれた方のみ本人認証が可能です。"
)
)
private val memberMessages = mapOf(
"member.signup.failed_retry" to mapOf(
Lang.KO to "회원가입을 하지 못했습니다.\n다시 시도해 주세요.",
Lang.EN to "Sign up failed.\nPlease try again.",
Lang.JA to "会員登録に失敗しました。\nもう一度お試しください。"
),
"member.signup.success" to mapOf(
Lang.KO to "회원가입을 축하드립니다.",
Lang.EN to "Congratulations on your sign up.",
Lang.JA to "ご登録おめでとうございます。"
),
"member.login.success" to mapOf(
Lang.KO to "로그인 되었습니다.",
Lang.EN to "You are logged in.",
Lang.JA to "ログインしました。"
),
"member.signout.success" to mapOf(
Lang.KO to "정상적으로 탈퇴 처리되었습니다.",
Lang.EN to "Your account has been successfully deleted.",
Lang.JA to "正常に退会処理されました。"
)
)
private val memberValidationMessages = mapOf(
"member.validation.invalid_request_retry" to mapOf(
Lang.KO to "잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.",
Lang.EN to "Invalid request.\nPlease close the app and try again.",
Lang.JA to "不正なリクエストです。\nアプリを終了して再度お試しください。"
),
"member.validation.agree_required" to mapOf(
Lang.KO to "약관에 동의하셔야 회원가입이 가능합니다.",
Lang.EN to "You must agree to the terms to sign up.",
Lang.JA to "会員登録には規約への同意が必要です。"
),
"member.validation.user_not_found" to mapOf(
Lang.KO to "없는 사용자 입니다.",
Lang.EN to "User not found.",
Lang.JA to "ユーザーが見つかりません。"
),
"member.validation.account_not_found" to mapOf(
Lang.KO to "없는 계정입니다.",
Lang.EN to "Account not found.",
Lang.JA to "アカウントが見つかりません。"
),
"member.validation.inactive_account" to mapOf(
Lang.KO to "탈퇴한 계정입니다.\n고객센터로 문의해 주시기 바랍니다.",
Lang.EN to "This account has been deleted.\nPlease contact customer support.",
Lang.JA to "退会したアカウントです。\nカスタマーサポートへお問い合わせください。"
),
"member.validation.creator_not_found" to mapOf(
Lang.KO to "크리에이터 정보를 확인해주세요.",
Lang.EN to "Please check the creator information.",
Lang.JA to "クリエイター情報を確認してください。"
),
"member.validation.nickname_min_length" to mapOf(
Lang.KO to "두 글자 이상 입력 하셔야 합니다.",
Lang.EN to "Please enter at least 2 characters.",
Lang.JA to "2文字以上入力してください。"
),
"member.validation.password_mismatch" to mapOf(
Lang.KO to "비밀번호가 일치하지 않습니다.",
Lang.EN to "Password does not match.",
Lang.JA to "パスワードが一致しません。"
),
"member.validation.signout_reason_required" to mapOf(
Lang.KO to "탈퇴하려는 이유를 입력해 주세요.",
Lang.EN to "Please enter a reason for deleting your account.",
Lang.JA to "退会理由を入力してください。"
),
"member.validation.email_available" to mapOf(
Lang.KO to "사용 가능한 이메일 입니다.",
Lang.EN to "This email is available.",
Lang.JA to "使用可能なメールアドレスです。"
),
"member.validation.nickname_available" to mapOf(
Lang.KO to "사용 가능한 닉네임 입니다.",
Lang.EN to "This nickname is available.",
Lang.JA to "使用可能なニックネームです。"
),
"member.validation.email_in_use" to mapOf(
Lang.KO to "이미 사용중인 이메일 입니다.",
Lang.EN to "This email is already in use.",
Lang.JA to "このメールアドレスは既に使用されています。"
),
"member.validation.nickname_in_use" to mapOf(
Lang.KO to "이미 사용중인 닉네임 입니다.",
Lang.EN to "This nickname is already in use.",
Lang.JA to "このニックネームは既に使用されています。"
),
"member.validation.email_registered_with_provider" to mapOf(
Lang.KO to "해당 이메일은 %s 계정으로 가입되어 있습니다. 해당 소셜 로그인을 사용해 주세요.",
Lang.EN to "This email is registered with a %s account. Please use that social login.",
Lang.JA to "このメールアドレスは%sアカウントで登録されています。該当のソーシャルログインをご利用ください。"
),
"member.validation.email_registered_with_provider_already" to mapOf(
Lang.KO to "해당 이메일은 %s 계정으로 이미 가입되어 있습니다. 해당 소셜 로그인을 사용해 주세요.",
Lang.EN to "This email is already registered with a %s account. Please use that social login.",
Lang.JA to "このメールアドレスは既に%sアカウントで登録されています。該当のソーシャルログインをご利用ください。"
),
"member.validation.unregistered_account_retry" to mapOf(
Lang.KO to "등록되지 않은 계정입니다.\n확인 후 다시 시도해 주세요.",
Lang.EN to "This account is not registered.\nPlease check and try again.",
Lang.JA to "登録されていないアカウントです。\n確認してもう一度お試しください。"
)
)
private val memberSocialMessages = mapOf(
"member.social.google_login_failed" to mapOf(
Lang.KO to "구글 로그인을 하지 못했습니다. 다시 시도해 주세요",
Lang.EN to "Google login failed. Please try again.",
Lang.JA to "Googleログインに失敗しました。もう一度お試しください。"
),
"member.social.kakao_login_failed" to mapOf(
Lang.KO to "카카오 로그인을 하지 못했습니다. 다시 시도해 주세요",
Lang.EN to "Kakao login failed. Please try again.",
Lang.JA to "カカオログインに失敗しました。もう一度お試しください。"
),
"member.social.email_consent_required" to mapOf(
Lang.KO to "이메일 제공에 동의하셔야 서비스 이용이 가능합니다.",
Lang.EN to "You must agree to provide your email to use the service.",
Lang.JA to "サービス利用にはメール提供への同意が必要です。"
)
)
private val memberProviderMessages = mapOf(
"member.provider.email" to mapOf(
Lang.KO to "이메일",
Lang.EN to "Email",
Lang.JA to "メール"
),
"member.provider.kakao" to mapOf(
Lang.KO to "카카오",
Lang.EN to "Kakao",
Lang.JA to "カカオ"
),
"member.provider.google" to mapOf(
Lang.KO to "구글",
Lang.EN to "Google",
Lang.JA to "Google"
),
"member.provider.apple" to mapOf(
Lang.KO to "애플",
Lang.EN to "Apple",
Lang.JA to "Apple"
)
)
private val memberGenderMessages = mapOf(
"member.gender.male" to mapOf(
Lang.KO to "",
Lang.EN to "Male",
Lang.JA to "男性"
),
"member.gender.female" to mapOf(
Lang.KO to "",
Lang.EN to "Female",
Lang.JA to "女性"
)
)
fun getMessage(key: String, lang: Lang): String? {
val messageGroups = listOf(
commonMessages,
@@ -496,7 +687,13 @@ class SodaMessageSource {
messageMessages,
noticeMessages,
reportMessages,
imageValidationMessages
imageValidationMessages,
memberAuthMessages,
memberMessages,
memberValidationMessages,
memberSocialMessages,
memberProviderMessages,
memberGenderMessages
)
for (messages in messageGroups) {
val translations = messages[key] ?: continue

View File

@@ -2,6 +2,8 @@ package kr.co.vividnext.sodalive.member
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.marketing.AdTrackingHistoryType
import kr.co.vividnext.sodalive.marketing.AdTrackingService
import kr.co.vividnext.sodalive.member.block.MemberBlockRequest
@@ -37,7 +39,9 @@ class MemberController(
private val kakaoAuthService: KakaoAuthService,
private val googleAuthService: GoogleAuthService,
private val trackingService: AdTrackingService,
private val userActionService: UserActionService
private val userActionService: UserActionService,
private val messageSource: SodaMessageSource,
private val langContext: LangContext
) {
@GetMapping("/check/email")
fun checkEmail(@RequestParam email: String) = service.duplicateCheckEmail(email)
@@ -69,7 +73,8 @@ class MemberController(
actionType = ActionType.SIGN_UP
)
return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse)
val message = messageSource.getMessage("member.signup.success", langContext.lang)
return ApiResponse.ok(message = message, data = response.loginResponse)
}
@PostMapping("/signup")
@@ -87,7 +92,8 @@ class MemberController(
)
}
return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse)
val message = messageSource.getMessage("member.signup.success", langContext.lang)
return ApiResponse.ok(message = message, data = response.loginResponse)
}
@PostMapping("/login")
@@ -230,7 +236,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.creatorUnFollow(creatorId = request.creatorId, memberId = member.id!!))
}
@@ -240,7 +246,7 @@ class MemberController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.getBlockedMemberIdList(member.id!!))
}
@@ -250,7 +256,7 @@ class MemberController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getBlockedMemberList(
@@ -266,7 +272,7 @@ class MemberController(
@RequestBody request: MemberBlockRequest,
@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.memberBlock(request = request, memberId = member.id!!))
}
@@ -276,7 +282,7 @@ class MemberController(
@RequestBody request: MemberBlockRequest,
@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.memberUnBlock(request = request, memberId = member.id!!))
}
@@ -286,7 +292,7 @@ class MemberController(
@RequestParam nickname: 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.searchMember(nickname = nickname, member = member))
}
@@ -295,13 +301,16 @@ class MemberController(
fun signOut(
@RequestBody signOutRequest: SignOutRequest,
@AuthenticationPrincipal user: User
) = ApiResponse.ok(service.signOut(signOutRequest, user), "정상적으로 탈퇴 처리되었습니다.")
) = ApiResponse.ok(
service.signOut(signOutRequest, user),
messageSource.getMessage("member.signout.success", langContext.lang)
)
@GetMapping("/change/nickname/price")
fun getChangeNicknamePrice(
@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.getChangeNicknamePrice(memberId = member.id!!))
}
@@ -327,7 +336,7 @@ class MemberController(
@PathVariable id: Long,
@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.getMemberProfile(memberId = id, myMemberId = member.id!!))
}
@@ -337,7 +346,7 @@ class MemberController(
@RequestBody request: SocialLoginRequest
): ApiResponse<LoginResponse> {
if (!authHeader.startsWith("Bearer ")) {
throw SodaException("구글 로그인을 하지 못했습니다. 다시 시도해 주세요")
throw SodaException(messageKey = "member.social.google_login_failed")
}
val token = authHeader.substring(7)
@@ -359,7 +368,8 @@ class MemberController(
)
}
return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse)
val message = messageSource.getMessage("member.signup.success", langContext.lang)
return ApiResponse.ok(message = message, data = response.loginResponse)
}
@PostMapping("/login/kakao")
@@ -368,7 +378,7 @@ class MemberController(
@RequestBody request: SocialLoginRequest
): ApiResponse<LoginResponse> {
if (!authHeader.startsWith("Bearer ")) {
throw SodaException("카카오 로그인을 하지 못했습니다. 다시 시도해 주세요")
throw SodaException(messageKey = "member.social.kakao_login_failed")
}
val token = authHeader.substring(7)
@@ -390,6 +400,7 @@ class MemberController(
)
}
return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse)
val message = messageSource.getMessage("member.signup.success", langContext.lang)
return ApiResponse.ok(message = message, data = response.loginResponse)
}
}

View File

@@ -11,6 +11,8 @@ import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.order.OrderService
import kr.co.vividnext.sodalive.email.SendEmailService
import kr.co.vividnext.sodalive.fcm.PushTokenService
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.jwt.TokenProvider
import kr.co.vividnext.sodalive.live.reservation.LiveReservationRepository
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
@@ -95,6 +97,9 @@ class MemberService(
private val passwordEncoder: PasswordEncoder,
private val authenticationManagerBuilder: AuthenticationManagerBuilder,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
private val objectMapper: ObjectMapper,
@Value("\${cloud.aws.s3.bucket}")
@@ -109,13 +114,13 @@ class MemberService(
@Transactional
fun signUpV2(request: SignUpRequestV2): SignUpResponse {
val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
val stipulationPrivacyPolicy = stipulationRepository.findByIdOrNull(StipulationIds.PRIVACY_POLICY_ID)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
if (!request.isAgreePrivacyPolicy || !request.isAgreeTermsOfService) {
throw SodaException("약관에 동의하셔야 회원가입이 가능합니다.")
throw SodaException(messageKey = "member.validation.agree_required")
}
duplicateCheckEmail(request.email)
@@ -160,14 +165,14 @@ class MemberService(
requestString: String
): SignUpResponse {
val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
val stipulationPrivacyPolicy = stipulationRepository.findByIdOrNull(StipulationIds.PRIVACY_POLICY_ID)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
val request = objectMapper.readValue(requestString, SignUpRequest::class.java)
if (!request.isAgreePrivacyPolicy || !request.isAgreeTermsOfService) {
throw SodaException("약관에 동의하셔야 회원가입이 가능합니다.")
throw SodaException(messageKey = "member.validation.agree_required")
}
validatePassword(request.password)
@@ -187,14 +192,14 @@ class MemberService(
fun login(request: LoginRequest): ApiResponse<LoginResponse> {
return ApiResponse.ok(
message = "로그인 되었습니다.",
message = messageSource.getMessage("member.login.success", langContext.lang),
data = login(request.email, request.password, request.isAdmin, request.isCreator)
)
}
fun getMember(id: Long, container: String): ProfileResponse {
val member = repository.findByIdOrNull(id)
?: throw SodaException("없는 사용자 입니다.")
?: throw SodaException(messageKey = "member.validation.user_not_found")
return ProfileResponse(member, cloudFrontHost, container)
}
@@ -202,9 +207,9 @@ class MemberService(
fun getMemberInfo(member: Member, container: String): GetMemberInfoResponse {
val gender = if (member.auth != null) {
if (member.auth!!.gender == 1) {
""
messageSource.getMessage("member.gender.male", langContext.lang)
} else {
""
messageSource.getMessage("member.gender.female", langContext.lang)
}
} else {
null
@@ -260,7 +265,7 @@ class MemberService(
@Transactional
fun updateAdid(memberId: Long, adid: String) {
val member = repository.findByIdOrNull(id = memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
member.adid = adid
}
@@ -305,28 +310,24 @@ class MemberService(
isAdmin: Boolean = false,
isCreator: Boolean = false
): LoginResponse {
val member = repository.findByEmail(email = email) ?: throw SodaException("없는 계정입니다.")
val member = repository.findByEmail(email = email)
?: throw SodaException(messageKey = "member.validation.account_not_found")
if (!member.isActive) {
throw SodaException("탈퇴한 계정입니다.\n고객센터로 문의해 주시기 바랍니다.")
throw SodaException(messageKey = "member.validation.inactive_account")
}
if (member.provider != MemberProvider.EMAIL) {
val provider = when (member.provider) {
MemberProvider.APPLE -> "애플"
MemberProvider.GOOGLE -> "구글"
else -> "카카오"
}
throw SodaException("해당 이메일은 $provider 계정으로 가입되어 있습니다. 해당 소셜 로그인을 사용해 주세요.")
val provider = resolveProviderLabel(member.provider)
throw SodaException(message = formatMessage("member.validation.email_registered_with_provider", provider))
}
if (isCreator && member.role != MemberRole.CREATOR) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
if (isAdmin && member.role != MemberRole.ADMIN) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
val authenticationToken = UsernamePasswordAuthenticationToken(email, password)
@@ -414,22 +415,17 @@ class MemberService(
if (findMember != null) {
if (findMember.provider == MemberProvider.EMAIL) {
throw SodaException("이미 사용중인 이메일 입니다.", "email")
throw SodaException(messageKey = "member.validation.email_in_use", errorProperty = "email")
} else {
val provider = when (findMember.provider) {
MemberProvider.APPLE -> "애플"
MemberProvider.GOOGLE -> "구글"
else -> "카카오"
}
val provider = resolveProviderLabel(findMember.provider)
throw SodaException(
"해당 이메일은 $provider 계정으로 이미 가입되어 있습니다. 해당 소셜 로그인을 사용해 주세요.",
"email"
message = formatMessage("member.validation.email_registered_with_provider_already", provider),
errorProperty = "email"
)
}
}
return ApiResponse.ok(message = "사용 가능한 이메일 입니다.")
return ApiResponse.ok(message = messageSource.getMessage("member.validation.email_available", langContext.lang))
}
private fun validateEmail(email: String) {
@@ -441,8 +437,9 @@ class MemberService(
fun duplicateCheckNickname(nickname: String): ApiResponse<Any> {
validateNickname(nickname)
repository.findByNickname(nickname)?.let { throw SodaException("이미 사용중인 닉네임 입니다.", "nickname") }
return ApiResponse.ok(message = "사용 가능한 닉네임 입니다.")
repository.findByNickname(nickname)
?.let { throw SodaException(messageKey = "member.validation.nickname_in_use", errorProperty = "nickname") }
return ApiResponse.ok(message = messageSource.getMessage("member.validation.nickname_available", langContext.lang))
}
private fun validateNickname(nickname: String) {
@@ -469,8 +466,10 @@ class MemberService(
)
if (creatorFollowing == null) {
val creator = repository.findByIdOrNull(creatorId) ?: throw SodaException("크리에이터 정보를 확인해주세요.")
val member = repository.findByIdOrNull(memberId) ?: throw SodaException("로그인 정보를 확인해주세요.")
val creator = repository.findByIdOrNull(creatorId)
?: throw SodaException(messageKey = "member.validation.creator_not_found")
val member = repository.findByIdOrNull(memberId)
?: throw SodaException(messageKey = "common.error.bad_credentials")
val newCreatorFollowing = CreatorFollowing()
newCreatorFollowing.member = member
@@ -514,10 +513,10 @@ class MemberService(
if (blockMember == null) {
val blockedMember = repository.findByIdOrNull(id = request.blockMemberId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
val member = repository.findByIdOrNull(id = memberId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
blockMember = BlockMember()
blockMember.member = member
@@ -545,7 +544,7 @@ class MemberService(
fun searchMember(nickname: String, member: Member): List<GetRoomDetailUser> {
if (nickname.length < 2) {
throw SodaException("두 글자 이상 입력 하셔야 합니다.")
throw SodaException(messageKey = "member.validation.nickname_min_length")
}
return repository.findByNicknameAndOtherCondition(nickname, member)
@@ -560,7 +559,7 @@ class MemberService(
@Transactional
fun logout(token: String, memberId: Long) {
val member = repository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
member.pushToken = null
pushTokenService.logout(memberId = memberId)
@@ -568,7 +567,7 @@ class MemberService(
val lock = getOrCreateLock(memberId = memberId)
lock.write {
val memberToken = tokenRepository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
memberToken.tokenSet.remove(token)
tokenRepository.save(memberToken)
@@ -578,7 +577,7 @@ class MemberService(
@Transactional
fun logoutAll(memberId: Long) {
val member = repository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
member.pushToken = null
pushTokenService.logout(memberId = memberId)
@@ -589,16 +588,17 @@ class MemberService(
@Transactional
fun signOut(signOutRequest: SignOutRequest, user: User) {
val member = repository.findByEmail(user.username) ?: throw SodaException("로그인 정보를 확인해주세요.")
val member = repository.findByEmail(user.username)
?: throw SodaException(messageKey = "common.error.bad_credentials")
if (
member.provider == MemberProvider.EMAIL &&
!passwordEncoder.matches(signOutRequest.password, member.password)
) {
throw SodaException("비밀번호가 일치하지 않습니다.")
throw SodaException(messageKey = "member.validation.password_mismatch")
}
if (signOutRequest.reason.isBlank()) {
throw SodaException("탈퇴하려는 이유를 입력해 주세요.")
throw SodaException(messageKey = "member.validation.signout_reason_required")
}
logoutAll(memberId = member.id!!)
@@ -617,15 +617,16 @@ class MemberService(
@Transactional
fun updateNickname(profileUpdateRequest: ProfileUpdateRequest, user: User) {
if (profileUpdateRequest.email != user.username) {
throw SodaException("로그인 정보를 확인해 주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
val member = repository.findByEmail(user.username) ?: throw SodaException("로그인 정보를 확인해주세요.")
val member = repository.findByEmail(user.username)
?: throw SodaException(messageKey = "common.error.bad_credentials")
if (profileUpdateRequest.nickname != null) {
validateNickname(profileUpdateRequest.nickname)
repository.findByNickname(profileUpdateRequest.nickname)
?.let { throw SodaException("이미 사용중인 닉네임 입니다.") }
?.let { throw SodaException(messageKey = "member.validation.nickname_in_use") }
val price = repository.getChangeNicknamePrice(memberId = member.id!!).price
if (price > 0) {
@@ -648,17 +649,18 @@ class MemberService(
@Transactional
fun profileUpdate(profileUpdateRequest: ProfileUpdateRequest, user: User): ProfileResponse {
if (profileUpdateRequest.email != user.username) {
throw SodaException("로그인 정보를 확인해 주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
val member = repository.findByEmail(user.username) ?: throw SodaException("로그인 정보를 확인해주세요.")
val member = repository.findByEmail(user.username)
?: throw SodaException(messageKey = "common.error.bad_credentials")
if (profileUpdateRequest.modifyPassword != null) {
if (passwordEncoder.matches(profileUpdateRequest.password, member.password)) {
validatePassword(profileUpdateRequest.modifyPassword)
member.password = passwordEncoder.encode(profileUpdateRequest.modifyPassword)
} else {
throw SodaException("비밀번호가 일치하지 않습니다.")
throw SodaException(messageKey = "member.validation.password_mismatch")
}
}
@@ -669,7 +671,7 @@ class MemberService(
if (profileUpdateRequest.nickname != null) {
validateNickname(profileUpdateRequest.nickname)
repository.findByNickname(profileUpdateRequest.nickname)
?.let { throw SodaException("이미 사용중인 닉네임 입니다.") }
?.let { throw SodaException(messageKey = "member.validation.nickname_in_use") }
member.nickname = profileUpdateRequest.nickname
}
@@ -723,7 +725,8 @@ class MemberService(
@Transactional
fun profileImageUpdate(multipartFile: MultipartFile, user: User): String {
val member = repository.findByEmail(user.username) ?: throw SodaException("로그인 정보를 확인해주세요.")
val member = repository.findByEmail(user.username)
?: throw SodaException(messageKey = "common.error.bad_credentials")
val metadata = ObjectMetadata()
metadata.contentLength = multipartFile.size
@@ -741,17 +744,11 @@ class MemberService(
@Transactional
fun forgotPassword(request: ForgotPasswordRequest) {
val member = repository.getMemberByEmail(email = request.email)
?: throw SodaException("등록되지 않은 계정입니다.\n확인 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "member.validation.unregistered_account_retry")
val provider = when (member.provider) {
MemberProvider.EMAIL -> "이메일"
MemberProvider.KAKAO -> "카카오"
MemberProvider.GOOGLE -> "구글"
MemberProvider.APPLE -> "애플"
}
if (provider != "이메일") {
throw SodaException("해당 계정은 $provider 계정으로 가입되어 있습니다. 해당 소셜 로그인을 사용해 주세요.")
if (member.provider != MemberProvider.EMAIL) {
val provider = resolveProviderLabel(member.provider)
throw SodaException(message = formatMessage("member.validation.email_registered_with_provider", provider))
}
val password = generatePassword(12)
@@ -779,6 +776,21 @@ class MemberService(
return repository.getMemberProfile(memberId, myMemberId)
}
private fun formatMessage(key: String, vararg args: Any): String {
val template = messageSource.getMessage(key, langContext.lang) ?: ""
return if (args.isEmpty()) template else String.format(template, *args)
}
private fun resolveProviderLabel(provider: MemberProvider): String {
val key = when (provider) {
MemberProvider.EMAIL -> "member.provider.email"
MemberProvider.KAKAO -> "member.provider.kakao"
MemberProvider.GOOGLE -> "member.provider.google"
MemberProvider.APPLE -> "member.provider.apple"
}
return messageSource.getMessage(key, langContext.lang) ?: provider.name
}
private fun getOrCreateLock(memberId: Long): ReentrantReadWriteLock {
return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() }
}
@@ -786,7 +798,7 @@ class MemberService(
@Transactional
fun updateMarketingInfo(memberId: Long, adid: String, pid: String): String? {
val member = repository.findByIdOrNull(id = memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
if (adid != member.adid) {
member.adid = adid
@@ -814,15 +826,15 @@ class MemberService(
if (findMember.isActive) {
return MemberResolveResult(member = findMember, isNew = false)
} else {
throw SodaException("탈퇴한 계정입니다.\n고객센터로 문의해 주시기 바랍니다.")
throw SodaException(messageKey = "member.validation.inactive_account")
}
}
val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
val stipulationPrivacyPolicy = stipulationRepository.findByIdOrNull(StipulationIds.PRIVACY_POLICY_ID)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
val email = googleUserInfo.email
checkEmail(email)
@@ -870,15 +882,15 @@ class MemberService(
if (findMember.isActive) {
return MemberResolveResult(member = findMember, isNew = false)
} else {
throw SodaException("탈퇴한 계정입니다.\n고객센터로 문의해 주시기 바랍니다.")
throw SodaException(messageKey = "member.validation.inactive_account")
}
}
val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
val stipulationPrivacyPolicy = stipulationRepository.findByIdOrNull(StipulationIds.PRIVACY_POLICY_ID)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
val email = kakaoUserInfo.email
checkEmail(email)
@@ -918,13 +930,8 @@ class MemberService(
val member = repository.findByEmail(email)
if (member != null) {
val provider = when (member.provider) {
MemberProvider.APPLE -> "애플"
MemberProvider.GOOGLE -> "구글"
else -> "카카오"
}
throw SodaException("해당 이메일은 $provider 계정으로 가입되어 있습니다. 해당 소셜 로그인을 사용해 주세요.")
val provider = resolveProviderLabel(member.provider)
throw SodaException(message = formatMessage("member.validation.email_registered_with_provider", provider))
}
}
}

View File

@@ -22,13 +22,13 @@ class AuthController(
@RequestBody request: AuthVerifyRequest,
@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 authenticateData = service.certificate(request, memberId = member.id!!)
if (service.isBlockAuth(authenticateData)) {
service.signOut(member.id!!)
throw SodaException("운영정책을 위반하여 이용을 제한합니다.")
throw SodaException(messageKey = "member.auth.blocked_policy")
}
val authResponse = service.authenticate(authenticateData, member.id!!)

View File

@@ -3,6 +3,8 @@ package kr.co.vividnext.sodalive.member.auth
import com.fasterxml.jackson.databind.ObjectMapper
import kr.co.bootpay.Bootpay
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.MemberRepository
import kr.co.vividnext.sodalive.member.MemberService
import kr.co.vividnext.sodalive.member.SignOut
@@ -22,6 +24,8 @@ class AuthService(
private val memberService: MemberService,
private val memberRepository: MemberRepository,
private val signOutRepository: SignOutRepository,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
@Value("\${bootpay.application-id}")
private val bootpayApplicationId: String,
@@ -32,16 +36,16 @@ class AuthService(
val bootpay = Bootpay(bootpayApplicationId, bootpayPrivateKey)
val authId = repository.getAuthIdByMemberId(memberId = memberId)
if (authId != null) throw SodaException("이미 인증된 계정입니다.")
if (authId != null) throw SodaException(messageKey = "member.auth.already_verified")
val certificateResult: AuthCertificateResult = try {
val token = bootpay.accessToken
if (token["error_code"] != null) throw SodaException("인증정보에 오류가 있습니다.\n다시 시도해 주세요.")
if (token["error_code"] != null) throw SodaException(messageKey = "member.auth.certificate_invalid_retry")
val res = bootpay.certificate(request.receiptId)
objectMapper.convertValue(res, AuthCertificateResult::class.java)
} catch (e: Exception) {
throw SodaException(e.message ?: "인증정보에 오류가 있습니다.\n다시 시도해 주세요.")
throw SodaException(messageKey = "member.auth.certificate_invalid_retry")
}
if (
@@ -51,7 +55,7 @@ class AuthService(
) {
return certificateResult.authenticateData
} else {
throw SodaException("인증정보에 오류가 있습니다.\n다시 시도해 주세요.")
throw SodaException(messageKey = "member.auth.certificate_invalid_retry")
}
}
@@ -62,11 +66,13 @@ class AuthService(
@Transactional
fun signOut(memberId: Long) {
val member = memberRepository.findByIdOrNull(memberId) ?: throw SodaException("로그인 정보를 확인해주세요.")
val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException(messageKey = "common.error.bad_credentials")
member.isActive = false
member.nickname = "deleted_${member.nickname}"
val signOut = SignOut(reason = "운영정책을 위반하여 이용을 제한합니다.")
val signOutReason = messageSource.getMessage("member.auth.blocked_policy", langContext.lang) ?: ""
val signOut = SignOut(reason = signOutReason)
signOut.member = member
signOutRepository.save(signOut)
@@ -77,13 +83,14 @@ class AuthService(
fun authenticate(certificate: AuthVerifyCertificate, memberId: Long): AuthResponse {
val memberIds = repository.getActiveMemberIdsByDi(di = certificate.di)
if (memberIds.size >= 3) {
val message = messageSource.getMessage("member.auth.max_accounts", langContext.lang) ?: ""
throw SodaException(
"이미 본인인증한 계정 ${memberIds.size}개 이용중입니다.\n" +
"소다라이브의 본인인증은 최대 3개의 계정만 이용할 수 있습니다."
message = String.format(message, memberIds.size)
)
}
val member = memberRepository.findByIdOrNull(memberId) ?: throw SodaException("로그인 정보를 확인해주세요.")
val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException(messageKey = "common.error.bad_credentials")
val nowYear = LocalDate.now().year
val certificateYear = certificate.birth.substring(0, 4).toInt()
if (nowYear - certificateYear >= 19) {
@@ -99,7 +106,8 @@ class AuthService(
repository.save(auth)
return AuthResponse(gender = certificate.gender)
} else {
throw SodaException("${nowYear - 19}년 1월 1일 이전 출생자만 본인인증이 가능합니다.")
val message = messageSource.getMessage("member.auth.age_limit", langContext.lang) ?: ""
throw SodaException(message = String.format(message, nowYear - 19))
}
}
}

View File

@@ -67,7 +67,7 @@ class NicknameGenerateService(private val repository: MemberRepository) {
}
}
}
throw SodaException("회원가입을 하지 못했습니다.\n다시 시도해 주세요.")
throw SodaException(messageKey = "member.signup.failed_retry")
}
fun generateUniqueNickname(): String {

View File

@@ -26,7 +26,7 @@ class GoogleAuthService(
pushToken: String?
): SocialLoginResponse {
val googleUserInfo = googleService.getUserInfo(idToken)
?: throw SodaException("구글 로그인을 하지 못했습니다. 다시 시도해 주세요")
?: throw SodaException(messageKey = "member.social.google_login_failed")
val memberResolveResult = memberService.findOrRegister(googleUserInfo, container, marketingPid, pushToken)
val member = memberResolveResult.member
val principal = MemberAdapter(member)

View File

@@ -27,7 +27,7 @@ class GoogleService(
if (token != null) {
val payload = token.payload
val email = payload.email ?: throw SodaException("이메일 제공에 동의하셔야 서비스 이용이 가능합니다.")
val email = payload.email ?: throw SodaException(messageKey = "member.social.email_consent_required")
GoogleUserInfo(
sub = payload.subject,

View File

@@ -26,7 +26,7 @@ class KakaoAuthService(
pushToken: String?
): SocialLoginResponse {
val kakaoUserInfo = kakaoService.getUserInfo(accessToken)
?: throw SodaException("카카오 로그인을 하지 못했습니다. 다시 시도해 주세요")
?: throw SodaException(messageKey = "member.social.kakao_login_failed")
val memberResolveResult = memberService.findOrRegister(kakaoUserInfo, container, marketingPid, pushToken)
val member = memberResolveResult.member
val principal = MemberAdapter(member)

View File

@@ -37,7 +37,7 @@ class KakaoService(
val id = jsonNode.get("id").asLong()
val kakaoAccount = jsonNode.get("kakao_account")
val email = kakaoAccount?.get("email")?.asText()
?: throw SodaException("카카오 로그인을 하지 못했습니다. 다시 시도해 주세요")
?: throw SodaException(messageKey = "member.social.kakao_login_failed")
val properties = jsonNode.get("properties")
val nickname = properties?.get("nickname")?.asText()

View File

@@ -16,14 +16,14 @@ class StipulationService(private val repository: StipulationRepository) {
fun getTermsOfService(): StipulationDto {
val stipulation = repository.findByIdOrNull(TERMS_OF_SERVICE_ID)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
return StipulationDto(stipulation)
}
fun getPrivacyPolicy(): StipulationDto {
val stipulation = repository.findByIdOrNull(PRIVACY_POLICY_ID)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
return StipulationDto(stipulation)
}
@@ -31,7 +31,7 @@ class StipulationService(private val repository: StipulationRepository) {
@Transactional
fun modify(request: StipulationModifyRequest) {
val stipulation = repository.findByIdOrNull(request.id)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
stipulation.description = request.description
}

View File

@@ -15,7 +15,7 @@ class MemberTagController(private val service: MemberTagService) {
fun getTags(
@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.getTags(member))
}