Skip to content

Commit

Permalink
Implement Remind Fine Batch
Browse files Browse the repository at this point in the history
  • Loading branch information
mkSpace committed Aug 6, 2023
1 parent abc7111 commit a34ed57
Show file tree
Hide file tree
Showing 15 changed files with 210 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,9 @@ data class StudyAttendanceStartNotificationPushEvent(val studyIdWithMoitIds: Set
return KafkaEventTopic.STUDY_ATTENDANCE_START_NOTIFICATION
}
}

data class RemindFineNotificationPushEvent(val fineIds: Set<Long>, val flushAt: LocalDateTime) : MoitEvent {
override fun getTopic(): String {
return KafkaEventTopic.REMIND_FINE_NOTIFICATION
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ object KafkaEventTopic {
const val FINE_CREATE = "fine_create"
const val FINE_CREATE_BULK = "fine_create_bulk"
const val FINE_APPROVE = "fine_approve"
const val REMIND_FINE_NOTIFICATION = "remind_fine_notification"
}

object KafkaConsumerGroup {
Expand All @@ -20,4 +21,5 @@ object KafkaConsumerGroup {
const val FINE_CREATE_BANNER_UPDATE = "fine_create_banner_update"
const val FINE_CREATE_BANNER_UPDATE_BULK = "fine_create_banner_update_bulk"
const val FINE_APPROVE_BANNER_UPDATE = "fine_approve_banner_update"
const val REMIND_FINE_NOTIFICATION_CREATE = "remind_fine_notification_create"
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.mashup.moit.domain.banner.update.StudyAttendanceStartBannerUpdateRequ
import com.mashup.moit.domain.fine.FineService
import com.mashup.moit.domain.notification.AttendanceStartNotificationEvent
import com.mashup.moit.domain.notification.NotificationService
import com.mashup.moit.domain.notification.RemindFineNotificationEvent
import com.mashup.moit.domain.study.StudyService
import com.mashup.moit.infra.event.EventProducer
import com.mashup.moit.infra.event.FineApproveEvent
Expand All @@ -14,6 +15,7 @@ import com.mashup.moit.infra.event.FineCreateEventBulk
import com.mashup.moit.infra.event.KafkaConsumerGroup
import com.mashup.moit.infra.event.KafkaEventTopic
import com.mashup.moit.infra.event.MoitCreateEvent
import com.mashup.moit.infra.event.RemindFineNotificationPushEvent
import com.mashup.moit.infra.event.StudyAttendanceEvent
import com.mashup.moit.infra.event.StudyAttendanceEventBulk
import com.mashup.moit.infra.event.StudyAttendanceStartNotificationPushEvent
Expand Down Expand Up @@ -112,4 +114,13 @@ class KafkaConsumer(
log.debug("consumeStudyAttendanceStartNotificationPushEvent called: {}", event)
notificationService.save(AttendanceStartNotificationEvent(event.studyIdWithMoitIds, event.flushAt))
}

@KafkaListener(
topics = [KafkaEventTopic.REMIND_FINE_NOTIFICATION],
groupId = KafkaConsumerGroup.REMIND_FINE_NOTIFICATION_CREATE,
)
fun consumeRemindFineNotificationPushEvent(event: RemindFineNotificationPushEvent) {
log.debug("consumeRemindFineNotificationPushEvent called: {}", event)
notificationService.save(RemindFineNotificationEvent(event.fineIds, event.flushAt))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.mashup.moit.infra.fcm

import com.mashup.moit.domain.moit.Moit
import com.mashup.moit.domain.moit.NotificationRemindOption
import com.mashup.moit.domain.notification.RemindFinePushNotificationGenerator
import com.mashup.moit.domain.notification.StartAttendancePushNotificationGenerator
import com.mashup.moit.domain.study.Study
import com.mashup.moit.domain.user.User

data class SampleNotificationRequest(
val studyId: Long,
Expand Down Expand Up @@ -33,3 +35,23 @@ data class StudyAttendanceStartNotification(
)
}
}

data class FineRemindNotification(
val userFcmToken: String,
val title: String,
val body: String
) {
companion object {
fun of(
user: User,
moit: Moit,
study: Study
): FineRemindNotification? = user.fcmToken?.let { token ->
FineRemindNotification(
userFcmToken = token,
title = RemindFinePushNotificationGenerator.TITLE_TEMPLATE,
body = RemindFinePushNotificationGenerator.bodyTemplate(user.nickname, moit.name, study.order)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,27 @@ class FCMNotificationService(
}
}

fun pushRemindFineNotification(fineNotification: FineRemindNotification) {
val userFcmToken = fineNotification.userFcmToken
runCatching {
val notification = Notification.builder()
.setTitle(fineNotification.title)
.setBody(fineNotification.body)
.build()

val message = Message.builder()
.setToken(userFcmToken)
.setNotification(notification)
.build()

firebaseMessaging.send(message)
}.onSuccess { response ->
logger.info("success to send notification : {}", response)
}.onFailure { e ->
logger.error("Fail to send Remind Fine Noti. userFcmToken : [{}], error: [{}]", userFcmToken, e.toString())
}
}

fun getMoitTopic(moitId: Long): String {
return TOPIC_MOIT_PREFIX + moitId
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.mashup.moit.scheduler

import com.mashup.moit.domain.fine.FineService
import com.mashup.moit.domain.moit.MoitService
import com.mashup.moit.domain.study.StudyService
import com.mashup.moit.domain.user.UserService
import com.mashup.moit.infra.event.EventProducer
import com.mashup.moit.infra.event.RemindFineNotificationPushEvent
import com.mashup.moit.infra.fcm.FCMNotificationService
import com.mashup.moit.infra.fcm.FineRemindNotification
import org.joda.time.LocalDateTime
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Async
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component

@Component
class FineRemindNotiPushScheduler(
private val userService: UserService,
private val moitService: MoitService,
private val studyService: StudyService,
private val fineService: FineService,
private val fcmNotificationService: FCMNotificationService,
private val eventProducer: EventProducer
) {

val logger: Logger = LoggerFactory.getLogger(FineRemindNotiPushScheduler::class.java)

@Scheduled(cron = "0 30 10 * * *")
@Async("pushSchedulerExecutor")
fun pushRemindFineNotification() {
val scheduleContext = LocalDateTime.now()

val unrequestedFines = fineService.getUnrequestedFines()
logger.info("Remind {} fines! Start Push Notification at {}.", unrequestedFines.size, scheduleContext)

val unrequestedFineUserMap = userService
.findUsersById(
unrequestedFines.map { fine -> fine.userId }.distinct()
)
.associateBy { user -> user.id }
val unrequestedFineMoitMap = moitService
.getMoitsByIds(
unrequestedFines.map { fine -> fine.moitId }.toSet()
)
.associateBy { moit -> moit.id }
val unrequestedFineStudyMap = studyService
.findByStudyIds(
unrequestedFines.map { fine -> fine.studyId }.distinct()
)
.associateBy { study -> study.id }

for (fine in unrequestedFines) {
val user = unrequestedFineUserMap[fine.userId] ?: continue
val moit = unrequestedFineMoitMap[fine.moitId] ?: continue
val study = unrequestedFineStudyMap[fine.studyId] ?: continue
FineRemindNotification.of(
user = user,
moit = moit,
study = study
)?.let { notification -> fcmNotificationService.pushRemindFineNotification(notification) }
}
eventProducer.produce(
RemindFineNotificationPushEvent(
fineIds = unrequestedFines.map { fine -> fine.id }.toSet(),
flushAt = java.time.LocalDateTime.now())
)
logger.info("Done Push notification for {} fines, at {}", unrequestedFines.size, LocalDateTime.now())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import org.springframework.data.jpa.repository.JpaRepository
interface FineRepository : JpaRepository<FineEntity, Long> {
fun findByMoitId(moitId: Long): List<FineEntity>
fun findAllByMoitIdAndUserIdAndApproveStatusIn(moitId: Long, userId: Long, approveStatuses: Collection<FineApproveStatus>): List<FineEntity>
fun findAllByApproveStatusIn(approveStatuses: Collection<FineApproveStatus>): List<FineEntity>
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ class FineService(
.map { it.toDomain() }
}

fun getUnrequestedFines(): List<Fine> {
return fineRepository.findAllByApproveStatusIn(
listOf(FineApproveStatus.NEW, FineApproveStatus.REJECTED)
).map { it.toDomain() }
}

@Transactional
fun updateFineApproveStatus(fineId: Long, confirmFine: Boolean) {
val approveStatus = if (confirmFine) FineApproveStatus.APPROVED else FineApproveStatus.REJECTED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import org.springframework.transaction.annotation.Transactional
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.LocalTime
import java.util.Locale
import java.util.*

@Service
@Transactional(readOnly = true)
Expand Down Expand Up @@ -84,6 +84,11 @@ class MoitService(
.toDomain()
}

fun getMoitsByIds(moitIds: Set<Long>): List<Moit> {
return moitRepository.findAllById(moitIds)
.map { it.toDomain() }
}

fun getMoitsByUserId(userId: Long): List<Moit> {
val moitIds = userMoitRepository.findAllByUserId(userId).map { it.moitId }
return moitRepository.findAllById(moitIds).map { it.toDomain() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ data class AttendanceStartNotificationEvent(
val studyIdWithMoitIds: Set<Pair<Long, Long>>,
val flushAt: LocalDateTime
) : NotificationEvent

data class RemindFineNotificationEvent(
val fineIds: Set<Long>,
val flushAt: LocalDateTime
) : NotificationEvent
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.mashup.moit.domain.notification

import com.mashup.moit.common.exception.MoitException
import com.mashup.moit.common.exception.MoitExceptionType
import com.mashup.moit.domain.fine.FineService
import com.mashup.moit.domain.moit.MoitService
import com.mashup.moit.domain.study.StudyService
import com.mashup.moit.domain.user.UserService
import com.mashup.moit.domain.usermoit.UserMoitService
import org.springframework.stereotype.Component

Expand Down Expand Up @@ -57,3 +59,51 @@ class StartAttendancePushNotificationGenerator(
}
}
}

@Component
class RemindFinePushNotificationGenerator(
private val userService: UserService,
private val moitService: MoitService,
private val studyService: StudyService,
private val fineService: FineService,
private val urlSchemeProperties: UrlSchemeProperties,
) : NotificationGenerator {
companion object {
const val TITLE_TEMPLATE = "밀린 벌금이 있어요!"
fun bodyTemplate(userName: String, moitName: String, studyOrder: Int): String {
return "$moitName ${userName}님, 아직 ${studyOrder + 1}회차 스터디의 벌금을 납부하지 않았습니다.\n얼른 납부하고 인증받으세요!"
}
}

override fun support(event: NotificationEvent): Boolean {
return event is RemindFineNotificationEvent
}

override fun generate(event: NotificationEvent): List<NotificationEntity> {
if (event !is RemindFineNotificationEvent) {
throw MoitException.of(MoitExceptionType.SYSTEM_FAIL)
}

return event.fineIds
.map { fineId ->
val fine = fineService.getFine(fineId)
val user = userService.findUserById(fine.userId)
val (userId, userName) = user.id to user.nickname
val moit = moitService.getMoitById(fine.moitId)
val (moitId, moitName) = moit.id to moit.name
val studyOrder = studyService.findById(fine.studyId).order

NotificationEntity(
type = NotificationType.CHECK_FINE_NOTIFICATION,
userId = userId,
title = TITLE_TEMPLATE,
body = bodyTemplate(userName, moitName, studyOrder),
urlScheme = UrlSchemeUtils.generate(
urlSchemeProperties.checkFine,
UrlSchemeParameter("moitId", moitId.toString()),
UrlSchemeParameter("fineId", fineId.toString())
)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ data class User(
val profileImage: Int,
val email: String,
val roles: Set<UserRole>,
val fcmToken: String?
)
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class UserEntity(
val roles: Set<UserRole>,

@Column(name = "fcm_token", nullable = true)
val fcmToken: String?
var fcmToken: String?
) : BaseEntity() {
fun toDomain(): User {
return User(
Expand All @@ -44,6 +44,7 @@ class UserEntity(
profileImage = profileImage,
email = email,
roles = roles,
fcmToken = fcmToken
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ class UserService(
.toDomain()
}

@Transactional
fun updateFcmToken(userId: Long, fcmToken: String?) {
userRepository.findByIdOrNull(userId)?.fcmToken = fcmToken
}

@Transactional
fun deleteUser(userId: Long) {
userRepository.deleteById(userId);
Expand Down
2 changes: 1 addition & 1 deletion moit-domain/src/main/resources/application-domain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ spring:
url-scheme:
study-scheduled: "moit://details?moitId={moitId}" # 모잇 상세 페이지
attendance-start: "moit://attendance?moitId={moitId}" # 출석 시작 -> 모잇 상세 페이지
check-fine: "moit://fine?moitId={moitId}&findId={findId}" # 벌금 상세
check-fine: "moit://fine?moitId={moitId}&fineId={fineId}" # 벌금 상세

0 comments on commit a34ed57

Please sign in to comment.