Skip to content

Commit

Permalink
feat: 봉투 생성시 유저 봉투 통계 재생성 이벤트 처리, 유저 봉투 통계 새로고침 api 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
wjdtkdgns committed Aug 11, 2024
1 parent 632a5fe commit b289e26
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.oksusu.susu.api.envelope.model.response.GetFriendStatisticsResponse
import com.oksusu.susu.api.envelope.model.response.SearchEnvelopeResponse
import com.oksusu.susu.api.event.model.CreateEnvelopeEvent
import com.oksusu.susu.api.event.model.DeleteEnvelopeEvent
import com.oksusu.susu.api.event.model.RefreshUserEnvelopeStatisticEvent
import com.oksusu.susu.api.event.model.UpdateEnvelopeEvent
import com.oksusu.susu.api.friend.application.FriendRelationshipService
import com.oksusu.susu.api.friend.application.FriendService
Expand Down Expand Up @@ -113,6 +114,7 @@ class EnvelopeFacade(
).run { categoryAssignmentService.saveSync(this) }

publisher.publishEvent(CreateEnvelopeEvent(createdEnvelope, ledger))
publisher.publishEvent(RefreshUserEnvelopeStatisticEvent(user.uid))

createdEnvelope
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.oksusu.susu.api.event.listener

import com.oksusu.susu.api.common.aspect.SusuEventListener
import com.oksusu.susu.api.event.model.CacheUserEnvelopeStatisticEvent
import com.oksusu.susu.api.event.model.RefreshUserEnvelopeStatisticEvent
import com.oksusu.susu.api.statistic.application.UserEnvelopeStatisticService
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import com.oksusu.susu.common.extension.mdcCoroutineScope
Expand All @@ -10,6 +11,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.springframework.context.event.EventListener
import org.springframework.transaction.event.TransactionalEventListener

@SusuEventListener
class StatisticEventListener(
Expand All @@ -28,4 +30,20 @@ class StatisticEventListener(
logger.info { "[${event.publishAt}] ${event.uid} 유저 봉투 통계 캐싱 끝" }
}
}

