푸시 알림 전송 언어 처리

This commit is contained in:
2026-01-15 17:21:22 +09:00
parent 9dc23f0622
commit ed2660adc6
18 changed files with 258 additions and 503 deletions

View File

@@ -33,17 +33,6 @@ class FcmController(private val applicationEventPublisher: ApplicationEventPubli
type = FcmEventType.ALL,
title = request.title,
message = request.message,
container = "ios",
isAuth = request.isAuth
)
)
applicationEventPublisher.publishEvent(
FcmEvent(
type = FcmEventType.ALL,
title = request.title,
message = request.message,
container = "aos",
isAuth = request.isAuth
)
)

View File

@@ -1,6 +1,8 @@
package kr.co.vividnext.sodalive.fcm
import kr.co.vividnext.sodalive.content.comment.AudioContentCommentRepository
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.MemberRepository
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Component
@@ -15,11 +17,14 @@ enum class FcmEventType {
class FcmEvent(
val type: FcmEventType,
val title: String,
val message: String,
val title: String = "",
val message: String = "",
val titleKey: String? = null,
val messageKey: String? = null,
val args: List<Any> = listOf(),
val container: String = "",
val recipients: List<Long> = listOf(),
val recipientsMap: Map<String, List<List<String>>>? = null,
val pushTokens: List<PushTokenInfo>? = null,
val isAuth: Boolean? = null,
val roomId: Long? = null,
val contentId: Long? = null,
@@ -35,7 +40,8 @@ class FcmEvent(
class FcmSendListener(
private val pushService: FcmService,
private val memberRepository: MemberRepository,
private val contentCommentRepository: AudioContentCommentRepository
private val contentCommentRepository: AudioContentCommentRepository,
private val messageSource: SodaMessageSource
) {
@Async
@TransactionalEventListener
@@ -43,21 +49,10 @@ class FcmSendListener(
fun send(fcmEvent: FcmEvent) {
when (fcmEvent.type) {
FcmEventType.ALL -> {
if (fcmEvent.container.isNotBlank()) {
val pushTokens = memberRepository.getAllRecipientPushTokens(
fcmEvent.isAuth,
fcmEvent.container
)
for (tokens in pushTokens) {
pushService.send(
tokens = tokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = fcmEvent.container
)
}
}
val pushTokens = memberRepository.getAllRecipientPushTokens(
fcmEvent.isAuth
)
sendPush(pushTokens, fcmEvent)
}
FcmEventType.INDIVIDUAL -> {
@@ -66,254 +61,117 @@ class FcmSendListener(
recipients = fcmEvent.recipients,
isAuth = fcmEvent.isAuth
)
val iosPushTokens = pushTokens["ios"]
val aosPushToken = pushTokens["aos"]
if (iosPushTokens != null) {
for (tokens in iosPushTokens) {
pushService.send(
tokens = tokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = "ios",
contentId = fcmEvent.contentId
)
}
}
if (aosPushToken != null) {
for (tokens in aosPushToken) {
pushService.send(
tokens = tokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = "aos",
contentId = fcmEvent.contentId
)
}
}
sendPush(pushTokens, fcmEvent)
}
}
FcmEventType.CREATE_LIVE -> {
if (fcmEvent.container.isNotBlank()) {
val pushTokens = memberRepository.getCreateLiveRoomNotificationRecipientPushTokens(
creatorId = fcmEvent.creatorId!!,
isAuth = fcmEvent.isAuth ?: false,
isAvailableJoinCreator = fcmEvent.isAvailableJoinCreator ?: false,
container = fcmEvent.container
)
for (tokens in pushTokens) {
pushService.send(
tokens = tokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = fcmEvent.container,
roomId = fcmEvent.roomId
)
}
}
val pushTokens = memberRepository.getCreateLiveRoomNotificationRecipientPushTokens(
creatorId = fcmEvent.creatorId!!,
isAuth = fcmEvent.isAuth ?: false,
isAvailableJoinCreator = fcmEvent.isAvailableJoinCreator ?: false
)
sendPush(pushTokens, fcmEvent, roomId = fcmEvent.roomId)
}
FcmEventType.START_LIVE -> {
if (fcmEvent.container.isNotBlank()) {
val pushTokens = memberRepository.getStartLiveRoomNotificationRecipientPushTokens(
creatorId = fcmEvent.creatorId!!,
roomId = fcmEvent.roomId!!,
isAuth = fcmEvent.isAuth ?: false,
isAvailableJoinCreator = fcmEvent.isAvailableJoinCreator ?: false,
container = fcmEvent.container
)
for (tokens in pushTokens) {
pushService.send(
tokens = tokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = fcmEvent.container,
roomId = fcmEvent.roomId
)
}
}
val pushTokens = memberRepository.getStartLiveRoomNotificationRecipientPushTokens(
creatorId = fcmEvent.creatorId!!,
roomId = fcmEvent.roomId!!,
isAuth = fcmEvent.isAuth ?: false,
isAvailableJoinCreator = fcmEvent.isAvailableJoinCreator ?: false
)
sendPush(pushTokens, fcmEvent, roomId = fcmEvent.roomId)
}
FcmEventType.CANCEL_LIVE -> {
if (fcmEvent.recipientsMap != null) {
val iosPushTokens = fcmEvent.recipientsMap["ios"]
val aosPushToken = fcmEvent.recipientsMap["aos"]
if (iosPushTokens != null) {
for (tokens in iosPushTokens) {
pushService.send(
tokens = tokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = "ios"
)
}
}
if (aosPushToken != null) {
for (tokens in aosPushToken) {
pushService.send(
tokens = tokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = "aos"
)
}
}
if (fcmEvent.pushTokens != null) {
sendPush(fcmEvent.pushTokens, fcmEvent)
}
}
FcmEventType.UPLOAD_CONTENT -> {
if (fcmEvent.container.isNotBlank()) {
val pushTokens = memberRepository.getUploadContentNotificationRecipientPushTokens(
creatorId = fcmEvent.creatorId!!,
isAuth = fcmEvent.isAuth ?: false,
container = fcmEvent.container
)
for (tokens in pushTokens) {
pushService.send(
tokens = tokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = fcmEvent.container,
contentId = fcmEvent.contentId
)
}
}
val pushTokens = memberRepository.getUploadContentNotificationRecipientPushTokens(
creatorId = fcmEvent.creatorId!!,
isAuth = fcmEvent.isAuth ?: false
)
sendPush(pushTokens, fcmEvent, contentId = fcmEvent.contentId)
}
FcmEventType.SEND_MESSAGE -> {
val response = memberRepository.getMessageRecipientPushToken(messageId = fcmEvent.messageId!!)
if (response != null) {
pushService.send(
tokens = listOf(response.pushToken),
title = fcmEvent.title,
message = fcmEvent.message,
container = response.container,
messageId = fcmEvent.messageId
)
val pushToken = memberRepository.getMessageRecipientPushToken(messageId = fcmEvent.messageId!!)
if (pushToken != null) {
sendPush(listOf(pushToken), fcmEvent, messageId = fcmEvent.messageId)
}
}
FcmEventType.CHANGE_NOTICE -> {
if (fcmEvent.creatorId != null) {
val pushTokenList = memberRepository.getChangeNoticeRecipientPushTokens(fcmEvent.creatorId)
val iosPushTokens = pushTokenList["ios"]
val aosPushToken = pushTokenList["aos"]
if (iosPushTokens != null) {
for (tokens in iosPushTokens) {
pushService.send(
tokens = tokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = "ios",
creatorId = fcmEvent.creatorId
)
}
}
if (aosPushToken != null) {
for (tokens in aosPushToken) {
pushService.send(
tokens = tokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = "aos",
creatorId = fcmEvent.creatorId
)
}
}
val pushTokens = memberRepository.getChangeNoticeRecipientPushTokens(fcmEvent.creatorId)
sendPush(pushTokens, fcmEvent, creatorId = fcmEvent.creatorId)
}
}
FcmEventType.CREATE_CONTENT_COMMENT -> {
if (fcmEvent.myMemberId != null && fcmEvent.contentId != null) {
val response = contentCommentRepository.findPushTokenByContentIdAndCommentParentIdMyMemberId(
val pushTokens = contentCommentRepository.findPushTokenByContentIdAndCommentParentIdMyMemberId(
contentId = fcmEvent.contentId,
commentParentId = fcmEvent.commentParentId,
myMemberId = fcmEvent.myMemberId
)
val iosPushTokens = response
.asSequence()
.distinct()
.filter { it.pushToken.isNotBlank() }
.filter { it.container == "ios" }
.map { it.pushToken }
.toList()
val aosPushTokens = response
.asSequence()
.distinct()
.filter { it.pushToken.isNotBlank() }
.filter { it.container == "aos" }
.map { it.pushToken }
.toList()
if (iosPushTokens.isNotEmpty()) {
pushService.send(
tokens = iosPushTokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = "ios",
contentId = fcmEvent.contentId
)
}
if (aosPushTokens.isNotEmpty()) {
pushService.send(
tokens = aosPushTokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = "aos",
contentId = fcmEvent.contentId
)
}
sendPush(pushTokens, fcmEvent, contentId = fcmEvent.contentId)
}
}
FcmEventType.IN_PROGRESS_AUDITION -> {
if (fcmEvent.auditionId != null && fcmEvent.auditionId > 0) {
val pushTokenList = memberRepository.getAuditionNoticeRecipientPushTokens(
val pushTokens = memberRepository.getAuditionNoticeRecipientPushTokens(
isAuth = fcmEvent.isAuth ?: false
)
val iosPushTokens = pushTokenList["ios"]
val aosPushToken = pushTokenList["aos"]
if (iosPushTokens != null) {
for (tokens in iosPushTokens) {
pushService.send(
tokens = tokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = "ios",
auditionId = fcmEvent.auditionId
)
}
}
if (aosPushToken != null) {
for (tokens in aosPushToken) {
pushService.send(
tokens = tokens,
title = fcmEvent.title,
message = fcmEvent.message,
container = "aos",
auditionId = fcmEvent.auditionId
)
}
}
sendPush(pushTokens, fcmEvent, auditionId = fcmEvent.auditionId)
}
}
}
}
private fun sendPush(
pushTokens: List<PushTokenInfo>,
fcmEvent: FcmEvent,
roomId: Long? = null,
contentId: Long? = null,
messageId: Long? = null,
creatorId: Long? = null,
auditionId: Long? = null
) {
val tokensByLang = pushTokens.groupBy { it.languageCode }
for ((langCode, tokens) in tokensByLang) {
val lang = Lang.fromAcceptLanguage(langCode)
val title = translate(fcmEvent.titleKey, fcmEvent.title, lang, fcmEvent.args)
val message = translate(fcmEvent.messageKey, fcmEvent.message, lang, fcmEvent.args)
val tokensByOS = tokens.groupBy { it.deviceType }
for ((os, osTokens) in tokensByOS) {
osTokens.map { it.token }.distinct().chunked(500).forEach { batch ->
pushService.send(
tokens = batch,
title = title,
message = message,
container = os,
roomId = roomId ?: fcmEvent.roomId,
contentId = contentId ?: fcmEvent.contentId,
messageId = messageId ?: fcmEvent.messageId,
creatorId = creatorId ?: fcmEvent.creatorId,
auditionId = auditionId ?: fcmEvent.auditionId
)
}
}
}
}
private fun translate(key: String?, default: String, lang: Lang, args: List<Any>): String {
if (key == null) return default
val template = messageSource.getMessage(key, lang) ?: return default
return String.format(template, *args.toTypedArray())
}
}

View File

@@ -1,8 +0,0 @@
package kr.co.vividnext.sodalive.fcm
import com.querydsl.core.annotations.QueryProjection
data class GetMessageRecipientPushTokenResponse @QueryProjection constructor(
val pushToken: String,
val container: String
)

View File

@@ -10,7 +10,8 @@ import javax.persistence.ManyToOne
@Entity
data class PushToken(
var token: String,
var deviceType: String
var deviceType: String,
var languageCode: String? = null
) : BaseEntity() {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = true)

View File

@@ -0,0 +1,9 @@
package kr.co.vividnext.sodalive.fcm
import com.querydsl.core.annotations.QueryProjection
data class PushTokenInfo @QueryProjection constructor(
val token: String,
val deviceType: String,
val languageCode: String
)

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.fcm
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.member.MemberRepository
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
@@ -8,7 +9,8 @@ import org.springframework.transaction.annotation.Transactional
@Service
class PushTokenService(
private val repository: PushTokenRepository,
private val memberRepository: MemberRepository
private val memberRepository: MemberRepository,
private val langContext: LangContext
) {
@Transactional
fun registerToken(memberId: Long, token: String, deviceType: String) {
@@ -20,8 +22,9 @@ class PushTokenService(
existing.member = member
existing.token = token
existing.deviceType = deviceType
existing.languageCode = langContext.lang.code
} else {
val newToken = PushToken(token, deviceType)
val newToken = PushToken(token, deviceType, langContext.lang.code)
newToken.member = member
repository.save(newToken)
}