라이브 룰렛 태그 메시지 다국어 처리

This commit is contained in:
2025-12-23 13:52:53 +09:00
parent 67b909daed
commit fd94df338b
7 changed files with 172 additions and 52 deletions

View File

@@ -626,6 +626,87 @@ class SodaMessageSource {
) )
) )
private val liveRouletteMessages = mapOf(
"live.roulette.unavailable" to mapOf(
Lang.KO to "룰렛을 사용할 수 없습니다.",
Lang.EN to "Roulette is unavailable.",
Lang.JA to "ルーレットを使用できません。"
),
"live.roulette.live_not_found" to mapOf(
Lang.KO to "해당하는 라이브가 없습니다.",
Lang.EN to "Live session not found.",
Lang.JA to "該当するライブがありません。"
),
"live.roulette.live_info_not_found" to mapOf(
Lang.KO to "해당하는 라이브의 정보가 없습니다.",
Lang.EN to "Live session information not found.",
Lang.JA to "該当するライブの情報がありません。"
),
"live.roulette.creator_contract_only" to mapOf(
Lang.KO to "주식회사 소다라이브와 계약한\n크리에이터의 룰렛만 사용하실 수 있습니다.",
Lang.EN to "Only roulette from creators contracted with Sodalive Co., Ltd. can be used.",
Lang.JA to "株式会社ソダライブと契約した\nクリエイターのルーレットのみ利用できます。"
),
"live.roulette.refund_failed" to mapOf(
Lang.KO to "룰렛 돌리기에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요.",
Lang.EN to "Cans from the failed roulette spin have not been refunded.\nPlease contact customer support.",
Lang.JA to "ルーレットの失敗分の缶が返金されていません。\nカスタマーサポートへお問い合わせください。"
),
"live.roulette.min_can" to mapOf(
Lang.KO to "룰렛 금액은 최소 5캔 입니다.",
Lang.EN to "Roulette cost is at least 5 cans.",
Lang.JA to "ルーレット金額は最低5缶です。"
),
"live.roulette.item_count_range" to mapOf(
Lang.KO to "룰렛 옵션은 최소 2개, 최대 10개까지 설정할 수 있습니다.",
Lang.EN to "Roulette options must be between 2 and 10 items.",
Lang.JA to "ルーレットのオプションは最小2個、最大10個まで設定できます。"
),
"live.roulette.probability_invalid" to mapOf(
Lang.KO to "확률이 100%가 아닙니다",
Lang.EN to "The probability is not 100%.",
Lang.JA to "確率が100%ではありません。"
),
"live.roulette.result_message" to mapOf(
Lang.KO to "[%s] 당첨!",
Lang.EN to "[%s] Won!",
Lang.JA to "[%s] 当選!"
),
"live.roulette.can_title" to mapOf(
Lang.KO to "%s 캔",
Lang.EN to "%s cans",
Lang.JA to "%s 缶"
),
"live.roulette.refund_method" to mapOf(
Lang.KO to "룰렛 환불",
Lang.EN to "Roulette refund",
Lang.JA to "ルーレット返金"
)
)
private val liveTagMessages = mapOf(
"live.tag.registered" to mapOf(
Lang.KO to "등록되었습니다.",
Lang.EN to "Successfully registered.",
Lang.JA to "登録されました。"
),
"live.tag.deleted" to mapOf(
Lang.KO to "삭제되었습니다.",
Lang.EN to "Deleted.",
Lang.JA to "削除されました。"
),
"live.tag.updated" to mapOf(
Lang.KO to "수정되었습니다.",
Lang.EN to "Updated.",
Lang.JA to "更新されました。"
),
"live.tag.duplicate" to mapOf(
Lang.KO to "이미 등록된 태그 입니다.",
Lang.EN to "Tag already registered.",
Lang.JA to "すでに登録されたタグです。"
)
)
private val memberProviderMessages = mapOf( private val memberProviderMessages = mapOf(
"member.provider.email" to mapOf( "member.provider.email" to mapOf(
Lang.KO to "이메일", Lang.KO to "이메일",
@@ -692,6 +773,8 @@ class SodaMessageSource {
memberMessages, memberMessages,
memberValidationMessages, memberValidationMessages,
memberSocialMessages, memberSocialMessages,
liveRouletteMessages,
liveTagMessages,
memberProviderMessages, memberProviderMessages,
memberGenderMessages memberGenderMessages
) )

View File

@@ -24,7 +24,7 @@ class NewRouletteController(private val service: NewRouletteService) {
@RequestParam creatorId: Long, @RequestParam creatorId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.getAllRoulette(creatorId = creatorId, memberId = member.id!!)) ApiResponse.ok(service.getAllRoulette(creatorId = creatorId, memberId = member.id!!))
} }
@@ -36,7 +36,7 @@ class NewRouletteController(private val service: NewRouletteService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null || member.role != MemberRole.CREATOR) { if (member == null || member.role != MemberRole.CREATOR) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
ApiResponse.ok(service.createRoulette(memberId = member.id!!, request = request)) ApiResponse.ok(service.createRoulette(memberId = member.id!!, request = request))
@@ -49,7 +49,7 @@ class NewRouletteController(private val service: NewRouletteService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null || member.role != MemberRole.CREATOR) { if (member == null || member.role != MemberRole.CREATOR) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
ApiResponse.ok(service.updateRoulette(memberId = member.id!!, request = request)) ApiResponse.ok(service.updateRoulette(memberId = member.id!!, request = request))
@@ -60,7 +60,7 @@ class NewRouletteController(private val service: NewRouletteService) {
@RequestParam creatorId: Long, @RequestParam creatorId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
): ApiResponse<GetRouletteResponse> { ): ApiResponse<GetRouletteResponse> {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
return ApiResponse.ok(service.getRoulette(creatorId = creatorId, memberId = member.id!!)) return ApiResponse.ok(service.getRoulette(creatorId = creatorId, memberId = member.id!!))
} }
@@ -70,7 +70,7 @@ class NewRouletteController(private val service: NewRouletteService) {
@RequestBody request: SpinRouletteRequest, @RequestBody request: SpinRouletteRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.spinRoulette(request = request, memberId = member.id!!)) ApiResponse.ok(service.spinRoulette(request = request, memberId = member.id!!))
} }
@@ -80,7 +80,7 @@ class NewRouletteController(private val service: NewRouletteService) {
@PathVariable id: Long, @PathVariable id: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.refundDonation(id, member)) ApiResponse.ok(service.refundDonation(id, member))
} }

