From fb63f733ddad8460ebe534ca42307c614351b10b Mon Sep 17 00:00:00 2001 From: dongE Date: Thu, 15 Jan 2026 23:10:26 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat=20:=20retry=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20config=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eeos/build.gradle.kts | 5 +++++ eeos/gradle/libs.versions.toml | 9 +++++++++ .../java/com/blackcompany/eeos/config/RetryConfig.java | 9 +++++++++ 3 files changed, 23 insertions(+) create mode 100644 eeos/src/main/java/com/blackcompany/eeos/config/RetryConfig.java diff --git a/eeos/build.gradle.kts b/eeos/build.gradle.kts index 9f8bc691..e4279703 100644 --- a/eeos/build.gradle.kts +++ b/eeos/build.gradle.kts @@ -67,6 +67,11 @@ dependencies { // Firebase Admin SDK implementation(libs.firebase.admin) + implementation(libs.spring.retry) + implementation(libs.spring.aspects) + + + } dependencyManagement { diff --git a/eeos/gradle/libs.versions.toml b/eeos/gradle/libs.versions.toml index 67f0a322..9c561931 100644 --- a/eeos/gradle/libs.versions.toml +++ b/eeos/gradle/libs.versions.toml @@ -22,6 +22,10 @@ spring-security-test="6.5.2" # Database flyway = "10.7.1" +# Retry +spring-retry = "2.0.5" + + [libraries] # Spring Boot spring-boot-starter-web = { group = "org.springframework.boot", name = "spring-boot-starter-web" } @@ -61,6 +65,11 @@ lombok = { group = "org.projectlombok", name = "lombok" } # Firebase Admin SDK firebase-admin = { group = "com.google.firebase", name = "firebase-admin", version.ref = "firebase-admin" } +# Retry +spring-retry = { group = "org.springframework.retry", name = "spring-retry", version.ref = "spring-retry" } +spring-aspects = { group = "org.springframework", name = "spring-aspects" } + + [plugins] spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" } spring-dependency-management = { id = "io.spring.dependency-management", version.ref = "spring-dependency-management" } diff --git a/eeos/src/main/java/com/blackcompany/eeos/config/RetryConfig.java b/eeos/src/main/java/com/blackcompany/eeos/config/RetryConfig.java new file mode 100644 index 00000000..71014fcd --- /dev/null +++ b/eeos/src/main/java/com/blackcompany/eeos/config/RetryConfig.java @@ -0,0 +1,9 @@ +package com.blackcompany.eeos.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.retry.annotation.EnableRetry; + +@Configuration +@EnableRetry +public class RetryConfig { +} From 61b262c6036f079ea9b8da378a3fcf73dc5cc6e5 Mon Sep 17 00:00:00 2001 From: dongE Date: Thu, 15 Jan 2026 23:11:28 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat=20:=20recover=20=EB=A1=9C=EC=A7=81=20-?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20=EB=B0=B0=EC=B9=98=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?retry=20=EB=AA=A8=EB=91=90=20=EC=8B=A4=ED=8C=A8=EC=8B=9C=20?= =?UTF-8?q?=EC=8A=AC=EB=9E=99=20=EC=95=8C=EB=A6=BC=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheduler/PushTokenCleanScheduler.java | 55 ++++++++++++++++++ .../service/SlackNotificationService.java | 56 +++++++++++++++++++ eeos/src/main/resources/application-slack.yml | 2 + 3 files changed, 113 insertions(+) create mode 100644 eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java create mode 100644 eeos/src/main/java/com/blackcompany/eeos/notification/application/service/SlackNotificationService.java diff --git a/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java b/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java new file mode 100644 index 00000000..b77760ed --- /dev/null +++ b/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java @@ -0,0 +1,55 @@ +package com.blackcompany.eeos.notification.application.scheduler; + +import java.time.LocalDateTime; + +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Recover; +import org.springframework.retry.annotation.Retryable; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import com.blackcompany.eeos.notification.application.repository.MemberPushTokenRepository; +import com.blackcompany.eeos.notification.application.service.SlackNotificationService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Component +@Slf4j +@RequiredArgsConstructor +public class PushTokenCleanScheduler { + + private final MemberPushTokenRepository memberPushTokenRepository; + private final SlackNotificationService slackNotificationService; + + private static final int INACTIVE_DAYS_THRESHOLD = 90; + private static final String SCHEDULER_NAME = "비활성화 토큰 삭제 스케줄러"; + + /* + * 매주 토요일 새벽 3시 90일 이상 비활성화된 푸시 토큰 삭제 + * cron : 초 분 시 일 월 요일(6=토요일) + * */ + + @Scheduled(cron = "0 0 3 * * 6") + @Transactional + @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 2000), recover = "recoverDeleteInactiveTokens") + public void deleteInactiveTokens(){ + log.info("{} 시작", SCHEDULER_NAME); + LocalDateTime limitDate = LocalDateTime.now().minusDays(INACTIVE_DAYS_THRESHOLD); + int deleteCount = memberPushTokenRepository.deleteByLastActiveAtBefore(limitDate); + log.info("{}개의 비활성 푸시 토큰 삭제 완료 (기준일: {})", deleteCount, limitDate); + } + + @Recover + public void recoverDeleteInactiveTokens(Exception e){ + log.error("{} 실행 실패 - 모든 재시도 소진", SCHEDULER_NAME, e); + + String errorMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); + + if(errorMessage.length()>300){ + errorMessage = errorMessage.substring(0, 300) + "...생략"; + } + slackNotificationService.sendSchedulerFailureMessage(SCHEDULER_NAME, errorMessage); + } +} diff --git a/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/SlackNotificationService.java b/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/SlackNotificationService.java new file mode 100644 index 00000000..d78dc807 --- /dev/null +++ b/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/SlackNotificationService.java @@ -0,0 +1,56 @@ +package com.blackcompany.eeos.notification.application.service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import com.blackcompany.eeos.program.infra.api.slack.chat.client.SlackChatApiClient; +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +public class SlackNotificationService { + + private final SlackChatApiClient slackChatApiClient; + private final ObjectMapper objectMapper; + + @Value("${slack.bot.black-company.eeos}") + private String botToken; + + @Value("${slack.channel.black-company.error-report}") + private String errorReportChannel; + + public void sendSchedulerFailureMessage(String schedulerName, String errorMessage){ + String message = String.format( + ":rotating_light: *스케줄러 실행 실패 알림*\n\n" + + "*Scheduler*\n `%s`\n\n" + + "*Failed At*\n`%s`\n\n" + + "*Error Message*\n`%s`\n\n", + schedulerName, + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), + errorMessage + ); + sendMessage(message); + } + + private void sendMessage(String text){ + try{ + String blocks = objectMapper.writeValueAsString(List.of(Map.of("type", "section", "text", Map.of("type","mrkdwn", "text", text)))); + + slackChatApiClient.post( + "Bearer " + botToken, errorReportChannel, blocks, "EEOS Scheduler Bot"); + + } catch (Exception e) { + log.error("Slack 알림 전송 실패", e); + + } + } +} diff --git a/eeos/src/main/resources/application-slack.yml b/eeos/src/main/resources/application-slack.yml index 8a4c8624..8d57033e 100644 --- a/eeos/src/main/resources/application-slack.yml +++ b/eeos/src/main/resources/application-slack.yml @@ -14,6 +14,8 @@ slack: black-company: slack-message-test: ${BLACK_COMPANY_TEST_CHANNEL_ID} notification : ${BLACK_COMPANY_NOTIFICATION_CHANNEL_ID} + error-report : ${BLACK_COMPANY_ERROR_REPORT_CHANNEL_ID} + econovation: notification: ${ECONOVATION_NOTIFICATION_CHANNEL_ID} small_talk: ${ECONOVATION_SMALL_TALK_CHANNEL_ID} From 8b32bfc10b4c40b2b0305879b58ecb97e5be582d Mon Sep 17 00:00:00 2001 From: dongE Date: Thu, 15 Jan 2026 23:12:19 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat=20:=20=EB=B9=84=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94=20=ED=86=A0=ED=81=B0=20=EC=82=AD=EC=A0=9C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/repository/MemberPushTokenRepository.java | 2 ++ .../persistence/JpaMemberPushTokenRepository.java | 5 +++++ .../eeos/notification/persistence/MemberPushTokenEntity.java | 4 ++-- .../persistence/MemberPushTokenRepositoryImpl.java | 5 +++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/eeos/src/main/java/com/blackcompany/eeos/notification/application/repository/MemberPushTokenRepository.java b/eeos/src/main/java/com/blackcompany/eeos/notification/application/repository/MemberPushTokenRepository.java index b7754175..17db4b3d 100644 --- a/eeos/src/main/java/com/blackcompany/eeos/notification/application/repository/MemberPushTokenRepository.java +++ b/eeos/src/main/java/com/blackcompany/eeos/notification/application/repository/MemberPushTokenRepository.java @@ -23,4 +23,6 @@ List findByMemberIdAndProvider( int deleteByUpdatedDateBefore(LocalDateTime updatedDate); MemberPushTokenModel save(MemberPushTokenModel memberPushToken); + + int deleteByLastActiveAtBefore(LocalDateTime limitDate); } diff --git a/eeos/src/main/java/com/blackcompany/eeos/notification/persistence/JpaMemberPushTokenRepository.java b/eeos/src/main/java/com/blackcompany/eeos/notification/persistence/JpaMemberPushTokenRepository.java index bc8a0eb1..24392acb 100644 --- a/eeos/src/main/java/com/blackcompany/eeos/notification/persistence/JpaMemberPushTokenRepository.java +++ b/eeos/src/main/java/com/blackcompany/eeos/notification/persistence/JpaMemberPushTokenRepository.java @@ -2,6 +2,7 @@ import com.blackcompany.eeos.notification.application.model.NotificationProvider; import java.sql.Timestamp; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -27,4 +28,8 @@ List findByMemberIdAndProvider( void deleteByPushToken(String token); int deleteByUpdatedDateBefore(Timestamp updatedDate); + + @Modifying + @Query("DELETE FROM MemberPushTokenEntity t where t.lastActiveAt < :limitDate") + int deleteByLastActiveAtBefore(@Param("limitDate") LocalDateTime limitDate); } diff --git a/eeos/src/main/java/com/blackcompany/eeos/notification/persistence/MemberPushTokenEntity.java b/eeos/src/main/java/com/blackcompany/eeos/notification/persistence/MemberPushTokenEntity.java index c4f3bdaa..88a1940e 100644 --- a/eeos/src/main/java/com/blackcompany/eeos/notification/persistence/MemberPushTokenEntity.java +++ b/eeos/src/main/java/com/blackcompany/eeos/notification/persistence/MemberPushTokenEntity.java @@ -24,10 +24,10 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @ToString @SuperBuilder(toBuilder = true) -@Table(name = "member_push_token") +@Table(name = "Notification_token") public class MemberPushTokenEntity extends BaseEntity { - public static final String ENTITY_PREFIX = "member_push_token"; + public static final String ENTITY_PREFIX = "Notification_token"; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/eeos/src/main/java/com/blackcompany/eeos/notification/persistence/MemberPushTokenRepositoryImpl.java b/eeos/src/main/java/com/blackcompany/eeos/notification/persistence/MemberPushTokenRepositoryImpl.java index f449b2dd..5fcdacc4 100644 --- a/eeos/src/main/java/com/blackcompany/eeos/notification/persistence/MemberPushTokenRepositoryImpl.java +++ b/eeos/src/main/java/com/blackcompany/eeos/notification/persistence/MemberPushTokenRepositoryImpl.java @@ -65,4 +65,9 @@ public MemberPushTokenModel save(MemberPushTokenModel memberPushToken) { MemberPushTokenEntity saved = jpaRepository.save(memberPushTokenEntity); return converter.from(saved); } + + @Override + public int deleteByLastActiveAtBefore(LocalDateTime limitDate) { + return jpaRepository.deleteByLastActiveAtBefore(limitDate); + } } From d7878dd9496879490dbb52f8987914b018ae960e Mon Sep 17 00:00:00 2001 From: dongE Date: Thu, 15 Jan 2026 23:13:13 +0900 Subject: [PATCH 4/7] chore : spotless apply --- .../blackcompany/eeos/config/RetryConfig.java | 3 +- .../scheduler/PushTokenCleanScheduler.java | 28 ++++++------- .../service/SlackNotificationService.java | 42 +++++++++---------- 3 files changed, 35 insertions(+), 38 deletions(-) diff --git a/eeos/src/main/java/com/blackcompany/eeos/config/RetryConfig.java b/eeos/src/main/java/com/blackcompany/eeos/config/RetryConfig.java index 71014fcd..678e2e47 100644 --- a/eeos/src/main/java/com/blackcompany/eeos/config/RetryConfig.java +++ b/eeos/src/main/java/com/blackcompany/eeos/config/RetryConfig.java @@ -5,5 +5,4 @@ @Configuration @EnableRetry -public class RetryConfig { -} +public class RetryConfig {} diff --git a/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java b/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java index b77760ed..094a9546 100644 --- a/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java +++ b/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java @@ -1,7 +1,10 @@ package com.blackcompany.eeos.notification.application.scheduler; +import com.blackcompany.eeos.notification.application.repository.MemberPushTokenRepository; +import com.blackcompany.eeos.notification.application.service.SlackNotificationService; import java.time.LocalDateTime; - +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Recover; import org.springframework.retry.annotation.Retryable; @@ -9,12 +12,6 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import com.blackcompany.eeos.notification.application.repository.MemberPushTokenRepository; -import com.blackcompany.eeos.notification.application.service.SlackNotificationService; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - @Component @Slf4j @RequiredArgsConstructor @@ -27,14 +24,17 @@ public class PushTokenCleanScheduler { private static final String SCHEDULER_NAME = "비활성화 토큰 삭제 스케줄러"; /* - * 매주 토요일 새벽 3시 90일 이상 비활성화된 푸시 토큰 삭제 - * cron : 초 분 시 일 월 요일(6=토요일) - * */ + * 매주 토요일 새벽 3시 90일 이상 비활성화된 푸시 토큰 삭제 + * cron : 초 분 시 일 월 요일(6=토요일) + * */ @Scheduled(cron = "0 0 3 * * 6") @Transactional - @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 2000), recover = "recoverDeleteInactiveTokens") - public void deleteInactiveTokens(){ + @Retryable( + maxAttempts = 3, + backoff = @Backoff(delay = 2000), + recover = "recoverDeleteInactiveTokens") + public void deleteInactiveTokens() { log.info("{} 시작", SCHEDULER_NAME); LocalDateTime limitDate = LocalDateTime.now().minusDays(INACTIVE_DAYS_THRESHOLD); int deleteCount = memberPushTokenRepository.deleteByLastActiveAtBefore(limitDate); @@ -42,12 +42,12 @@ public void deleteInactiveTokens(){ } @Recover - public void recoverDeleteInactiveTokens(Exception e){ + public void recoverDeleteInactiveTokens(Exception e) { log.error("{} 실행 실패 - 모든 재시도 소진", SCHEDULER_NAME, e); String errorMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); - if(errorMessage.length()>300){ + if (errorMessage.length() > 300) { errorMessage = errorMessage.substring(0, 300) + "...생략"; } slackNotificationService.sendSchedulerFailureMessage(SCHEDULER_NAME, errorMessage); diff --git a/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/SlackNotificationService.java b/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/SlackNotificationService.java index d78dc807..7e280b4d 100644 --- a/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/SlackNotificationService.java +++ b/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/SlackNotificationService.java @@ -1,18 +1,15 @@ package com.blackcompany.eeos.notification.application.service; +import com.blackcompany.eeos.program.infra.api.slack.chat.client.SlackChatApiClient; +import com.fasterxml.jackson.databind.ObjectMapper; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import com.blackcompany.eeos.program.infra.api.slack.chat.client.SlackChatApiClient; -import com.fasterxml.jackson.databind.ObjectMapper; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; @Slf4j @Service @@ -28,29 +25,30 @@ public class SlackNotificationService { @Value("${slack.channel.black-company.error-report}") private String errorReportChannel; - public void sendSchedulerFailureMessage(String schedulerName, String errorMessage){ - String message = String.format( - ":rotating_light: *스케줄러 실행 실패 알림*\n\n" + - "*Scheduler*\n `%s`\n\n" + - "*Failed At*\n`%s`\n\n" + - "*Error Message*\n`%s`\n\n", - schedulerName, - LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), - errorMessage - ); + public void sendSchedulerFailureMessage(String schedulerName, String errorMessage) { + String message = + String.format( + ":rotating_light: *스케줄러 실행 실패 알림*\n\n" + + "*Scheduler*\n `%s`\n\n" + + "*Failed At*\n`%s`\n\n" + + "*Error Message*\n`%s`\n\n", + schedulerName, + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), + errorMessage); sendMessage(message); } - private void sendMessage(String text){ - try{ - String blocks = objectMapper.writeValueAsString(List.of(Map.of("type", "section", "text", Map.of("type","mrkdwn", "text", text)))); + private void sendMessage(String text) { + try { + String blocks = + objectMapper.writeValueAsString( + List.of(Map.of("type", "section", "text", Map.of("type", "mrkdwn", "text", text)))); slackChatApiClient.post( - "Bearer " + botToken, errorReportChannel, blocks, "EEOS Scheduler Bot"); + "Bearer " + botToken, errorReportChannel, blocks, "EEOS Scheduler Bot"); } catch (Exception e) { log.error("Slack 알림 전송 실패", e); - } } } From 8b67f8f146f3eadeb7f1b4647dcb024abe092099 Mon Sep 17 00:00:00 2001 From: dongE Date: Sat, 17 Jan 2026 00:25:05 +0900 Subject: [PATCH 5/7] =?UTF-8?q?refactor=20:=20aop=20=ED=94=84=EB=A1=9D?= =?UTF-8?q?=EC=8B=9C=20=EC=88=9C=EC=84=9C=EB=AC=B8=EC=A0=9C,=20transaction?= =?UTF-8?q?al=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/scheduler/PushTokenCleanScheduler.java | 9 ++++----- .../application/service/NotificationTokenService.java | 7 +++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java b/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java index 094a9546..c3ba74e0 100644 --- a/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java +++ b/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java @@ -1,6 +1,7 @@ package com.blackcompany.eeos.notification.application.scheduler; import com.blackcompany.eeos.notification.application.repository.MemberPushTokenRepository; +import com.blackcompany.eeos.notification.application.service.NotificationTokenService; import com.blackcompany.eeos.notification.application.service.SlackNotificationService; import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; @@ -17,8 +18,8 @@ @RequiredArgsConstructor public class PushTokenCleanScheduler { - private final MemberPushTokenRepository memberPushTokenRepository; private final SlackNotificationService slackNotificationService; + private final NotificationTokenService notificationTokenService; private static final int INACTIVE_DAYS_THRESHOLD = 90; private static final String SCHEDULER_NAME = "비활성화 토큰 삭제 스케줄러"; @@ -29,16 +30,14 @@ public class PushTokenCleanScheduler { * */ @Scheduled(cron = "0 0 3 * * 6") - @Transactional @Retryable( maxAttempts = 3, backoff = @Backoff(delay = 2000), recover = "recoverDeleteInactiveTokens") public void deleteInactiveTokens() { log.info("{} 시작", SCHEDULER_NAME); - LocalDateTime limitDate = LocalDateTime.now().minusDays(INACTIVE_DAYS_THRESHOLD); - int deleteCount = memberPushTokenRepository.deleteByLastActiveAtBefore(limitDate); - log.info("{}개의 비활성 푸시 토큰 삭제 완료 (기준일: {})", deleteCount, limitDate); + int deleteCount = notificationTokenService.deleteInactiveTokens(); + log.info("{}개의 비활성화 토큰 삭제 완료", deleteCount); } @Recover diff --git a/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/NotificationTokenService.java b/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/NotificationTokenService.java index bdb01e86..db07d091 100644 --- a/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/NotificationTokenService.java +++ b/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/NotificationTokenService.java @@ -24,6 +24,7 @@ public class NotificationTokenService DeleteMemberPushTokenUsecase { private final MemberPushTokenRepository memberPushTokenRepository; + private static final int INACTIVE_DAYS_THRESHOLD = 90; @Override @Transactional @@ -64,4 +65,10 @@ public void delete(Long memberId, DeleteMemberPushTokenRequest request) { } memberPushTokenRepository.deleteByPushToken(request.getPushToken()); } + + @Transactional + public int deleteInactiveTokens(){ + LocalDateTime limitDate = LocalDateTime.now().minusDays(INACTIVE_DAYS_THRESHOLD); + return memberPushTokenRepository.deleteByLastActiveAtBefore(limitDate); + } } From 947e2b34e55cf122e57de287d6e5a033030e91df Mon Sep 17 00:00:00 2001 From: dongE Date: Sun, 18 Jan 2026 14:14:09 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor=20:=20flyway=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eeos/src/main/resources/db/migration/V1.00.0.0__init_tables.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/eeos/src/main/resources/db/migration/V1.00.0.0__init_tables.sql b/eeos/src/main/resources/db/migration/V1.00.0.0__init_tables.sql index 0a267217..6f539cf3 100644 --- a/eeos/src/main/resources/db/migration/V1.00.0.0__init_tables.sql +++ b/eeos/src/main/resources/db/migration/V1.00.0.0__init_tables.sql @@ -101,4 +101,3 @@ CREATE TABLE `restrict_team_building` ( - From 4e35730252a23b51e033ef7786f326d25626aa90 Mon Sep 17 00:00:00 2001 From: dongE Date: Sun, 18 Jan 2026 14:15:14 +0900 Subject: [PATCH 7/7] =?UTF-8?q?chore=20:=20spotless=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/scheduler/PushTokenCleanScheduler.java | 3 --- .../application/service/NotificationTokenService.java | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java b/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java index c3ba74e0..b912fe95 100644 --- a/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java +++ b/eeos/src/main/java/com/blackcompany/eeos/notification/application/scheduler/PushTokenCleanScheduler.java @@ -1,9 +1,7 @@ package com.blackcompany.eeos.notification.application.scheduler; -import com.blackcompany.eeos.notification.application.repository.MemberPushTokenRepository; import com.blackcompany.eeos.notification.application.service.NotificationTokenService; import com.blackcompany.eeos.notification.application.service.SlackNotificationService; -import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.retry.annotation.Backoff; @@ -11,7 +9,6 @@ import org.springframework.retry.annotation.Retryable; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; @Component @Slf4j diff --git a/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/NotificationTokenService.java b/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/NotificationTokenService.java index db07d091..5ebd7a55 100644 --- a/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/NotificationTokenService.java +++ b/eeos/src/main/java/com/blackcompany/eeos/notification/application/service/NotificationTokenService.java @@ -67,7 +67,7 @@ public void delete(Long memberId, DeleteMemberPushTokenRequest request) { } @Transactional - public int deleteInactiveTokens(){ + public int deleteInactiveTokens() { LocalDateTime limitDate = LocalDateTime.now().minusDays(INACTIVE_DAYS_THRESHOLD); return memberPushTokenRepository.deleteByLastActiveAtBefore(limitDate); }