Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature/526] 탭바 SSE를 적용한다 #536

Merged
merged 5 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import org.springframework.web.bind.annotation.RestController
class BottleController(
private val bottleFacade: BottleFacade
) {

@ApiOperation("홈 - 받은 보틀 목록 조회하기")
@GetMapping
@AuthRequired
Expand Down Expand Up @@ -75,6 +75,7 @@ class BottleController(
bottleFacade.registerLetter(userId, bottleId, registerLetterRequest)
}

// TODO 클라이언트에서 핑퐁중인 보틀 읽음 표시도 v2로 변경한 후 제거
@ApiOperation("보틀 보관함 - 보틀 읽음 표시하기")
@PostMapping("/ping-pong/{bottleId}/read")
@AuthRequired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.nexters.bottles.api.global.interceptor.AuthRequired
import com.nexters.bottles.api.global.resolver.AuthUserId
import io.swagger.annotations.ApiOperation
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
Expand Down Expand Up @@ -46,4 +47,11 @@ class BottleControllerV2(
fun getAdditionalRandomBottle(@AuthUserId userId: Long) {
return bottleFacadeV2.getAdditionalRandomBottle(userId)
}

@ApiOperation("보틀 읽음 표시하기")
@PostMapping("/bottle/{bottleId}/read")
@AuthRequired
fun readBottle(@AuthUserId userId: Long, @PathVariable bottleId: Long) {
bottleFacadeV2.readBottle(userId, bottleId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.nexters.bottles.api.bottle.controller

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.RestController
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter

@RestController
class TabEventController(
private val tabEventService: TabEventService
) {

@GetMapping("/connect", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
@AuthRequired
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,14 +2,20 @@ 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
import com.nexters.bottles.api.bottle.event.dto.BottleShareImageEventDto
import com.nexters.bottles.api.bottle.event.dto.BottleStopEventDto
import com.nexters.bottles.app.bottle.domain.Bottle
import com.nexters.bottles.app.bottle.domain.enum.BottleStatus
import com.nexters.bottles.app.bottle.domain.enum.TabType
import com.nexters.bottles.app.bottle.service.BottleHistoryService
import com.nexters.bottles.app.bottle.service.BottleReadHistoryService
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 All @@ -25,10 +31,12 @@ import org.springframework.stereotype.Component
class BottleApiEventListener(
private val bottleService: BottleService,
private val bottleHistoryService: BottleHistoryService,
private val bottleReadHistoryService: BottleReadHistoryService,
private val fcmTokenService: FcmTokenService,
private val fcmClient: FcmClient,
private val userService: UserService,
private val userAlimyService: UserAlimyService,
private val tabEventService: TabEventService
) {

private val log = KotlinLogging.logger { }
Expand All @@ -42,7 +50,16 @@ class BottleApiEventListener(
@Async
@EventListener
fun handleCustomEvent(event: BottleMatchEventDto) {
bottleHistoryService.saveMatchingHistory(event.sourceUserId, event.targetUserId)
val bottle = bottleService.findBottleById(event.bottleId)

bottleHistoryService.saveMatchingHistory(sourceUserId = event.sourceUserId, targetUserId = event.targetUserId)
bottleReadHistoryService.saveBottleReadHistory(user = bottle.sourceUser, bottle = bottle)
bottleReadHistoryService.saveBottleReadHistory(user = bottle.targetUser, bottle = bottle)

tabEventService.sendEventByTabType(
event.targetUserId,
TabEventDto(tabType = TabType.SANDBEACH, isNewBadgeVisible = true)
)
}

@Async
Expand All @@ -51,6 +68,12 @@ class BottleApiEventListener(
val bottle = bottleService.findBottleById(event.bottleId)
when {
bottle.isSentLikeMessageAndNotStart() -> {
bottleHistoryService.saveMatchingHistory(bottle.sourceUser.id, bottle.targetUser.id)
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} 켜져 있지 않아 발송하지 않음" }
return
Expand All @@ -66,10 +89,18 @@ class BottleApiEventListener(
log.info { "[BottleAcceptEventDto] 호감 보냄 bottleId: ${bottle.id} targetUserId: ${bottle.targetUser.id} sourceUserId: ${bottle.sourceUser.id} sourceUserToken: ${it.token}" }
}

bottleHistoryService.saveMatchingHistory(bottle.sourceUser.id, bottle.targetUser.id)
}

bottle.isActive() -> {
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 {
if (!userAlimyService.isTurnedOn(it.userId, AlimyType.PINGPONG)) {
Expand Down Expand Up @@ -98,6 +129,11 @@ class BottleApiEventListener(
val bottle = bottleService.findBottleById(event.bottleId)
val otherUser = bottle.findOtherUser(bottle.stoppedUser!!)

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

fcmTokenService.findAllByUserIdAndTokenNotBlank(otherUser.id).forEach {
if (!userAlimyService.isTurnedOn(otherUser.id, AlimyType.PINGPONG)) {
log.info { "userId: ${otherUser.id} alimyType: ${AlimyType.PINGPONG} 켜져 있지 않아 발송하지 않음" }
Expand All @@ -120,6 +156,11 @@ class BottleApiEventListener(
val user = userService.findByIdAndNotDeleted(event.userId)
val otherUser = bottle.findOtherUser(user)

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

fcmTokenService.findAllByUserIdAndTokenNotBlank(otherUser.id).forEach {
if (!userAlimyService.isTurnedOn(otherUser.id, AlimyType.PINGPONG)) {
log.info { "userId: ${otherUser.id} alimyType: ${AlimyType.PINGPONG} 켜져 있지 않아 발송하지 않음" }
Expand All @@ -135,13 +176,45 @@ class BottleApiEventListener(
}
}

@Async
@EventListener
fun handleCustomEvent(event: BottleReadEventDto) {
val bottle = bottleService.findBottleById(event.bottleId)

var tabEventDto = TabEventDto(tabType = TabType.PINGPONG, isNewBadgeVisible = false)

if (bottle.isNotStart()) {
when (bottle.bottleStatus) {
BottleStatus.RANDOM -> {
val isAllRead = bottleService.isAllReadByBottleStatus(event.userId, BottleStatus.RANDOM)
tabEventDto = TabEventDto(tabType = TabType.SANDBEACH, isNewBadgeVisible = !isAllRead)
}

BottleStatus.SENT -> {
val isAllRead = bottleService.isAllReadByBottleStatus(event.userId, BottleStatus.SENT)
tabEventDto = TabEventDto(tabType = TabType.LIKE, isNewBadgeVisible = !isAllRead)
}
}
} else {
val isAllRead = bottleService.isAllReadPingPongBottles(event.userId)
tabEventDto = TabEventDto(tabType = TabType.PINGPONG, isNewBadgeVisible = !isAllRead)
}

tabEventService.sendEventByTabType(event.userId, tabEventDto)
}

@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,
TabEventDto(tabType = TabType.PINGPONG, isNewBadgeVisible = true)
)

fcmTokenService.findAllByUserIdAndTokenNotBlank(otherUser.id).forEach {
if (!userAlimyService.isTurnedOn(otherUser.id, AlimyType.PINGPONG)) {
log.info { "userId: ${otherUser.id} alimyType: ${AlimyType.PINGPONG} 켜져 있지 않아 발송하지 않음" }
Expand All @@ -163,6 +236,11 @@ class BottleApiEventListener(
val user = userService.findByIdAndNotDeleted(event.userId)
val otherUser = bottle.findOtherUser(user)

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

fcmTokenService.findAllByUserIdAndTokenNotBlank(otherUser.id).forEach {
if (!userAlimyService.isTurnedOn(otherUser.id, AlimyType.PINGPONG)) {
log.info { "userId: ${otherUser.id} alimyType: ${AlimyType.PINGPONG} 켜져 있지 않아 발송하지 않음" }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.nexters.bottles.api.bottle.event.dto

data class BottleMatchEventDto(
val bottleId: Long,
val sourceUserId: Long,
val targetUserId: Long
) {
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 @@ -76,6 +77,7 @@ class BottleFacade(
?.also {
applicationEventPublisher.publishEvent(
BottleMatchEventDto(
bottleId = it.id,
sourceUserId = it.sourceUser.id,
targetUserId = it.targetUser.id,
)
Expand Down Expand Up @@ -261,6 +263,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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.nexters.bottles.api.bottle.facade

import com.nexters.bottles.api.bottle.event.dto.BottleMatchEventDto
import com.nexters.bottles.api.bottle.event.dto.BottleReadEventDto
import com.nexters.bottles.api.bottle.facade.dto.PingPongBottleDtoV2
import com.nexters.bottles.api.bottle.facade.dto.PingPongListResponseV2
import com.nexters.bottles.api.bottle.facade.dto.RandomBottleDto
Expand All @@ -12,6 +13,7 @@ import com.nexters.bottles.api.user.component.event.dto.UserApplicationEventDto
import com.nexters.bottles.app.bottle.domain.Bottle
import com.nexters.bottles.app.bottle.domain.enum.BottleStatus
import com.nexters.bottles.app.bottle.service.BottleCachingService
import com.nexters.bottles.app.bottle.service.BottleReadHistoryService
import com.nexters.bottles.app.bottle.service.BottleService
import com.nexters.bottles.app.bottle.service.LetterService
import com.nexters.bottles.app.user.domain.User
Expand All @@ -30,6 +32,7 @@ class BottleFacadeV2(
private val userReportService: UserReportService,
private val blockContactListService: BlockContactListService,
private val letterService: LetterService,
private val bottleReadHistoryService: BottleReadHistoryService,
private val bottleCachingService: BottleCachingService,
private val applicationEventPublisher: ApplicationEventPublisher,
) {
Expand All @@ -49,6 +52,7 @@ class BottleFacadeV2(
?.also {
applicationEventPublisher.publishEvent(
BottleMatchEventDto(
bottleId = it.id,
sourceUserId = it.sourceUser.id,
targetUserId = it.targetUser.id,
)
Expand Down Expand Up @@ -77,6 +81,7 @@ class BottleFacadeV2(
?.also {
applicationEventPublisher.publishEvent(
BottleMatchEventDto(
bottleId = it.id,
sourceUserId = it.sourceUser.id,
targetUserId = it.targetUser.id,
)
Expand Down Expand Up @@ -211,4 +216,17 @@ class BottleFacadeV2(
lastStatus = lastStatus
)
}

fun readBottle(userId: Long, bottleId: Long) {
val bottle = bottleService.findBottleById(bottleId)
val me = userService.findByIdAndNotDeleted(userId)
bottleReadHistoryService.markReadUserBottle(bottle, me)

applicationEventPublisher.publishEvent(
BottleReadEventDto(
bottleId = bottle.id,
userId = me.id
)
)
}
}
40 changes: 25 additions & 15 deletions api/src/main/resources/sql/ddl/table_query.sql
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
CREATE TABLE user
(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) DEFAULT NULL,
birthdate DATE DEFAULT NULL,
kakao_id VARCHAR(255) DEFAULT NULL,
city VARCHAR(255) DEFAULT NULL comment '시',
state VARCHAR(255) DEFAULT NULL comment '구',
phone_number VARCHAR(255) DEFAULT NULL comment 'ex) 01012345678',
gender VARCHAR(10) DEFAULT 'MALE',
sign_up_type VARCHAR(20) DEFAULT 'NORMAL' NOT NULL,
apple_account_id VARCHAR(255) DEFAULT NULL,
deleted BOOLEAN DEFAULT FALSE NOT NULL,
deleted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_activated_at DATETIME DEFAULT CURRENT_TIMESTAMP comment '유저의 최근 활성 시간으로, 보틀 목록 조회하기 api 요청시 갱신',
is_match_activated BOOLEAN DEFAULT TRUE NOT NULL comment '매칭 활성화 여부',
last_random_matched_at DATETIME DEFAULT CURRENT_TIMESTAMP comment '유저의 최근 랜덤 매칭 시간으로, 랜덤 매칭시 갱신',
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) DEFAULT NULL,
birthdate DATE DEFAULT NULL,
kakao_id VARCHAR(255) DEFAULT NULL,
city VARCHAR(255) DEFAULT NULL comment '시',
state VARCHAR(255) DEFAULT NULL comment '구',
phone_number VARCHAR(255) DEFAULT NULL comment 'ex) 01012345678',
gender VARCHAR(10) DEFAULT 'MALE',
sign_up_type VARCHAR(20) DEFAULT 'NORMAL' NOT NULL,
apple_account_id VARCHAR(255) DEFAULT NULL,
deleted BOOLEAN DEFAULT FALSE NOT NULL,
deleted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_activated_at DATETIME DEFAULT CURRENT_TIMESTAMP comment '유저의 최근 활성 시간으로, 보틀 목록 조회하기 api 요청시 갱신',
is_match_activated BOOLEAN DEFAULT TRUE NOT NULL comment '매칭 활성화 여부',
last_random_matched_at DATETIME DEFAULT CURRENT_TIMESTAMP comment '유저의 최근 랜덤 매칭 시간으로, 랜덤 매칭시 갱신',
is_notification_enabled tinyint(1) default 0 not null comment '핸드폰 알림 허용 여부',
device_name varchar(255) null,
app_version varchar(255) null,
Expand Down Expand Up @@ -170,3 +170,13 @@ CREATE TABLE user_alimy

UNIQUE KEY unique_user_alimy (user_id, alimy_type)
);

CREATE TABLE bottle_read_history
(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
bottle_id BIGINT NOT NULL,
is_read_by_user BOOLEAN DEFAULT FALSE NOT NULL comment '내가 해당 보틀의 변경된 내용을 읽었는지 여부',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL
);
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ class Bottle(
return pingPongStatus == PingPongStatus.ACTIVE
}

fun isNotStart(): Boolean {
return pingPongStatus == PingPongStatus.NONE
}

fun calculateDeletedAfterDays(): Long? {
if (stoppedAt == null) return null

Expand Down
Loading
Loading