@TransactionalEventListener
fun createUserWithdrawService(event: RefreshUserEnvelopeStatisticEvent) {
mdcCoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler, event.traceId).launch {
/** 통계 캐싱 안되어있으면 중단 */
userEnvelopeStatisticService.getStatisticOrNull(event.uid) ?: run { return@launch }

logger.info { "[${event.publishAt}] ${event.uid} refresh user envelope statistic 시작" }

val statistic = userEnvelopeStatisticService.createUserEnvelopeStatistic(event.uid)

userEnvelopeStatisticService.save(event.uid, statistic)

logger.info { "[${event.publishAt}] ${event.uid} refresh user envelope statistic 끝" }
}
}
}
4 changes: 4 additions & 0 deletions api/src/main/kotlin/com/oksusu/susu/api/event/model/Event.kt
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,7 @@ data class CreateUserWithdrawEvent(
data class CacheAppleOidcPublicKeysEvent(
val keys: OidcPublicKeysCacheModel,
) : BaseEvent()

data class RefreshUserEnvelopeStatisticEvent(
val uid: Long,
) : BaseEvent()
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ import com.oksusu.susu.api.statistic.model.response.UserEnvelopeStatisticRespons
import com.oksusu.susu.api.statistic.model.vo.SusuEnvelopeStatisticRequest
import com.oksusu.susu.cache.statistic.domain.UserEnvelopeStatistic
import com.oksusu.susu.common.extension.parZipWithMDC
import com.oksusu.susu.common.extension.yearMonth
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Service
import java.time.LocalDate

@Service
class StatisticFacade(
Expand All @@ -22,7 +20,6 @@ class StatisticFacade(
private val susuEnvelopeStatisticService: SusuEnvelopeStatisticService,
private val susuSpecificEnvelopeStatisticService: SusuSpecificEnvelopeStatisticService,
private val relationshipService: RelationshipService,
private val envelopeStatisticService: EnvelopeStatisticService,
private val eventPublisher: ApplicationEventPublisher,
) {
private val logger = KotlinLogging.logger { }
Expand All @@ -35,53 +32,29 @@ class StatisticFacade(
return UserEnvelopeStatisticResponse.from(this)
}

val userEnvelopeStatistic = parZipWithMDC(
/** 최근 사용 금액 1년 */
{ envelopeStatisticService.getRecentSpentFor1Year(user.uid) },
/** 최다 수수 관계 */
{ envelopeStatisticService.getMostFrequentRelationship(user.uid) },
/** 최다 수수 경조사 */
{ envelopeStatisticService.getMostFrequentCategory(user.uid) },
/** 가장 많이 받은 금액 */
{ envelopeStatisticService.getMaxReceivedEnvelope(user.uid) },
/** 가장 많이 보낸 금액 */
{ envelopeStatisticService.getMaxSentEnvelope(user.uid) }
) {
recentSpent,
mostFrequentRelationShip,
mostFrequentCategory,
maxReceivedEnvelope,
maxSentEnvelope,
->
val userEnvelopeStatistic = createUserEnvelopeStatistic(user.uid)

/** 최근 사용 금액 8달 */
val before8Month = LocalDate.now().minusMonths(7).yearMonth()
val recentSpentForLast8Months = recentSpent?.filter { spent ->
spent.title >= before8Month
}
return UserEnvelopeStatisticResponse.from(userEnvelopeStatistic)
}

/** 경조사비 가장 많이 쓴 달 */
val mostSpentMonth = recentSpent?.maxBy { model -> model.value }?.title?.substring(4)?.toLong()
suspend fun refreshUserEnvelopeStatistic(user: AuthUser): UserEnvelopeStatisticResponse {
val userEnvelopeStatistic = createUserEnvelopeStatistic(user.uid)

UserEnvelopeStatistic(
recentSpent = recentSpentForLast8Months,
mostSpentMonth = mostSpentMonth,
mostFrequentRelationShip = mostFrequentRelationShip,
mostFrequentCategory = mostFrequentCategory,
maxReceivedEnvelope = maxReceivedEnvelope,
maxSentEnvelope = maxSentEnvelope
)
}
return UserEnvelopeStatisticResponse.from(userEnvelopeStatistic)
}

private suspend fun createUserEnvelopeStatistic(uid: Long): UserEnvelopeStatistic {
val userEnvelopeStatistic = userEnvelopeStatisticService.createUserEnvelopeStatistic(uid)

/** 유저 봉투 통계 캐싱 */
eventPublisher.publishEvent(
CacheUserEnvelopeStatisticEvent(
uid = user.uid,
uid = uid,
statistic = userEnvelopeStatistic
)
)

return UserEnvelopeStatisticResponse.from(userEnvelopeStatistic)
return userEnvelopeStatistic
}

suspend fun getSusuEnvelopeStatistic(requestParam: SusuEnvelopeStatisticRequest): SusuEnvelopeStatisticResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,58 @@ package com.oksusu.susu.api.statistic.application

import com.oksusu.susu.cache.statistic.domain.UserEnvelopeStatistic
import com.oksusu.susu.cache.statistic.infrastructure.UserEnvelopeStatisticRepository
import com.oksusu.susu.common.extension.parZipWithMDC
import com.oksusu.susu.common.extension.withMDCContext
import com.oksusu.susu.common.extension.yearMonth
import kotlinx.coroutines.Dispatchers
import org.springframework.stereotype.Service
import java.time.LocalDate

@Service
class UserEnvelopeStatisticService(
private val userEnvelopeStatisticRepository: UserEnvelopeStatisticRepository,
private val envelopeStatisticService: EnvelopeStatisticService,
) {
suspend fun createUserEnvelopeStatistic(uid: Long): UserEnvelopeStatistic {
return parZipWithMDC(
/** 최근 사용 금액 1년 */
{ envelopeStatisticService.getRecentSpentFor1Year(uid) },
/** 최다 수수 관계 */
{ envelopeStatisticService.getMostFrequentRelationship(uid) },
/** 최다 수수 경조사 */
{ envelopeStatisticService.getMostFrequentCategory(uid) },
/** 가장 많이 받은 금액 */
{ envelopeStatisticService.getMaxReceivedEnvelope(uid) },
/** 가장 많이 보낸 금액 */
{ envelopeStatisticService.getMaxSentEnvelope(uid) }
) {
recentSpent,
mostFrequentRelationShip,
mostFrequentCategory,
maxReceivedEnvelope,
maxSentEnvelope,
->

/** 최근 사용 금액 8달 */
val before8Month = LocalDate.now().minusMonths(7).yearMonth()
val recentSpentForLast8Months = recentSpent?.filter { spent ->
spent.title >= before8Month
}

/** 경조사비 가장 많이 쓴 달 */
val mostSpentMonth = recentSpent?.maxBy { model -> model.value }?.title?.substring(4)?.toLong()

UserEnvelopeStatistic(
recentSpent = recentSpentForLast8Months,
mostSpentMonth = mostSpentMonth,
mostFrequentRelationShip = mostFrequentRelationShip,
mostFrequentCategory = mostFrequentCategory,
maxReceivedEnvelope = maxReceivedEnvelope,
maxSentEnvelope = maxSentEnvelope
)
}
}

suspend fun save(uid: Long, userEnvelopeStatistic: UserEnvelopeStatistic) {
return withMDCContext(Dispatchers.IO) {
userEnvelopeStatisticRepository.save(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ class StatisticResource(
user: AuthUser,
) = statisticFacade.getUserEnvelopeStatistic(user).wrapOk()

@Operation(summary = "나의 통계 새로고침")
@GetMapping("/mine/envelope/refresh")
suspend fun refreshUserEnvelopeStatistic(
user: AuthUser,
) = statisticFacade.refreshUserEnvelopeStatistic(user).wrapOk()

@Operation(summary = "수수 통계")
@GetMapping("/susu/envelope")
suspend fun getSusuEnvelopeStatistic(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component

@Component
Expand All @@ -17,7 +16,7 @@ class UserScheduler(
) {
private val logger = KotlinLogging.logger { }

@Scheduled(cron = "0 0 3 * * *")
// @Scheduled(cron = "0 0 3 * * *")
fun deleteWithdrawUserData() {
CoroutineScope(Dispatchers.IO + coroutineExceptionHandler.handler).launch {
runCatching {
Expand Down

0 comments on commit b289e26

Please sign in to comment.