Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
7 changes: 7 additions & 0 deletions database/migrations/20260813_01_shedlock.sql
Original file line number Diff line number Diff line change
@@ -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)
);
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ public void sendTestNotification(Long userId) {
"테스트 알림입니다.",
"/home"
);

log.info(
"테스트 알림 발송 성공. notificationSettingId={}, userId={}",
notificationSetting.getId(),
userId
);
}

public void sendDiaryReminders(LocalDate today) {
Expand All @@ -70,6 +76,12 @@ public void sendDiaryReminders(LocalDate today) {
"오늘 하루를 영어로 천천히 돌아보세요.",
"/home"
);

log.info(
"회고 알림 발송 성공. notificationSettingId={}, userId={}",
target.notificationSettingId(),
target.userId()
);
} catch (PushSubscriptionExpiredException exception) {
try {
pushSubscriptionCleanupService.clearExpiredSubscription(
Expand Down Expand Up @@ -99,4 +111,4 @@ public void sendDiaryReminders(LocalDate today) {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@
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;
import org.apache.http.util.EntityUtils;

@Slf4j
@Service
@RequiredArgsConstructor
public class WebPushService {
Expand Down Expand Up @@ -98,21 +101,27 @@ private void send(
Encoding.AES128GCM
);

HttpResponse response =
webPushHttpClient.execute(request);
try (CloseableHttpResponse response =
webPushHttpClient.execute(request)) {
int statusCode =
response.getStatusLine().getStatusCode();
EntityUtils.consume(response.getEntity());

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 (
Expand All @@ -126,4 +135,4 @@ private void send(
);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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()
);
}
}
Loading