231 lines
7.5 KiB
Kotlin
231 lines
7.5 KiB
Kotlin
package kr.co.vividnext.sodalive.fcm
|
|
|
|
import com.google.firebase.messaging.AndroidConfig
|
|
import com.google.firebase.messaging.ApnsConfig
|
|
import com.google.firebase.messaging.Aps
|
|
import com.google.firebase.messaging.FirebaseMessaging
|
|
import com.google.firebase.messaging.MessagingErrorCode
|
|
import com.google.firebase.messaging.MulticastMessage
|
|
import com.google.firebase.messaging.Notification
|
|
import org.slf4j.LoggerFactory
|
|
import org.springframework.beans.factory.annotation.Value
|
|
import org.springframework.scheduling.annotation.Async
|
|
import org.springframework.stereotype.Service
|
|
|
|
@Service
|
|
class FcmService(
|
|
private val pushTokenService: PushTokenService,
|
|
@Value("\${server.env}")
|
|
private val serverEnv: String
|
|
) {
|
|
private val logger = LoggerFactory.getLogger(this::class.java)
|
|
|
|
@Async
|
|
fun send(
|
|
tokens: List<String>,
|
|
title: String,
|
|
message: String,
|
|
container: String,
|
|
roomId: Long? = null,
|
|
messageId: Long? = null,
|
|
contentId: Long? = null,
|
|
creatorId: Long? = null,
|
|
auditionId: Long? = null,
|
|
deepLinkValue: FcmDeepLinkValue? = null,
|
|
deepLinkId: Long? = null,
|
|
deepLinkCommentPostId: Long? = null
|
|
) {
|
|
if (tokens.isEmpty()) return
|
|
logger.info("os: $container")
|
|
|
|
var targets = tokens
|
|
val maxAttempts = 3
|
|
var attempt = 1
|
|
|
|
while (attempt <= maxAttempts && targets.isNotEmpty()) {
|
|
val multicastMessage = MulticastMessage.builder()
|
|
.addAllTokens(targets)
|
|
|
|
multicastMessage.setAndroidConfig(
|
|
AndroidConfig.builder()
|
|
.setPriority(AndroidConfig.Priority.HIGH)
|
|
.build()
|
|
)
|
|
|
|
multicastMessage.setApnsConfig(
|
|
ApnsConfig.builder()
|
|
.setAps(
|
|
Aps.builder()
|
|
.setSound("default")
|
|
.build()
|
|
)
|
|
.build()
|
|
)
|
|
|
|
multicastMessage
|
|
.setNotification(
|
|
Notification.builder()
|
|
.setTitle(title)
|
|
.setBody(message)
|
|
.build()
|
|
)
|
|
|
|
if (roomId != null) {
|
|
multicastMessage.putData("room_id", roomId.toString())
|
|
}
|
|
|
|
if (messageId != null) {
|
|
multicastMessage.putData("message_id", messageId.toString())
|
|
}
|
|
|
|
if (contentId != null) {
|
|
multicastMessage.putData("content_id", contentId.toString())
|
|
}
|
|
|
|
if (creatorId != null) {
|
|
multicastMessage.putData("channel_id", creatorId.toString())
|
|
}
|
|
|
|
if (auditionId != null) {
|
|
multicastMessage.putData("audition_id", auditionId.toString())
|
|
}
|
|
|
|
val deepLink = createDeepLink(deepLinkValue, deepLinkId, deepLinkCommentPostId)
|
|
if (deepLink != null) {
|
|
multicastMessage.putData("deep_link", deepLink)
|
|
}
|
|
|
|
val response = FirebaseMessaging.getInstance().sendEachForMulticast(multicastMessage.build())
|
|
val failedTokens = mutableListOf<String>()
|
|
|
|
response.responses.forEachIndexed { index, res ->
|
|
if (!res.isSuccessful) {
|
|
val exception = res.exception
|
|
val token = targets[index]
|
|
|
|
if (exception?.messagingErrorCode == MessagingErrorCode.UNREGISTERED) {
|
|
logger.error("[FCM] ❌ UNREGISTERED → $token")
|
|
// DB에서 삭제
|
|
pushTokenService.unregisterInvalidToken(token)
|
|
} else {
|
|
logger.error("[FCM] ❌ 실패: $token / ${exception?.messagingErrorCode}")
|
|
failedTokens.add(token)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (failedTokens.isEmpty()) {
|
|
logger.info("[FCM] ✅ 전체 전송 성공")
|
|
return
|
|
}
|
|
|
|
targets = failedTokens
|
|
attempt++
|
|
}
|
|
|
|
if (targets.isNotEmpty()) {
|
|
logger.error("[FCM] ❌ 최종 실패 대상 ${targets.size}명 → $targets")
|
|
}
|
|
}
|
|
|
|
private fun createDeepLink(
|
|
deepLinkValue: FcmDeepLinkValue?,
|
|
deepLinkId: Long?,
|
|
deepLinkCommentPostId: Long?
|
|
): String? {
|
|
return buildDeepLink(serverEnv, deepLinkValue, deepLinkId, deepLinkCommentPostId)
|
|
}
|
|
|
|
fun sendPointGranted(tokens: List<String>, point: Int) {
|
|
if (tokens.isEmpty()) return
|
|
val data = mapOf(
|
|
"type" to "POINT_GRANTED",
|
|
"point" to point.toString(),
|
|
"message" to "${point}포인트가 지급되었습니다!"
|
|
)
|
|
|
|
var targets = tokens
|
|
var attempts = 0
|
|
val maxAttempts = 3
|
|
|
|
while (attempts <= maxAttempts && targets.isNotEmpty()) {
|
|
val multicastMessage = MulticastMessage.builder()
|
|
.addAllTokens(targets)
|
|
.putAllData(data)
|
|
|
|
multicastMessage.setAndroidConfig(
|
|
AndroidConfig.builder()
|
|
.setPriority(AndroidConfig.Priority.HIGH)
|
|
.build()
|
|
)
|
|
|
|
multicastMessage.setApnsConfig(
|
|
ApnsConfig.builder()
|
|
.setAps(
|
|
Aps.builder()
|
|
.setSound("default")
|
|
.build()
|
|
)
|
|
.build()
|
|
)
|
|
|
|
val response = FirebaseMessaging.getInstance().sendEachForMulticast(multicastMessage.build())
|
|
val failedTokens = mutableListOf<String>()
|
|
|
|
response.responses.forEachIndexed { index, res ->
|
|
if (!res.isSuccessful) {
|
|
val exception = res.exception
|
|
val token = targets[index]
|
|
|
|
if (exception?.messagingErrorCode == MessagingErrorCode.UNREGISTERED) {
|
|
logger.error("[FCM] ❌ UNREGISTERED → $token")
|
|
// DB에서 삭제
|
|
pushTokenService.unregisterInvalidToken(token)
|
|
} else {
|
|
logger.error("[FCM] ❌ 실패: $token / ${exception?.messagingErrorCode}")
|
|
failedTokens.add(token)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (failedTokens.isEmpty()) {
|
|
logger.info("[FCM] ✅ 전체 전송 성공")
|
|
return
|
|
}
|
|
|
|
targets = failedTokens
|
|
attempts++
|
|
}
|
|
|
|
if (targets.isNotEmpty()) {
|
|
logger.error("[FCM] ❌ 최종 실패 대상 ${targets.size}명 → $targets")
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
fun buildDeepLink(
|
|
serverEnv: String,
|
|
deepLinkValue: FcmDeepLinkValue?,
|
|
deepLinkId: Long?,
|
|
deepLinkCommentPostId: Long? = null
|
|
): String? {
|
|
if (deepLinkValue == null || deepLinkId == null) {
|
|
return null
|
|
}
|
|
|
|
val uriScheme = if (serverEnv.equals("voiceon", ignoreCase = true)) {
|
|
"voiceon"
|
|
} else {
|
|
"voiceon-test"
|
|
}
|
|
|
|
val baseDeepLink = "$uriScheme://${deepLinkValue.value}/$deepLinkId"
|
|
if (deepLinkValue == FcmDeepLinkValue.COMMUNITY && deepLinkCommentPostId != null) {
|
|
return "$baseDeepLink?postId=$deepLinkCommentPostId"
|
|
}
|
|
|
|
return baseDeepLink
|
|
}
|
|
}
|
|
}
|