From 31ce3f3f936b221ac337eac912d32137090720fb Mon Sep 17 00:00:00 2001 From: Chanhae Lee Date: Sun, 12 Apr 2026 22:48:55 +0900 Subject: [PATCH] =?UTF-8?q?fix(notify):=20=EB=B1=83=EC=A7=80=20=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=ED=83=80=EC=9D=B4=EB=B0=8D=20=EC=9D=B4?= =?UTF-8?q?=EC=8A=88=20=ED=95=B4=EA=B2=B0=20=EB=B0=8F=20notificationId=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 트랜잭션 커밋 후 뱃지 동기화하여 타이밍을 맞추기 위해 BadgeSyncEvent 추가, FCM 메시지에 notificationId 추가, 리마인더 알림 notificationId=null 명시 --- .../application/NotificationCommandService.java | 5 +++-- .../application/event/BadgeSyncEvent.java | 16 ++++++++++++++++ .../event/NotificationEventListener.java | 12 ++++++++++++ .../answer/DailyWriteReminderScheduler.java | 1 + .../InactiveUserNotificationScheduler.java | 1 + .../core/dto/NotificationMessageDto.java | 2 ++ .../domain/notification/infra/FcmClient.java | 1 + .../infra/FcmNotificationSender.java | 5 ++--- 8 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/devkor/ifive/nadab/domain/notification/application/event/BadgeSyncEvent.java diff --git a/src/main/java/com/devkor/ifive/nadab/domain/notification/application/NotificationCommandService.java b/src/main/java/com/devkor/ifive/nadab/domain/notification/application/NotificationCommandService.java index 3924ba62..45a02f6d 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/notification/application/NotificationCommandService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/notification/application/NotificationCommandService.java @@ -1,5 +1,6 @@ package com.devkor.ifive.nadab.domain.notification.application; +import com.devkor.ifive.nadab.domain.notification.application.event.BadgeSyncEvent; import com.devkor.ifive.nadab.domain.notification.application.event.NotificationCreatedEvent; import com.devkor.ifive.nadab.domain.notification.core.entity.Notification; import com.devkor.ifive.nadab.domain.notification.core.entity.NotificationGroup; @@ -207,9 +208,9 @@ public void sendTestNotification(Long userId, String title, String body) { /** * 다른 기기로 뱃지 개수 동기화 (Silent Badge Update) - * - 비동기로 즉시 실행 (실시간 동기화) + * - 이벤트 발행 (트랜잭션 커밋 후 실행) */ private void syncBadgeCount(Long userId) { - fcmNotificationSender.sendSilentBadgeUpdate(userId); + eventPublisher.publishEvent(new BadgeSyncEvent(userId)); } } \ No newline at end of file diff --git a/src/main/java/com/devkor/ifive/nadab/domain/notification/application/event/BadgeSyncEvent.java b/src/main/java/com/devkor/ifive/nadab/domain/notification/application/event/BadgeSyncEvent.java new file mode 100644 index 00000000..a366e22b --- /dev/null +++ b/src/main/java/com/devkor/ifive/nadab/domain/notification/application/event/BadgeSyncEvent.java @@ -0,0 +1,16 @@ +package com.devkor.ifive.nadab.domain.notification.application.event; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * 뱃지 동기화 이벤트 + * - 알림 읽음/삭제 후 다른 기기로 뱃지 개수를 동기화할 때 사용 + * - 트랜잭션 커밋 후 처리 (AFTER_COMMIT) + */ +@Getter +@RequiredArgsConstructor +public class BadgeSyncEvent { + + private final Long userId; +} \ No newline at end of file diff --git a/src/main/java/com/devkor/ifive/nadab/domain/notification/application/event/NotificationEventListener.java b/src/main/java/com/devkor/ifive/nadab/domain/notification/application/event/NotificationEventListener.java index 5f63b731..89894a55 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/notification/application/event/NotificationEventListener.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/notification/application/event/NotificationEventListener.java @@ -87,4 +87,16 @@ public void handleNotificationCreated(NotificationCreatedEvent event) { transactionHelper.markAsFailed(notificationId); } } + + /** + * 뱃지 동기화 이벤트 처리 + * - AFTER_COMMIT: 알림 읽음/삭제 트랜잭션이 커밋된 후 실행 + * - @Async: 비동기로 다른 기기에 뱃지 카운트 동기화 + */ + @Async("fcmTaskExecutor") + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handleBadgeSync(BadgeSyncEvent event) { + log.debug("Badge sync event received: userId={}", event.getUserId()); + fcmSender.sendSilentBadgeUpdate(event.getUserId()); + } } \ No newline at end of file diff --git a/src/main/java/com/devkor/ifive/nadab/domain/notification/application/scheduler/answer/DailyWriteReminderScheduler.java b/src/main/java/com/devkor/ifive/nadab/domain/notification/application/scheduler/answer/DailyWriteReminderScheduler.java index c6ce3a6c..2817ca9e 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/notification/application/scheduler/answer/DailyWriteReminderScheduler.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/notification/application/scheduler/answer/DailyWriteReminderScheduler.java @@ -156,6 +156,7 @@ private boolean sendDailyWriteNotification(User user, List devices) // 4. FCM 푸시 발송 NotificationMessageDto messageDto = NotificationMessageDto.builder() + .notificationId(null) // 알림함에 저장하지 않음 .type(NotificationType.DAILY_WRITE_REMINDER) .title(content.title()) .body(content.body()) diff --git a/src/main/java/com/devkor/ifive/nadab/domain/notification/application/scheduler/answer/InactiveUserNotificationScheduler.java b/src/main/java/com/devkor/ifive/nadab/domain/notification/application/scheduler/answer/InactiveUserNotificationScheduler.java index 02ba1e56..c132868b 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/notification/application/scheduler/answer/InactiveUserNotificationScheduler.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/notification/application/scheduler/answer/InactiveUserNotificationScheduler.java @@ -178,6 +178,7 @@ private boolean sendInactiveUserNotification( // 5. FCM 푸시 발송 NotificationMessageDto messageDto = NotificationMessageDto.builder() + .notificationId(null) // 알림함에 저장하지 않음 .type(NotificationType.INACTIVE_USER_REMINDER) .title(content.title()) .body(content.body()) diff --git a/src/main/java/com/devkor/ifive/nadab/domain/notification/core/dto/NotificationMessageDto.java b/src/main/java/com/devkor/ifive/nadab/domain/notification/core/dto/NotificationMessageDto.java index 2c67477a..08c637af 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/notification/core/dto/NotificationMessageDto.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/notification/core/dto/NotificationMessageDto.java @@ -12,6 +12,7 @@ @Builder public class NotificationMessageDto { + private Long notificationId; // 푸시 알림 탭 시 읽음 처리용 private NotificationType type; private String title; private String body; @@ -21,6 +22,7 @@ public class NotificationMessageDto { public static NotificationMessageDto from(Notification notification, int unreadCount) { return NotificationMessageDto.builder() + .notificationId(notification.getId()) .type(notification.getType()) .title(notification.getTitle()) .body(notification.getBody()) diff --git a/src/main/java/com/devkor/ifive/nadab/domain/notification/infra/FcmClient.java b/src/main/java/com/devkor/ifive/nadab/domain/notification/infra/FcmClient.java index 459cc799..f93dabb3 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/notification/infra/FcmClient.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/notification/infra/FcmClient.java @@ -60,6 +60,7 @@ public List sendMulticast(List fcmTokens, NotificationMessageDto .setTitle(message.getTitle()) .setBody(message.getBody()) .build()) + .putData("notificationId", String.valueOf(message.getNotificationId())) .putData("type", message.getType().name()) .putData("targetId", message.getTargetId() != null ? message.getTargetId() : "") .putData("inboxMessage", message.getInboxMessage()) diff --git a/src/main/java/com/devkor/ifive/nadab/domain/notification/infra/FcmNotificationSender.java b/src/main/java/com/devkor/ifive/nadab/domain/notification/infra/FcmNotificationSender.java index 45b458db..0b41d22c 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/notification/infra/FcmNotificationSender.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/notification/infra/FcmNotificationSender.java @@ -221,10 +221,9 @@ private Map> loadNotificationSettings(List /** * Silent Badge Update (알림 없이 뱃지만 업데이트) - * - 알림 읽음/삭제 후 다른 기기 뱃지 동기화 - * - 비동기로 즉시 실행 (실시간 동기화) + * - EventListener에서 호출 (다른 기기 뱃지 동기화) + * - Multicast 발송 */ - @Async("fcmTaskExecutor") public void sendSilentBadgeUpdate(Long userId) { // 사용자 조회 User user = userRepository.findById(userId).orElse(null);