From ad59926891699071450a3ac7d368c0097232a84a Mon Sep 17 00:00:00 2001 From: ownue Date: Thu, 13 Aug 2026 11:07:15 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=EC=8A=A4=EC=BC=80=EC=A4=84=EB=9F=AC?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20=EC=8B=A4=ED=96=89=20=EB=B0=A9=EC=A7=80?= =?UTF-8?q?=20=EB=B0=8F=20=EC=95=8C=EB=A6=BC=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ database/migrations/20260813_01_shedlock.sql | 7 ++++ .../scheduler/DiaryReminderScheduler.java | 6 ++++ .../service/NotificationService.java | 14 +++++++- .../notification/service/WebPushService.java | 35 +++++++++++-------- .../global/config/SchedulingConfig.java | 17 +++++++++ 6 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 database/migrations/20260813_01_shedlock.sql diff --git a/build.gradle b/build.gradle index 17e936c..232762e 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,8 @@ dependencies { // JPA / MySQL implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'com.mysql:mysql-connector-j' + implementation 'net.javacrumbs.shedlock:shedlock-spring:7.7.0' + implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:7.7.0' // Security / JWT implementation 'org.springframework.boot:spring-boot-starter-security' diff --git a/database/migrations/20260813_01_shedlock.sql b/database/migrations/20260813_01_shedlock.sql new file mode 100644 index 0000000..795477b --- /dev/null +++ b/database/migrations/20260813_01_shedlock.sql @@ -0,0 +1,7 @@ +CREATE TABLE shedlock ( + name VARCHAR(64) NOT NULL, + lock_until TIMESTAMP(3) NOT NULL, + locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + locked_by VARCHAR(255) NOT NULL, + PRIMARY KEY (name) +); diff --git a/src/main/java/com/example/todayEng/domain/notification/scheduler/DiaryReminderScheduler.java b/src/main/java/com/example/todayEng/domain/notification/scheduler/DiaryReminderScheduler.java index b0aad69..78103c3 100644 --- a/src/main/java/com/example/todayEng/domain/notification/scheduler/DiaryReminderScheduler.java +++ b/src/main/java/com/example/todayEng/domain/notification/scheduler/DiaryReminderScheduler.java @@ -5,6 +5,7 @@ import java.time.ZoneId; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -22,6 +23,11 @@ public class DiaryReminderScheduler { cron = "${notification.diary-reminder.cron:0 0 22 * * *}", zone = "${notification.diary-reminder.zone:Asia/Seoul}" ) + @SchedulerLock( + name = "diaryReminderScheduler", + lockAtLeastFor = "1m", + lockAtMostFor = "1h" + ) public void sendDiaryReminders() { LocalDate today = LocalDate.now(ZoneId.of(reminderZone)); diff --git a/src/main/java/com/example/todayEng/domain/notification/service/NotificationService.java b/src/main/java/com/example/todayEng/domain/notification/service/NotificationService.java index ea5f38f..2c512b4 100644 --- a/src/main/java/com/example/todayEng/domain/notification/service/NotificationService.java +++ b/src/main/java/com/example/todayEng/domain/notification/service/NotificationService.java @@ -49,6 +49,12 @@ public void sendTestNotification(Long userId) { "테스트 알림입니다.", "/home" ); + + log.info( + "테스트 알림 발송 성공. notificationSettingId={}, userId={}", + notificationSetting.getId(), + userId + ); } public void sendDiaryReminders(LocalDate today) { @@ -70,6 +76,12 @@ public void sendDiaryReminders(LocalDate today) { "오늘 하루를 영어로 천천히 돌아보세요.", "/home" ); + + log.info( + "회고 알림 발송 성공. notificationSettingId={}, userId={}", + target.notificationSettingId(), + target.userId() + ); } catch (PushSubscriptionExpiredException exception) { try { pushSubscriptionCleanupService.clearExpiredSubscription( @@ -99,4 +111,4 @@ public void sendDiaryReminders(LocalDate today) { } } } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/todayEng/domain/notification/service/WebPushService.java b/src/main/java/com/example/todayEng/domain/notification/service/WebPushService.java index 4f86611..a3f83bb 100644 --- a/src/main/java/com/example/todayEng/domain/notification/service/WebPushService.java +++ b/src/main/java/com/example/todayEng/domain/notification/service/WebPushService.java @@ -8,15 +8,17 @@ import java.io.IOException; import java.security.GeneralSecurityException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import nl.martijndwars.webpush.Notification; import nl.martijndwars.webpush.PushService; -import org.apache.http.HttpResponse; import org.jose4j.lang.JoseException; import org.springframework.stereotype.Service; import nl.martijndwars.webpush.Encoding; import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.impl.client.CloseableHttpClient; +@Slf4j @Service @RequiredArgsConstructor public class WebPushService { @@ -98,21 +100,26 @@ private void send( Encoding.AES128GCM ); - HttpResponse response = - webPushHttpClient.execute(request); + try (CloseableHttpResponse response = + webPushHttpClient.execute(request)) { + int statusCode = + response.getStatusLine().getStatusCode(); - int statusCode = - response.getStatusLine().getStatusCode(); + if (statusCode == NOT_FOUND_STATUS + || statusCode == GONE_STATUS) { + throw new PushSubscriptionExpiredException(); + } - if (statusCode == NOT_FOUND_STATUS - || statusCode == GONE_STATUS) { - throw new PushSubscriptionExpiredException(); - } + if (statusCode < 200 || statusCode >= 300) { + throw new IllegalStateException( + "Web Push 전송에 실패했습니다. status=" + + statusCode + ); + } - if (statusCode < 200 || statusCode >= 300) { - throw new IllegalStateException( - "Web Push 전송에 실패했습니다. status=" - + statusCode + log.info( + "Web Push Provider 응답 성공. status={}", + statusCode ); } } catch ( @@ -126,4 +133,4 @@ private void send( ); } } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/todayEng/global/config/SchedulingConfig.java b/src/main/java/com/example/todayEng/global/config/SchedulingConfig.java index 6902907..c7748ed 100644 --- a/src/main/java/com/example/todayEng/global/config/SchedulingConfig.java +++ b/src/main/java/com/example/todayEng/global/config/SchedulingConfig.java @@ -1,9 +1,26 @@ package com.example.todayEng.global.config; +import javax.sql.DataSource; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; +import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.scheduling.annotation.EnableScheduling; @Configuration @EnableScheduling +@EnableSchedulerLock(defaultLockAtMostFor = "1h") public class SchedulingConfig { + + @Bean + public LockProvider lockProvider(DataSource dataSource) { + return new JdbcTemplateLockProvider( + JdbcTemplateLockProvider.Configuration.builder() + .withJdbcTemplate(new JdbcTemplate(dataSource)) + .usingDbTime() + .build() + ); + } } From 90b363a28eed273313355def2b0610ca5817fb50 Mon Sep 17 00:00:00 2001 From: ownue Date: Thu, 13 Aug 2026 11:32:24 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20Web=20Push=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EC=9E=AC=EC=82=AC=EC=9A=A9=20=EB=B3=B4?= =?UTF-8?q?=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todayEng/domain/notification/service/WebPushService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/example/todayEng/domain/notification/service/WebPushService.java b/src/main/java/com/example/todayEng/domain/notification/service/WebPushService.java index a3f83bb..0477852 100644 --- a/src/main/java/com/example/todayEng/domain/notification/service/WebPushService.java +++ b/src/main/java/com/example/todayEng/domain/notification/service/WebPushService.java @@ -17,6 +17,7 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; @Slf4j @Service @@ -104,6 +105,7 @@ private void send( webPushHttpClient.execute(request)) { int statusCode = response.getStatusLine().getStatusCode(); + EntityUtils.consume(response.getEntity()); if (statusCode == NOT_FOUND_STATUS || statusCode == GONE_STATUS) {