View File

@@ -12,6 +12,8 @@ import kr.co.vividnext.sodalive.can.use.CanUsage
import kr.co.vividnext.sodalive.can.use.UseCanCalculateRepository import kr.co.vividnext.sodalive.can.use.UseCanCalculateRepository
import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus
import kr.co.vividnext.sodalive.common.SodaException 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.live.room.LiveRoomRepository import kr.co.vividnext.sodalive.live.room.LiveRoomRepository
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRepository import kr.co.vividnext.sodalive.member.MemberRepository
@@ -24,6 +26,8 @@ import org.springframework.transaction.annotation.Transactional
class NewRouletteService( class NewRouletteService(
private val idGenerator: RedisIdGenerator, private val idGenerator: RedisIdGenerator,
private val canPaymentService: CanPaymentService, private val canPaymentService: CanPaymentService,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
private val canRepository: CanRepository, private val canRepository: CanRepository,
private val repository: NewRouletteRepository, private val repository: NewRouletteRepository,
@@ -33,7 +37,7 @@ class NewRouletteService(
private val useCanCalculateRepository: UseCanCalculateRepository private val useCanCalculateRepository: UseCanCalculateRepository
) { ) {
fun getAllRoulette(creatorId: Long, memberId: Long): List<GetNewRouletteResponse> { fun getAllRoulette(creatorId: Long, memberId: Long): List<GetNewRouletteResponse> {
if (creatorId != memberId) throw SodaException("잘못된 요청입니다.") if (creatorId != memberId) throw SodaException(messageKey = "common.error.invalid_request")
val rouletteList = repository.findByCreatorId(creatorId) val rouletteList = repository.findByCreatorId(creatorId)
@@ -77,7 +81,7 @@ class NewRouletteService(
val rouletteList = repository.findByCreatorId(creatorId = memberId) val rouletteList = repository.findByCreatorId(creatorId = memberId)
if (rouletteList.isEmpty()) { if (rouletteList.isEmpty()) {
throw SodaException("잘못된 요청입니다.") throw SodaException(messageKey = "common.error.invalid_request")
} }
var isActive = false var isActive = false
@@ -104,7 +108,7 @@ class NewRouletteService(
val rouletteList = repository.findByCreatorId(creatorId = creatorId) val rouletteList = repository.findByCreatorId(creatorId = creatorId)
if (rouletteList.isEmpty()) { if (rouletteList.isEmpty()) {
throw SodaException("룰렛을 사용할 수 없습니다.") throw SodaException(messageKey = "live.roulette.unavailable")
} }
var activeRoulette: NewRoulette? = null var activeRoulette: NewRoulette? = null
@@ -116,7 +120,7 @@ class NewRouletteService(
} }
if (activeRoulette == null || activeRoulette.items.isEmpty()) { if (activeRoulette == null || activeRoulette.items.isEmpty()) {
throw SodaException("룰렛을 사용할 수 없습니다.") throw SodaException(messageKey = "live.roulette.unavailable")
} }
return GetRouletteResponse( return GetRouletteResponse(
@@ -130,19 +134,19 @@ class NewRouletteService(
fun spinRoulette(request: SpinRouletteRequest, memberId: Long): GetRouletteResponse { fun spinRoulette(request: SpinRouletteRequest, memberId: Long): GetRouletteResponse {
// STEP 1 - 라이브 정보 가져오기 // STEP 1 - 라이브 정보 가져오기
val room = roomRepository.findByIdOrNull(request.roomId) val room = roomRepository.findByIdOrNull(request.roomId)
?: throw SodaException("해당하는 라이브가 없습니다.") ?: throw SodaException(messageKey = "live.roulette.live_not_found")
val host = room.member ?: throw SodaException("잘못된 요청입니다.") val host = room.member ?: throw SodaException(messageKey = "common.error.invalid_request")
if (host.role != MemberRole.CREATOR) { if (host.role != MemberRole.CREATOR) {
throw SodaException("주식회사 소다라이브와 계약한\n크리에이터의 룰렛만 사용하실 수 있습니다.") throw SodaException(messageKey = "live.roulette.creator_contract_only")
} }
// STEP 2 - 룰렛 데이터 가져오기 // STEP 2 - 룰렛 데이터 가져오기
val rouletteList = repository.findByCreatorId(creatorId = host.id!!) val rouletteList = repository.findByCreatorId(creatorId = host.id!!)
if (rouletteList.isEmpty()) { if (rouletteList.isEmpty()) {
throw SodaException("룰렛을 사용할 수 없습니다.") throw SodaException(messageKey = "live.roulette.unavailable")
} }
var activeRoulette: NewRoulette? = null var activeRoulette: NewRoulette? = null
@@ -154,7 +158,7 @@ class NewRouletteService(
} }
if (activeRoulette == null || activeRoulette.items.isEmpty()) { if (activeRoulette == null || activeRoulette.items.isEmpty()) {
throw SodaException("룰렛을 사용할 수 없습니다.") throw SodaException(messageKey = "live.roulette.unavailable")
} }
// STEP 3 - 캔 사용 // STEP 3 - 캔 사용
@@ -176,20 +180,23 @@ class NewRouletteService(
@Transactional @Transactional
fun refundDonation(roomId: Long, member: Member) { fun refundDonation(roomId: Long, member: Member) {
val donator = memberRepository.findByIdOrNull(member.id) val donator = memberRepository.findByIdOrNull(member.id)
?: throw SodaException("룰렛 돌리기에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요.") ?: throw SodaException(messageKey = "live.roulette.refund_failed")
val useCan = canRepository.getCanUsedForLiveRoomNotRefund( val useCan = canRepository.getCanUsedForLiveRoomNotRefund(
memberId = member.id!!, memberId = member.id!!,
roomId = roomId, roomId = roomId,
canUsage = CanUsage.SPIN_ROULETTE canUsage = CanUsage.SPIN_ROULETTE
) ?: throw SodaException("룰렛 돌리기에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요.") ) ?: throw SodaException(messageKey = "live.roulette.refund_failed")
useCan.isRefund = true useCan.isRefund = true
val useCanCalculates = useCanCalculateRepository.findByUseCanIdAndStatus(useCan.id!!) val useCanCalculates = useCanCalculateRepository.findByUseCanIdAndStatus(useCan.id!!)
useCanCalculates.forEach { useCanCalculates.forEach {
it.status = UseCanCalculateStatus.REFUND it.status = UseCanCalculateStatus.REFUND
val charge = Charge(0, it.can, status = ChargeStatus.REFUND_CHARGE) val charge = Charge(0, it.can, status = ChargeStatus.REFUND_CHARGE)
charge.title = "${it.can}" val canTitleTemplate = messageSource
.getMessage("live.roulette.can_title", langContext.lang)
.orEmpty()
charge.title = String.format(canTitleTemplate, it.can)
charge.useCan = useCan charge.useCan = useCan
when (it.paymentGateway) { when (it.paymentGateway) {
@@ -203,7 +210,7 @@ class NewRouletteService(
status = PaymentStatus.COMPLETE, status = PaymentStatus.COMPLETE,
paymentGateway = it.paymentGateway paymentGateway = it.paymentGateway
) )
payment.method = "룰렛 환불" payment.method = messageSource.getMessage("live.roulette.refund_method", langContext.lang).orEmpty()
charge.payment = payment charge.payment = payment
chargeRepository.save(charge) chargeRepository.save(charge)
@@ -212,11 +219,11 @@ class NewRouletteService(
private fun rouletteValidate(can: Int, items: List<RouletteItem>) { private fun rouletteValidate(can: Int, items: List<RouletteItem>) {
if (can < 5) { if (can < 5) {
throw SodaException("룰렛 금액은 최소 5캔 입니다.") throw SodaException(messageKey = "live.roulette.min_can")
} }
if (items.size < 2 || items.size > 10) { if (items.size < 2 || items.size > 10) {
throw SodaException("룰렛 옵션은 최소 2개, 최대 10개까지 설정할 수 있습니다.") throw SodaException(messageKey = "live.roulette.item_count_range")
} }
} }

View File

@@ -24,7 +24,7 @@ class RouletteController(private val service: RouletteService) {
@RequestParam creatorId: Long, @RequestParam creatorId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.getAllRoulette(creatorId = creatorId, memberId = member.id!!)) ApiResponse.ok(service.getAllRoulette(creatorId = creatorId, memberId = member.id!!))
} }
@@ -36,7 +36,7 @@ class RouletteController(private val service: RouletteService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null || member.role != MemberRole.CREATOR) { if (member == null || member.role != MemberRole.CREATOR) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
ApiResponse.ok(service.createRoulette(memberId = member.id!!, request = request)) ApiResponse.ok(service.createRoulette(memberId = member.id!!, request = request))
@@ -49,7 +49,7 @@ class RouletteController(private val service: RouletteService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null || member.role != MemberRole.CREATOR) { if (member == null || member.role != MemberRole.CREATOR) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
ApiResponse.ok(service.updateRoulette(memberId = member.id!!, request = request)) ApiResponse.ok(service.updateRoulette(memberId = member.id!!, request = request))
@@ -60,7 +60,7 @@ class RouletteController(private val service: RouletteService) {
@RequestParam creatorId: Long, @RequestParam creatorId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.getRoulette(creatorId = creatorId, memberId = member.id!!)) ApiResponse.ok(service.getRoulette(creatorId = creatorId, memberId = member.id!!))
} }
@@ -70,7 +70,7 @@ class RouletteController(private val service: RouletteService) {
@RequestBody request: SpinRouletteRequestV2, @RequestBody request: SpinRouletteRequestV2,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.spinRoulette(request = request, member = member)) ApiResponse.ok(service.spinRoulette(request = request, member = member))
} }
@@ -80,7 +80,7 @@ class RouletteController(private val service: RouletteService) {
@PathVariable id: Long, @PathVariable id: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.refundDonation(id, member)) ApiResponse.ok(service.refundDonation(id, member))
} }

View File

@@ -12,6 +12,8 @@ import kr.co.vividnext.sodalive.can.use.CanUsage
import kr.co.vividnext.sodalive.can.use.UseCanCalculateRepository import kr.co.vividnext.sodalive.can.use.UseCanCalculateRepository
import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus
import kr.co.vividnext.sodalive.common.SodaException 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.live.room.LiveRoomRepository import kr.co.vividnext.sodalive.live.room.LiveRoomRepository
import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfoRedisRepository import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfoRedisRepository
import kr.co.vividnext.sodalive.live.roulette.NewRoulette import kr.co.vividnext.sodalive.live.roulette.NewRoulette
@@ -31,6 +33,8 @@ import kotlin.random.Random
class RouletteService( class RouletteService(
private val idGenerator: RedisIdGenerator, private val idGenerator: RedisIdGenerator,
private val canPaymentService: CanPaymentService, private val canPaymentService: CanPaymentService,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
private val canRepository: CanRepository, private val canRepository: CanRepository,
private val repository: RouletteRepository, private val repository: RouletteRepository,
@@ -43,7 +47,7 @@ class RouletteService(
private val tokenLocks: MutableMap<Long, ReentrantReadWriteLock> = mutableMapOf() private val tokenLocks: MutableMap<Long, ReentrantReadWriteLock> = mutableMapOf()
fun getAllRoulette(creatorId: Long, memberId: Long): List<GetRouletteResponseV2> { fun getAllRoulette(creatorId: Long, memberId: Long): List<GetRouletteResponseV2> {
if (creatorId != memberId) throw SodaException("잘못된 요청입니다.") if (creatorId != memberId) throw SodaException(messageKey = "common.error.invalid_request")
return repository.findByCreatorId(creatorId) return repository.findByCreatorId(creatorId)
.sortedBy { it.id } .sortedBy { it.id }
@@ -88,7 +92,7 @@ class RouletteService(
val rouletteList = repository.findByCreatorId(creatorId = memberId) val rouletteList = repository.findByCreatorId(creatorId = memberId)
if (rouletteList.isEmpty()) { if (rouletteList.isEmpty()) {
throw SodaException("잘못된 요청입니다.") throw SodaException(messageKey = "common.error.invalid_request")
} }
var activeRoulette = false var activeRoulette = false
@@ -119,7 +123,7 @@ class RouletteService(
.map { GetRouletteResponseV2(it.id, it.can, it.isActive, it.items) } .map { GetRouletteResponseV2(it.id, it.can, it.isActive, it.items) }
if (rouletteList.isEmpty()) { if (rouletteList.isEmpty()) {
throw SodaException("룰렛을 사용할 수 없습니다.") throw SodaException(messageKey = "live.roulette.unavailable")
} }
return rouletteList return rouletteList
@@ -129,19 +133,19 @@ class RouletteService(
fun spinRoulette(request: SpinRouletteRequestV2, member: Member): SpinRouletteResponse { fun spinRoulette(request: SpinRouletteRequestV2, member: Member): SpinRouletteResponse {
// STEP 1 - 라이브 정보 가져오기 // STEP 1 - 라이브 정보 가져오기
val room = roomRepository.findByIdOrNull(request.roomId) val room = roomRepository.findByIdOrNull(request.roomId)
?: throw SodaException("해당하는 라이브가 없습니다.") ?: throw SodaException(messageKey = "live.roulette.live_not_found")
val host = room.member ?: throw SodaException("잘못된 요청입니다.") val host = room.member ?: throw SodaException(messageKey = "common.error.invalid_request")
if (host.role != MemberRole.CREATOR) { if (host.role != MemberRole.CREATOR) {
throw SodaException("주식회사 소다라이브와 계약한\n크리에이터의 룰렛만 사용하실 수 있습니다.") throw SodaException(messageKey = "live.roulette.creator_contract_only")
} }
// STEP 2 - 룰렛 데이터 가져오기 // STEP 2 - 룰렛 데이터 가져오기
val roulette = repository.findByIdOrNull(id = request.rouletteId) val roulette = repository.findByIdOrNull(id = request.rouletteId)
if (roulette == null || roulette.items.isEmpty()) { if (roulette == null || roulette.items.isEmpty()) {
throw SodaException("룰렛을 사용할 수 없습니다.") throw SodaException(messageKey = "live.roulette.unavailable")
} }
// STEP 3 - 캔 사용 // STEP 3 - 캔 사용
@@ -159,12 +163,15 @@ class RouletteService(
val lock = getOrCreateLock(memberId = member.id!!) val lock = getOrCreateLock(memberId = member.id!!)
lock.write { lock.write {
val roomInfo = roomInfoRepository.findByIdOrNull(room.id!!) val roomInfo = roomInfoRepository.findByIdOrNull(room.id!!)
?: throw SodaException("해당하는 라이브의 정보가 없습니다.") ?: throw SodaException(messageKey = "live.roulette.live_info_not_found")
val messageTemplate = messageSource
.getMessage("live.roulette.result_message", langContext.lang)
.orEmpty()
roomInfo.addRouletteMessage( roomInfo.addRouletteMessage(
memberId = member.id!!, memberId = member.id!!,
nickname = member.nickname, nickname = member.nickname,
donationMessage = "[$result] 당첨!" donationMessage = String.format(messageTemplate, result)
) )
roomInfoRepository.save(roomInfo) roomInfoRepository.save(roomInfo)
@@ -176,20 +183,23 @@ class RouletteService(
@Transactional @Transactional
fun refundDonation(roomId: Long, member: Member) { fun refundDonation(roomId: Long, member: Member) {
val donator = memberRepository.findByIdOrNull(member.id) val donator = memberRepository.findByIdOrNull(member.id)
?: throw SodaException("룰렛 돌리기에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요.") ?: throw SodaException(messageKey = "live.roulette.refund_failed")
val useCan = canRepository.getCanUsedForLiveRoomNotRefund( val useCan = canRepository.getCanUsedForLiveRoomNotRefund(
memberId = member.id!!, memberId = member.id!!,
roomId = roomId, roomId = roomId,
canUsage = CanUsage.SPIN_ROULETTE canUsage = CanUsage.SPIN_ROULETTE
) ?: throw SodaException("룰렛 돌리기에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요.") ) ?: throw SodaException(messageKey = "live.roulette.refund_failed")
useCan.isRefund = true useCan.isRefund = true
val useCanCalculates = useCanCalculateRepository.findByUseCanIdAndStatus(useCan.id!!) val useCanCalculates = useCanCalculateRepository.findByUseCanIdAndStatus(useCan.id!!)
useCanCalculates.forEach { useCanCalculates.forEach {
it.status = UseCanCalculateStatus.REFUND it.status = UseCanCalculateStatus.REFUND
val charge = Charge(0, it.can, status = ChargeStatus.REFUND_CHARGE) val charge = Charge(0, it.can, status = ChargeStatus.REFUND_CHARGE)
charge.title = "${it.can}" val canTitleTemplate = messageSource
.getMessage("live.roulette.can_title", langContext.lang)
.orEmpty()
charge.title = String.format(canTitleTemplate, it.can)
charge.useCan = useCan charge.useCan = useCan
when (it.paymentGateway) { when (it.paymentGateway) {
@@ -203,7 +213,7 @@ class RouletteService(
status = PaymentStatus.COMPLETE, status = PaymentStatus.COMPLETE,
paymentGateway = it.paymentGateway paymentGateway = it.paymentGateway
) )
payment.method = "룰렛 환불" payment.method = messageSource.getMessage("live.roulette.refund_method", langContext.lang).orEmpty()
charge.payment = payment charge.payment = payment
chargeRepository.save(charge) chargeRepository.save(charge)
@@ -234,16 +244,16 @@ class RouletteService(
private fun rouletteValidate(can: Int, items: List<RouletteItem>) { private fun rouletteValidate(can: Int, items: List<RouletteItem>) {
if (can < 5) { if (can < 5) {
throw SodaException("룰렛 금액은 최소 5캔 입니다.") throw SodaException(messageKey = "live.roulette.min_can")
} }
if (items.size < 2 || items.size > 10) { if (items.size < 2 || items.size > 10) {
throw SodaException("룰렛 옵션은 최소 2개, 최대 10개까지 설정할 수 있습니다.") throw SodaException(messageKey = "live.roulette.item_count_range")
} }
val totalPercentage = items.map { it.percentage }.sum() val totalPercentage = items.map { it.percentage }.sum()
if (totalPercentage > 100.1f || totalPercentage <= 99.99f) { if (totalPercentage > 100.1f || totalPercentage <= 99.99f) {
throw SodaException("확률이 100%가 아닙니다") throw SodaException(messageKey = "live.roulette.probability_invalid")
} }
} }

View File

@@ -3,6 +3,8 @@ package kr.co.vividnext.sodalive.live.tag
import kr.co.vividnext.sodalive.admin.member.tag.UpdateTagOrdersRequest import kr.co.vividnext.sodalive.admin.member.tag.UpdateTagOrdersRequest
import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException 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.Member import kr.co.vividnext.sodalive.member.Member
import org.springframework.security.access.prepost.PreAuthorize import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.annotation.AuthenticationPrincipal
@@ -19,17 +21,27 @@ import org.springframework.web.multipart.MultipartFile
@RestController @RestController
@RequestMapping("/live/tag") @RequestMapping("/live/tag")
class LiveTagController(private val service: LiveTagService) { class LiveTagController(
private val service: LiveTagService,
private val messageSource: SodaMessageSource,
private val langContext: LangContext
) {
@PostMapping @PostMapping
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
fun enrollmentLiveTag( fun enrollmentLiveTag(
@RequestPart("image") image: MultipartFile, @RequestPart("image") image: MultipartFile,
@RequestPart("request") requestString: String @RequestPart("request") requestString: String
) = ApiResponse.ok(service.enrollmentLiveTag(image, requestString), "등록되었습니다.") ) = ApiResponse.ok(
service.enrollmentLiveTag(image, requestString),
messageSource.getMessage("live.tag.registered", langContext.lang)
)
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
fun deleteLiveTag(@PathVariable id: Long) = ApiResponse.ok(service.deleteTag(id), "삭제되었습니다.") fun deleteLiveTag(@PathVariable id: Long) = ApiResponse.ok(
service.deleteTag(id),
messageSource.getMessage("live.tag.deleted", langContext.lang)
)
@PutMapping("/{id}") @PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@@ -37,20 +49,26 @@ class LiveTagController(private val service: LiveTagService) {
@PathVariable id: Long, @PathVariable id: Long,
@RequestPart("image") image: MultipartFile?, @RequestPart("image") image: MultipartFile?,
@RequestPart("request") requestString: String @RequestPart("request") requestString: String
) = ApiResponse.ok(service.modifyTag(id, image, requestString), "수정되었습니다.") ) = ApiResponse.ok(
service.modifyTag(id, image, requestString),
messageSource.getMessage("live.tag.updated", langContext.lang)
)
@PutMapping("/orders") @PutMapping("/orders")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
fun updateTagOrders( fun updateTagOrders(
@RequestBody request: UpdateTagOrdersRequest @RequestBody request: UpdateTagOrdersRequest
) = ApiResponse.ok(service.updateTagOrders(request.ids), "수정되었습니다.") ) = ApiResponse.ok(
service.updateTagOrders(request.ids),
messageSource.getMessage("live.tag.updated", langContext.lang)
)
@GetMapping @GetMapping
fun getTags( fun getTags(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) { if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
ApiResponse.ok(service.getTags(member)) ApiResponse.ok(service.getTags(member))

View File

@@ -48,7 +48,7 @@ class LiveTagService(
@Transactional @Transactional
fun deleteTag(id: Long) { fun deleteTag(id: Long) {
val tag = repository.findByIdOrNull(id) val tag = repository.findByIdOrNull(id)
?: throw SodaException("잘못된 요청입니다.") ?: throw SodaException(messageKey = "common.error.invalid_request")
tag.tag = "${tag.tag}_deleted" tag.tag = "${tag.tag}_deleted"
tag.isActive = false tag.isActive = false
@@ -57,7 +57,7 @@ class LiveTagService(
@Transactional @Transactional
fun modifyTag(id: Long, image: MultipartFile?, requestString: String) { fun modifyTag(id: Long, image: MultipartFile?, requestString: String) {
val tag = repository.findByIdOrNull(id) val tag = repository.findByIdOrNull(id)
?: throw SodaException("잘못된 요청입니다.") ?: throw SodaException(messageKey = "common.error.invalid_request")
val request = objectMapper.readValue(requestString, CreateLiveTagRequest::class.java) val request = objectMapper.readValue(requestString, CreateLiveTagRequest::class.java)
tag.tag = request.tag tag.tag = request.tag
@@ -95,6 +95,8 @@ class LiveTagService(
} }
fun tagExistCheck(request: CreateLiveTagRequest) { fun tagExistCheck(request: CreateLiveTagRequest) {
repository.findByTag(request.tag)?.let { throw SodaException("이미 등록된 태그 입니다.") } repository.findByTag(request.tag)?.let {
throw SodaException(messageKey = "live.tag.duplicate")
}
} }
} }