Skip to content

Commit

Permalink
[Feat/#493] 예약 공지 관리 기능 (#498)
Browse files Browse the repository at this point in the history
* feat: TimeOutEventTaskManager 구현

* feat: ScheduledEvent 구현

* feat: ScheduledEvent canceled 칼럼 추가

* feat: NotificationEmailSendTimeOutEventReplayer 반영

* feat: CrmEmailSendView 화면 구현
  • Loading branch information
belljun3395 authored Jan 7, 2025
1 parent b83eb8f commit f7db0e0
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ data class ScheduledEvent(
var completed: Boolean,
@Column(name = "is_not_consumed")
var isNotConsumed: Boolean = false,
@Column(name = "canceled")
var canceled: Boolean = false,
) {
constructor() : this(
eventId = "",
Expand All @@ -37,4 +39,11 @@ data class ScheduledEvent(
isNotConsumed = true
return this
}

fun cancel(): ScheduledEvent {
completed = true
isNotConsumed = true
canceled = true
return this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.few.crm.email.event.schedule

import event.Event
import event.EventUtils
import java.time.LocalDateTime

abstract class ScheduledEvent(
eventId: String,
eventType: String,
eventTime: LocalDateTime,
) : Event(
eventId,
eventType,
eventTime,
)

class CancelScheduledEvent(
val targetEventId: String,
eventId: String = EventUtils.generateEventId(),
eventTime: LocalDateTime = LocalDateTime.now(),
) : ScheduledEvent(
eventId,
"CancelScheduledEvent",
eventTime,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.few.crm.email.event.schedule

import com.few.crm.email.event.schedule.handler.CancelScheduledEventHandler
import com.few.crm.support.jpa.CrmTransactional
import org.springframework.context.event.EventListener
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Propagation

@Component
class ScheduledEventListener(
private val cancelScheduledEventHandler: CancelScheduledEventHandler,
) {
@Async
@EventListener
@CrmTransactional(propagation = Propagation.REQUIRES_NEW)
fun onCancelEvent(event: CancelScheduledEvent) {
cancelScheduledEventHandler.handle(event)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.few.crm.email.event.schedule.handler

import com.few.crm.email.event.schedule.CancelScheduledEvent
import com.few.crm.email.repository.ScheduledEventRepository
import com.few.crm.support.jpa.CrmTransactional
import event.EventHandler
import org.springframework.stereotype.Component

@Component
class CancelScheduledEventHandler(
private val scheduledEventRepository: ScheduledEventRepository,
) : EventHandler<CancelScheduledEvent> {
@CrmTransactional
override fun handle(event: CancelScheduledEvent) {
scheduledEventRepository
.findByEventId(event.targetEventId)
?.cancel() ?: throw IllegalStateException("Scheduled event not found for event id: ${event.eventId}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import com.few.crm.support.LocalDateTimeExtension
import com.few.crm.support.jpa.CrmTransactional
import com.few.crm.support.parseEventTime
import com.few.crm.support.parseExpiredTime
import com.few.crm.support.toScheduleTime
import com.few.crm.support.schedule.TimeOutEventTaskManager
import event.EventRePlayer
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
import org.springframework.context.ApplicationEventPublisher
import org.springframework.scheduling.TaskScheduler
import org.springframework.stereotype.Component

fun JsonNode.templateId() = this["templateId"].asLong()
Expand All @@ -30,7 +29,7 @@ class NotificationEmailSendTimeOutEventReplayer(
private val eventScheduleRepository: ScheduledEventRepository,
private val objectMapper: ObjectMapper,
private val applicationEventPublisher: ApplicationEventPublisher,
private val taskScheduler: TaskScheduler,
private val timeOutEventTaskManager: TimeOutEventTaskManager,
) : EventRePlayer(),
ApplicationRunner {
val log = KotlinLogging.logger {}
Expand Down Expand Up @@ -66,7 +65,7 @@ class NotificationEmailSendTimeOutEventReplayer(
return@forEach
}
log.info { "Event is replayed. eventId: ${event.eventId} expiredTime: ${event.expiredTime}" }
taskScheduler.schedule(event, event.expiredTime.toScheduleTime())
timeOutEventTaskManager.reSchedule(event)
}
}
log.info { "==================== [END] NotificationEmailSendTimeOutEventReplayer ====================" }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.few.crm.support.schedule

import com.few.crm.email.event.schedule.CancelScheduledEvent
import com.few.crm.email.event.send.NotificationEmailSendTimeOutEvent
import com.few.crm.support.toScheduleTime
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.context.ApplicationEventPublisher
import org.springframework.scheduling.TaskScheduler
import org.springframework.stereotype.Component
import java.util.concurrent.ScheduledFuture

class ManagedTask(
val taskName: String,
val task: ScheduledFuture<*>,
val taskView: TaskView,
)

data class TaskView(
val taskName: String,
val values: Map<String, Any>,
)

@Component
class TimeOutEventTaskManager(
private val taskScheduler: TaskScheduler,
private val applicationEventPublisher: ApplicationEventPublisher,
) {
val log = KotlinLogging.logger {}

companion object {
val tasks = mutableMapOf<String, ManagedTask>()
}

fun schedule(
taskName: String,
task: ScheduledFuture<*>,
taskView: TaskView,
) {
log.info { "Scheduling task $taskName" }
tasks[taskName] = ManagedTask(taskName, task, taskView)
}

fun newSchedule(event: NotificationEmailSendTimeOutEvent) {
schedule(event)
applicationEventPublisher.publishEvent(event)
}

fun reSchedule(event: NotificationEmailSendTimeOutEvent) {
schedule(event)
}

private fun schedule(event: NotificationEmailSendTimeOutEvent) {
this.schedule(
event.eventId,
taskScheduler.schedule(event, event.expiredTime.toScheduleTime()),
TaskView(
taskName = event.eventId,
values =
mapOf(
"templateId" to event.templateId,
"userIds" to event.userIds,
"eventId" to event.eventId,
"eventType" to event.eventType,
"eventTime" to event.eventTime,
"expiredTime" to event.expiredTime,
"completed" to event.completed,
),
),
)
}

fun cancel(taskName: String) {
tasks[taskName]?.let {
it.task.cancel(false)
tasks.remove(taskName)
applicationEventPublisher.publishEvent(
CancelScheduledEvent(
targetEventId = taskName,
),
)
log.info { "Task $taskName is cancelled" }
}
}

fun scheduledTasksView(): List<TaskView> = tasks.values.map { it.taskView }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import com.few.crm.email.event.send.NotificationEmailSendTimeOutEvent
import com.few.crm.email.repository.EmailTemplateRepository
import com.few.crm.email.usecase.SendNotificationEmailUseCase
import com.few.crm.email.usecase.dto.SendNotificationEmailUseCaseIn
import com.few.crm.support.toScheduleTime
import com.few.crm.support.schedule.TaskView
import com.few.crm.support.schedule.TimeOutEventTaskManager
import com.few.crm.user.domain.User
import com.few.crm.user.repository.UserRepository
import com.few.crm.view.CommonVerticalLayout
Expand All @@ -25,15 +26,14 @@ import com.vaadin.flow.component.timepicker.TimePicker
import com.vaadin.flow.router.Route
import org.springframework.context.ApplicationEventPublisher
import org.springframework.data.domain.Sort
import org.springframework.scheduling.TaskScheduler
import java.time.*

@Route("/crm/email/send")
class CrmEmailSendView(
private val emailTemplateRepository: EmailTemplateRepository,
private val userRepository: UserRepository,
private val sendNotificationEmailUseCase: SendNotificationEmailUseCase,
private val taskScheduler: TaskScheduler,
private val timeOutEventTaskManager: TimeOutEventTaskManager,
private val applicationEventPublisher: ApplicationEventPublisher,
private val objectMapper: ObjectMapper,
) : CommonVerticalLayout() {
Expand Down Expand Up @@ -69,11 +69,24 @@ class CrmEmailSendView(
templateGrid.height = "auto"
userGrid.height = "auto"

val buttonLayout =
HorizontalLayout().apply {
isSpacing = true
isPadding = true
}

val notificationButton =
Button("Notification").apply {
addClickListener { sendNotificationEmail() }
}

val scheduledNotificationButton =
Button("Scheduled Notification").apply {
addClickListener {
openScheduledNotificationDialog()
}
}

val templates = emailTemplateRepository.findAll(Sort.by(Sort.Order.desc("id")))
templateGrid.setItems(templates)

Expand Down Expand Up @@ -120,11 +133,45 @@ class CrmEmailSendView(
userGrid.addColumn(User::userAttributes).setHeader("User Attributes")
userGrid.addColumn(User::createdAt).setHeader("Created At")

contentArea.add(notificationButton)
buttonLayout.add(notificationButton, scheduledNotificationButton)
contentArea.add(buttonLayout)
contentArea.add(searchLayout, templateGrid)
contentArea.add(userGrid)
}

private fun openScheduledNotificationDialog() {
val dialog =
Dialog().apply {
width = "80%"
height = "60%"
}
val layout =
VerticalLayout().apply {
setSizeFull()
style.set("overflow", "auto")
}

val grid = Grid(TaskView::class.java)
grid.selectionMode = Grid.SelectionMode.SINGLE
val tasks = timeOutEventTaskManager.scheduledTasksView()
grid.setItems(tasks)
val cancelTaskButton =
Button("Cancel Task").apply {
addClickListener {
val selectedTask = grid.selectedItems.firstOrNull()
if (selectedTask != null) {
timeOutEventTaskManager.cancel(selectedTask.taskName)
grid.setItems(timeOutEventTaskManager.scheduledTasksView())
}
}
}

layout.add(grid)
layout.add(cancelTaskButton)
dialog.add(layout)
dialog.open()
}

private fun filterGridByEmail(templateName: String) {
val filteredTemplates =
if (templateName.isBlank()) {
Expand Down Expand Up @@ -250,8 +297,7 @@ class CrmEmailSendView(
expiredTime = dateTime,
eventPublisher = applicationEventPublisher,
).let {
taskScheduler.schedule(it, it.expiredTime.toScheduleTime())
applicationEventPublisher.publishEvent(it)
timeOutEventTaskManager.newSchedule(it)
}
}
dialog.close()
Expand Down

0 comments on commit f7db0e0

Please sign in to comment.