-
Notifications
You must be signed in to change notification settings - Fork 2
일정 알림 기능 개발 #167
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
base: develop
Are you sure you want to change the base?
일정 알림 기능 개발 #167
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package im.toduck.domain.notification.domain.event; | ||
|
|
||
| import im.toduck.domain.notification.domain.data.ScheduleReminderData; | ||
| import im.toduck.domain.notification.persistence.entity.NotificationType; | ||
| import im.toduck.domain.schedule.persistence.vo.ScheduleAlram; | ||
| import lombok.AccessLevel; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
| public class ScheduleReminderNotificationEvent extends NotificationEvent<ScheduleReminderData> { | ||
|
|
||
| private ScheduleReminderNotificationEvent(final Long userId, final ScheduleReminderData data) { | ||
| super(userId, NotificationType.SCHEDULE_REMINDER, data); | ||
| } | ||
|
|
||
| public static ScheduleReminderNotificationEvent of( | ||
| final Long userId, | ||
| final Long scheduleId, | ||
| final String scheduleTitle, | ||
| final ScheduleAlram reminderType, | ||
| final boolean isAllDay) { | ||
| return new ScheduleReminderNotificationEvent( | ||
| userId, | ||
| ScheduleReminderData.of(scheduleId, scheduleTitle, reminderType, isAllDay)); | ||
| } | ||
|
|
||
| @Override | ||
| public String getInAppTitle() { | ||
| return ""; | ||
| } | ||
|
|
||
| @Override | ||
| public String getInAppBody() { | ||
| return ""; | ||
| } | ||
|
|
||
| @Override | ||
| public String getPushTitle() { | ||
| return getData().getScheduleTitle(); | ||
| } | ||
|
|
||
| @Override | ||
| public String getPushBody() { | ||
| if (getData().isAllDay()) { | ||
| return "일정 하루 전! 준비된 하루를 시작해볼까요? 📅"; | ||
| } | ||
|
|
||
| ScheduleAlram reminderType = getData().getReminderType(); | ||
| return String.format("일정 %d분 전! 준비된 하루를 시작해볼까요? 📅", reminderType.getMinutes()); | ||
| } | ||
|
|
||
| @Override | ||
| public String getActionUrl() { | ||
| return "toduck://todo"; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package im.toduck.domain.schedule.domain.event; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public class ScheduleCreatedEvent { | ||
| private final Long scheduleId; | ||
| private final Long userId; | ||
|
|
||
| public ScheduleCreatedEvent(final Long scheduleId, final Long userId) { | ||
| this.scheduleId = scheduleId; | ||
| this.userId = userId; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package im.toduck.domain.schedule.domain.event; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public class ScheduleDeletedEvent { | ||
| private final Long scheduleId; | ||
|
|
||
| public ScheduleDeletedEvent(final Long scheduleId) { | ||
| this.scheduleId = scheduleId; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,75 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| package im.toduck.domain.schedule.domain.event; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import java.time.LocalDate; | ||||||||||||||||||||||||||||||||||||||||||||
| import java.time.LocalDateTime; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.scheduling.annotation.Async; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.transaction.event.TransactionPhase; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.transaction.event.TransactionalEventListener; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import im.toduck.domain.schedule.domain.service.ScheduleReadService; | ||||||||||||||||||||||||||||||||||||||||||||
| import im.toduck.domain.schedule.domain.service.ScheduleReminderSchedulerService; | ||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Slf4j | ||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||
| @ConditionalOnProperty(name = "spring.quartz.auto-startup", havingValue = "true", matchIfMissing = true) | ||||||||||||||||||||||||||||||||||||||||||||
| public class ScheduleReminderEventListener { | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private final ScheduleReadService scheduleReadService; | ||||||||||||||||||||||||||||||||||||||||||||
| private final ScheduleReminderSchedulerService scheduleReminderSchedulerService; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Async | ||||||||||||||||||||||||||||||||||||||||||||
| @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||||||||||||||||||||||||||||||||||||||||||||
| public void handleScheduleCreated(final ScheduleCreatedEvent event) { | ||||||||||||||||||||||||||||||||||||||||||||
| log.info("일정 생성 이벤트 처리 시작 - ScheduleId: {}", event.getScheduleId()); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| scheduleReadService.getScheduleById(event.getScheduleId()) | ||||||||||||||||||||||||||||||||||||||||||||
| .ifPresent(schedule -> { | ||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime currentDateTime = LocalDateTime.now(); | ||||||||||||||||||||||||||||||||||||||||||||
| scheduleReminderSchedulerService.scheduleScheduleReminders(schedule, currentDateTime, false); | ||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||
| log.error("일정 생성 이벤트 처리 중 오류 발생 - ScheduleId: {}", event.getScheduleId(), e); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Async | ||||||||||||||||||||||||||||||||||||||||||||
| @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||||||||||||||||||||||||||||||||||||||||||||
| public void handleScheduleUpdated(final ScheduleUpdatedEvent event) { | ||||||||||||||||||||||||||||||||||||||||||||
| log.info("일정 수정 이벤트 처리 시작 - ScheduleId: {}", event.getScheduleId()); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| if (event.isReminderRelatedChanged()) { | ||||||||||||||||||||||||||||||||||||||||||||
| scheduleReminderSchedulerService.cancelFutureScheduleReminders( | ||||||||||||||||||||||||||||||||||||||||||||
| event.getScheduleId(), LocalDate.now()); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| scheduleReadService.getScheduleById(event.getScheduleId()) | ||||||||||||||||||||||||||||||||||||||||||||
| .ifPresent(schedule -> { | ||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime currentDateTime = LocalDateTime.now(); | ||||||||||||||||||||||||||||||||||||||||||||
| scheduleReminderSchedulerService.scheduleScheduleReminders( | ||||||||||||||||||||||||||||||||||||||||||||
| schedule, currentDateTime, false); | ||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+47
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 취소 후 조회 실패 시 알림이 유실될 수 있습니다. Line 49-50에서 기존 알림을 먼저 취소한 후, Line 52에서 스케줄을 다시 조회합니다. 만약 스케줄 조회를 먼저 수행하고, 성공한 경우에만 취소 + 재스케줄링을 진행하는 것을 권장합니다. 🐛 제안: 조회 후 취소 순서로 변경 try {
if (event.isReminderRelatedChanged()) {
- scheduleReminderSchedulerService.cancelFutureScheduleReminders(
- event.getScheduleId(), LocalDate.now());
-
scheduleReadService.getScheduleById(event.getScheduleId())
.ifPresent(schedule -> {
+ scheduleReminderSchedulerService.cancelFutureScheduleReminders(
+ event.getScheduleId(), LocalDate.now());
LocalDateTime currentDateTime = LocalDateTime.now();
scheduleReminderSchedulerService.scheduleScheduleReminders(
schedule, currentDateTime, false);
});
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||
| log.error("일정 수정 이벤트 처리 중 오류 발생 - ScheduleId: {}", event.getScheduleId(), e); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Async | ||||||||||||||||||||||||||||||||||||||||||||
| @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||||||||||||||||||||||||||||||||||||||||||||
| public void handleScheduleDeleted(final ScheduleDeletedEvent event) { | ||||||||||||||||||||||||||||||||||||||||||||
| log.info("일정 삭제 이벤트 처리 시작 - ScheduleId: {}", event.getScheduleId()); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| scheduleReminderSchedulerService.cancelAllScheduleReminders(event.getScheduleId()); | ||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||
| log.error("일정 삭제 이벤트 처리 중 오류 발생 - ScheduleId: {}", event.getScheduleId(), e); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package im.toduck.domain.schedule.domain.event; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public class ScheduleUpdatedEvent { | ||
| private final Long scheduleId; | ||
| private final Long userId; | ||
| private final boolean isAlarmChanged; | ||
| private final boolean isTimeChanged; | ||
| private final boolean isAllDayChanged; | ||
| private final boolean isTitleChanged; | ||
| private final boolean isDateChanged; | ||
| private final boolean isDaysOfWeekChanged; | ||
|
|
||
| public ScheduleUpdatedEvent( | ||
| final Long scheduleId, | ||
| final Long userId, | ||
| final boolean isAlarmChanged, | ||
| final boolean isTimeChanged, | ||
| final boolean isAllDayChanged, | ||
| final boolean isTitleChanged, | ||
| final boolean isDateChanged, | ||
| final boolean isDaysOfWeekChanged) { | ||
| this.scheduleId = scheduleId; | ||
| this.userId = userId; | ||
| this.isAlarmChanged = isAlarmChanged; | ||
| this.isTimeChanged = isTimeChanged; | ||
| this.isAllDayChanged = isAllDayChanged; | ||
| this.isTitleChanged = isTitleChanged; | ||
| this.isDateChanged = isDateChanged; | ||
| this.isDaysOfWeekChanged = isDaysOfWeekChanged; | ||
| } | ||
|
|
||
| public boolean isReminderRelatedChanged() { | ||
| return isAlarmChanged || isTimeChanged || isAllDayChanged || isTitleChanged | ||
| || isDateChanged || isDaysOfWeekChanged; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ONE_DAYenum이isAllDay=false와 조합될 경우 "1440분 전" 메시지가 표시됩니다.현재
getPushBody는isAllDay플래그만으로 분기하므로,ScheduleAlram.ONE_DAY가 비-종일 일정에 사용되면"일정 1440분 전!"이라는 부자연스러운 메시지가 생성됩니다. 현재 비즈니스 로직상 이 조합이 발생하지 않는다면 방어 코드나 주석을 추가하는 것을 권장합니다.🛡️ 방어 로직 예시
`@Override` public String getPushBody() { if (getData().isAllDay()) { return "일정 하루 전! 준비된 하루를 시작해볼까요? 📅"; } ScheduleAlram reminderType = getData().getReminderType(); + if (reminderType.getMinutes() >= 1440) { + return "일정 하루 전! 준비된 하루를 시작해볼까요? 📅"; + } return String.format("일정 %d분 전! 준비된 하루를 시작해볼까요? 📅", reminderType.getMinutes()); }📝 Committable suggestion
🤖 Prompt for AI Agents