Skip to content

Commit

Permalink
feat: sse 구조 변경 및 문답 탭 뱃지 여부 판단 로직 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
miseongk committed Nov 1, 2024
1 parent ab2eede commit 1d55be1
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.nexters.bottles.api.bottle.controller

import com.nexters.bottles.api.bottle.facade.dto.TabTypeRequest
import com.nexters.bottles.api.global.interceptor.AuthRequired
import com.nexters.bottles.api.global.resolver.AuthUserId
import com.nexters.bottles.app.bottle.service.TabEventService
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter

Expand All @@ -17,8 +15,8 @@ class TabEventController(

@GetMapping("/connect", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
@AuthRequired
fun connect(@AuthUserId userId: Long, @ModelAttribute("tapType") tapType: TabTypeRequest): SseEmitter {
val sseEmitter = tabEventService.connect(userId, tapType.tabType)
fun connectSse(@AuthUserId userId: Long): SseEmitter {
val sseEmitter = tabEventService.connectSse(userId)
return sseEmitter
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.nexters.bottles.api.bottle.event

import com.nexters.bottles.api.bottle.event.dto.BottleAcceptEventDto
import com.nexters.bottles.api.bottle.event.dto.BottleMatchEventDto
import com.nexters.bottles.api.bottle.event.dto.BottleReadEventDto
import com.nexters.bottles.api.bottle.event.dto.BottleRefuseEventDto
import com.nexters.bottles.api.bottle.event.dto.BottleRegisterLetterEventDto
import com.nexters.bottles.api.bottle.event.dto.BottleShareContactEventDto
Expand All @@ -12,6 +13,7 @@ import com.nexters.bottles.app.bottle.domain.enum.TabType
import com.nexters.bottles.app.bottle.service.BottleHistoryService
import com.nexters.bottles.app.bottle.service.BottleService
import com.nexters.bottles.app.bottle.service.TabEventService
import com.nexters.bottles.app.bottle.service.dto.TabEventDto
import com.nexters.bottles.app.notification.component.FcmClient
import com.nexters.bottles.app.notification.component.dto.FcmNotification
import com.nexters.bottles.app.notification.service.FcmTokenService
Expand Down Expand Up @@ -46,7 +48,10 @@ class BottleApiEventListener(
@EventListener
fun handleCustomEvent(event: BottleMatchEventDto) {
bottleHistoryService.saveMatchingHistory(event.sourceUserId, event.targetUserId)
tabEventService.sendEventByTabType(event.targetUserId, TabType.SANDBEACH)
tabEventService.sendEventByTabType(
event.targetUserId,
TabEventDto(tabType = TabType.SANDBEACH, isNewBadgeVisible = true)
)
}

@Async
Expand All @@ -56,7 +61,10 @@ class BottleApiEventListener(
when {
bottle.isSentLikeMessageAndNotStart() -> {
bottleHistoryService.saveMatchingHistory(bottle.sourceUser.id, bottle.targetUser.id)
tabEventService.sendEventByTabType(bottle.targetUser.id, TabType.LIKE)
tabEventService.sendEventByTabType(
bottle.targetUser.id,
TabEventDto(tabType = TabType.LIKE, isNewBadgeVisible = true)
)

if (!userAlimyService.isTurnedOn(bottle.targetUser.id, AlimyType.RECEIVE_LIKE)) {
log.info { "userId: ${bottle.targetUser.id} alimyType: ${AlimyType.RECEIVE_LIKE} 켜져 있지 않아 발송하지 않음" }
Expand All @@ -76,8 +84,14 @@ class BottleApiEventListener(
}

bottle.isActive() -> {
tabEventService.sendEventByTabType(bottle.targetUser.id, TabType.PINGPONG)
tabEventService.sendEventByTabType(bottle.sourceUser.id, TabType.PINGPONG)
tabEventService.sendEventByTabType(
bottle.targetUser.id,
TabEventDto(tabType = TabType.PINGPONG, isNewBadgeVisible = true)
)
tabEventService.sendEventByTabType(
bottle.sourceUser.id,
TabEventDto(tabType = TabType.PINGPONG, isNewBadgeVisible = true)
)

fcmTokenService.findAllByUserIdsAndTokenNotBlank(listOf(bottle.sourceUser.id, bottle.targetUser.id))
.forEach {
Expand Down Expand Up @@ -107,7 +121,10 @@ class BottleApiEventListener(
val bottle = bottleService.findBottleById(event.bottleId)
val otherUser = bottle.findOtherUser(bottle.stoppedUser!!)

tabEventService.sendEventByTabType(otherUser.id, TabType.PINGPONG)
tabEventService.sendEventByTabType(
otherUser.id,
TabEventDto(tabType = TabType.PINGPONG, isNewBadgeVisible = true)
)

fcmTokenService.findAllByUserIdAndTokenNotBlank(otherUser.id).forEach {
if (!userAlimyService.isTurnedOn(otherUser.id, AlimyType.PINGPONG)) {
Expand All @@ -131,7 +148,10 @@ class BottleApiEventListener(
val user = userService.findByIdAndNotDeleted(event.userId)
val otherUser = bottle.findOtherUser(user)

tabEventService.sendEventByTabType(otherUser.id, TabType.PINGPONG)
tabEventService.sendEventByTabType(
otherUser.id,
TabEventDto(tabType = TabType.PINGPONG, isNewBadgeVisible = true)
)

fcmTokenService.findAllByUserIdAndTokenNotBlank(otherUser.id).forEach {
if (!userAlimyService.isTurnedOn(otherUser.id, AlimyType.PINGPONG)) {
Expand All @@ -148,14 +168,28 @@ class BottleApiEventListener(
}
}

@Async
@EventListener
fun handleCustomEvent(event: BottleReadEventDto) {
val isAllRead = bottleService.isAllReadPingPongBottles(event.userId)

tabEventService.sendEventByTabType(
event.userId,
TabEventDto(tabType = TabType.PINGPONG, isNewBadgeVisible = !isAllRead)
)
}

@Async
@EventListener
fun handleCustomEvent(event: BottleShareImageEventDto) {
val bottle = bottleService.findBottleById(event.bottleId)
val user = userService.findByIdAndNotDeleted(event.userId)
val otherUser = bottle.findOtherUser(user)

tabEventService.sendEventByTabType(otherUser.id, TabType.PINGPONG)
tabEventService.sendEventByTabType(
otherUser.id,
TabEventDto(tabType = TabType.PINGPONG, isNewBadgeVisible = true)
)

fcmTokenService.findAllByUserIdAndTokenNotBlank(otherUser.id).forEach {
if (!userAlimyService.isTurnedOn(otherUser.id, AlimyType.PINGPONG)) {
Expand All @@ -178,7 +212,10 @@ class BottleApiEventListener(
val user = userService.findByIdAndNotDeleted(event.userId)
val otherUser = bottle.findOtherUser(user)

tabEventService.sendEventByTabType(otherUser.id, TabType.PINGPONG)
tabEventService.sendEventByTabType(
otherUser.id,
TabEventDto(tabType = TabType.PINGPONG, isNewBadgeVisible = true)
)

fcmTokenService.findAllByUserIdAndTokenNotBlank(otherUser.id).forEach {
if (!userAlimyService.isTurnedOn(otherUser.id, AlimyType.PINGPONG)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.nexters.bottles.api.bottle.event.dto

data class BottleReadEventDto(
val bottleId: Long,
val userId: Long
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.nexters.bottles.api.bottle.facade

import com.nexters.bottles.api.bottle.event.dto.BottleAcceptEventDto
import com.nexters.bottles.api.bottle.event.dto.BottleMatchEventDto
import com.nexters.bottles.api.bottle.event.dto.BottleReadEventDto
import com.nexters.bottles.api.bottle.event.dto.BottleRefuseEventDto
import com.nexters.bottles.api.bottle.event.dto.BottleRegisterLetterEventDto
import com.nexters.bottles.api.bottle.event.dto.BottleShareContactEventDto
Expand Down Expand Up @@ -261,6 +262,13 @@ class BottleFacade(
val me = userService.findByIdAndNotDeleted(userId)
val otherUser = pingPongBottle.findOtherUser(me)
letterService.markReadOtherUserLetter(pingPongBottle, otherUser)

applicationEventPublisher.publishEvent(
BottleReadEventDto(
bottleId = pingPongBottle.id,
userId = me.id
)
)
}

@CacheEvict(PING_PONG_BOTTLE, key = "#bottleId")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,24 @@ package com.nexters.bottles.app.bottle.repository

import com.nexters.bottles.app.bottle.domain.Bottle
import com.nexters.bottles.app.bottle.domain.Letter
import com.nexters.bottles.app.bottle.domain.enum.PingPongStatus
import com.nexters.bottles.app.user.domain.User
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param

interface LetterRepository : JpaRepository<Letter, Long> {

fun findByBottleAndUser(bottle: Bottle, user: User): Letter?

fun findAllByUserId(userId: Long): List<Letter>

@Query(
value = "SELECT l FROM Letter l " +
"JOIN Bottle b " +
"ON l.bottle = b AND b.pingPongStatus IN :pingPongStatus "
)
fun findAllByPingPongStatus(
@Param("pingPongStatus") pingPongStatus: Set<PingPongStatus>
): List<Letter>
}
Original file line number Diff line number Diff line change
Expand Up @@ -295,4 +295,19 @@ class BottleService(

return savedBottle
}

@Transactional(readOnly = true)
fun isAllReadPingPongBottles(userId: Long): Boolean {
val user = userRepository.findByIdAndDeletedFalse(userId) ?: throw IllegalStateException("회원가입 상태를 문의해주세요")
val userLetters = letterRepository.findAllByPingPongStatus(
setOf(
PingPongStatus.ACTIVE,
PingPongStatus.MATCHED,
PingPongStatus.STOPPED
)
)
val unreadLetters = userLetters.filter { it.user.id != user.id }
.filter { !it.isReadByOtherUser }
return unreadLetters.isEmpty()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.nexters.bottles.app.bottle.service

import com.nexters.bottles.app.bottle.domain.enum.TabType
import com.nexters.bottles.app.bottle.service.dto.TabEventDto
import org.springframework.stereotype.Service
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter.SseEventBuilder
Expand All @@ -11,36 +11,32 @@ import java.util.concurrent.ConcurrentHashMap
@Service
class TabEventService {

private val sseEmitters: MutableMap<Long, MutableMap<String, SseEmitter>> = ConcurrentHashMap()
private val sseEmitters: MutableMap<Long, SseEmitter> = ConcurrentHashMap()

fun connect(userId: Long, tapType: TabType): SseEmitter {
fun connectSse(userId: Long): SseEmitter {
val sseEmitter = SseEmitter(300_000L)
val sseEventBuilder = SseEmitter.event()
.name(tapType.name)
.name("tab")
.data("connected")
.reconnectTime(10000L)

sendEvent(sseEmitter, sseEventBuilder)

val userSseEmitters = sseEmitters.getOrDefault(userId, ConcurrentHashMap())
userSseEmitters[tapType.name] = sseEmitter
sseEmitters[userId] = userSseEmitters
sseEmitters[userId] = sseEmitter

sseEmitter.onCompletion { userSseEmitters.remove(tapType.name) }
sseEmitter.onCompletion { sseEmitters.remove(userId) }
sseEmitter.onTimeout { sseEmitter.complete() }

return sseEmitter
}

fun sendEventByTabType(to: Long, tabType: TabType) {
val userSseEmitters = sseEmitters.getOrDefault(to, ConcurrentHashMap())

fun sendEventByTabType(to: Long, event: TabEventDto) {
val sseEventBuilder = SseEmitter.event()
.name(tabType.name)
.data("new")
.name("tab")
.data(event)
.reconnectTime(10000L)

val userSseEmitter = userSseEmitters.getOrDefault(tabType.name, null)
val userSseEmitter = sseEmitters.getOrDefault(to, null)
if (userSseEmitter != null) {
sendEvent(userSseEmitter, sseEventBuilder)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.nexters.bottles.app.bottle.service.dto

import com.nexters.bottles.app.bottle.domain.enum.TabType

data class TabEventDto(
val tabType: TabType,
val isNewBadgeVisible: Boolean
) {
}

0 comments on commit 1d55be1

Please sign in to comment.