feat: 푸시 토큰 - 한 사람이 여러개의 디바이스로 로그인 해도 모든 푸시 토큰이 기록되어 있어서 모든 디바이스에 푸시가 가도록 수정

This commit is contained in:
2025-04-28 21:40:20 +09:00
parent 73edc0515f
commit 8a924bd5be
7 changed files with 172 additions and 65 deletions

View File

@@ -14,7 +14,7 @@ import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service
@Service
class FcmService {
class FcmService(private val pushTokenService: PushTokenService) {
private val logger = LoggerFactory.getLogger(this::class.java)
@Async
@@ -100,7 +100,8 @@ class FcmService {
if (exception?.messagingErrorCode == MessagingErrorCode.UNREGISTERED) {
logger.error("[FCM] ❌ UNREGISTERED → $token")
// 필요 시 DB에서 삭제
// DB에서 삭제
pushTokenService.unregisterInvalidToken(token)
} else {
logger.error("[FCM] ❌ 실패: $token / ${exception?.messagingErrorCode}")
failedTokens.add(token)
@@ -148,6 +149,8 @@ class FcmService {
// "registration-token-not-registered" 예외 코드 확인
if (e.messagingErrorCode == MessagingErrorCode.UNREGISTERED) {
logger.error("[FCM] ❌ 실패: 토큰이 등록되지 않음 (등록 해제됨) → 재시도 안함")
// DB에서 삭제
pushTokenService.unregisterInvalidToken(token)
return
}

View File

@@ -0,0 +1,18 @@
package kr.co.vividnext.sodalive.fcm
import kr.co.vividnext.sodalive.common.BaseEntity
import kr.co.vividnext.sodalive.member.Member
import javax.persistence.Entity
import javax.persistence.FetchType
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
@Entity
data class PushToken(
var token: String,
var deviceType: String
) : BaseEntity() {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = true)
var member: Member? = null
}

View File

@@ -0,0 +1,30 @@
package kr.co.vividnext.sodalive.fcm
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.fcm.QPushToken.pushToken
import org.springframework.data.jpa.repository.JpaRepository
interface PushTokenRepository : JpaRepository<PushToken, Long>, PushTokenQueryRepository
interface PushTokenQueryRepository {
fun findByToken(token: String): PushToken?
fun findByMemberId(memberId: Long): List<PushToken>
}
class PushTokenQueryRepositoryImpl(
private val queryFactory: JPAQueryFactory
) : PushTokenQueryRepository {
override fun findByToken(token: String): PushToken? {
return queryFactory
.selectFrom(pushToken)
.where(pushToken.token.eq(token))
.fetchFirst()
}
override fun findByMemberId(memberId: Long): List<PushToken> {
return queryFactory
.selectFrom(pushToken)
.where(pushToken.member.id.eq(memberId))
.fetch()
}
}

View File

@@ -0,0 +1,47 @@
package kr.co.vividnext.sodalive.fcm
import kr.co.vividnext.sodalive.member.MemberRepository
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
class PushTokenService(
private val repository: PushTokenRepository,
private val memberRepository: MemberRepository
) {
@Transactional
fun registerToken(memberId: Long, token: String, deviceType: String) {
val member = memberRepository.findByIdOrNull(memberId)
if (member != null) {
val existing = repository.findByToken(token)
if (existing != null) {
existing.member = member
existing.token = token
existing.deviceType = deviceType
} else {
val newToken = PushToken(token, deviceType)
newToken.member = member
repository.save(newToken)
}
}
}
@Transactional
fun logout(memberId: Long) {
val tokens = repository.findByMemberId(memberId)
for (token in tokens) {
token.member = null
}
}
@Transactional
fun unregisterInvalidToken(token: String) {
val existing = repository.findByToken(token)
if (existing != null) {
repository.delete(existing)
}
}
}