diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index fc84c865..ed04a46e 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -93,6 +93,7 @@ services: - PERFORMANCE_POSTGRES_URL=${PERFORMANCE_POSTGRES_URL} - PERFORMANCE_POSTGRES_USERNAME=${PERFORMANCE_POSTGRES_USERNAME} - PERFORMANCE_POSTGRES_PASSWORD=${PERFORMANCE_POSTGRES_PASSWORD} + - DISCORD_WEBHOOK_URL=${GF_DISCORD_WEBHOOK_URL} - REDIS_NODE_1=${REDIS_NODE_1} - REDIS_NODE_2=${REDIS_NODE_2} - REDIS_NODE_3=${REDIS_NODE_3} diff --git a/services/performance/src/main/java/com/ticketPing/performance/application/scheduler/SeatCacheScheduler.java b/services/performance/src/main/java/com/ticketPing/performance/application/scheduler/SeatCacheScheduler.java index ee9124f4..18f73255 100644 --- a/services/performance/src/main/java/com/ticketPing/performance/application/scheduler/SeatCacheScheduler.java +++ b/services/performance/src/main/java/com/ticketPing/performance/application/scheduler/SeatCacheScheduler.java @@ -2,6 +2,7 @@ import com.ticketPing.performance.application.service.PerformanceService; import com.ticketPing.performance.domain.model.entity.Performance; +import com.ticketPing.performance.infrastructure.service.DiscordNotificationService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RLock; @@ -17,6 +18,7 @@ public class SeatCacheScheduler { private final PerformanceService performanceService; private final RedissonClient redissonClient; + private final DiscordNotificationService discordNotificationService; private static final String LOCK_KEY = "SchedulerLock"; private static final int LOCK_TIMEOUT = 300; @@ -24,24 +26,24 @@ public class SeatCacheScheduler { @Scheduled(cron = "0 0/10 * * * *") public void runScheduler() { try { - log.info("[SeatCacheScheduler] 스케줄러 실행 시작"); + log.info("Scheduler started"); RLock lock = redissonClient.getLock(LOCK_KEY); - boolean acquired = lock.tryLock(10, LOCK_TIMEOUT, TimeUnit.SECONDS); + boolean acquired = lock.tryLock(0, LOCK_TIMEOUT, TimeUnit.SECONDS); if (acquired) { Performance performance = performanceService.getUpcomingPerformance(); if (performance != null) { performanceService.cacheAllSeatsForPerformance(performance.getId()); - log.info("[SeatCacheScheduler] 캐싱 완료"); + log.info("Caching completed"); } else { - log.info("[SeatCacheScheduler] 예정된 공연이 없음"); + log.info("No upcoming performance"); } } else { - log.warn("[SeatCacheScheduler] 다른 서버에서 스케줄러 실행 중"); + log.warn("Another server is running the scheduler"); } } catch (Exception e) { - log.error("[SeatCacheScheduler] 실행 중 오류 발생: {}", e.getMessage(), e); - // TODO: discord 알림? + log.error("Error occurred during execution: {}", e.getMessage(), e); + discordNotificationService.sendErrorNotification(e.getMessage()); } } } diff --git a/services/performance/src/main/java/com/ticketPing/performance/application/service/PerformanceService.java b/services/performance/src/main/java/com/ticketPing/performance/application/service/PerformanceService.java index 9584949e..51b44381 100644 --- a/services/performance/src/main/java/com/ticketPing/performance/application/service/PerformanceService.java +++ b/services/performance/src/main/java/com/ticketPing/performance/application/service/PerformanceService.java @@ -46,7 +46,7 @@ public Performance getUpcomingPerformance() { LocalDateTime now = LocalDateTime.now(); LocalDateTime tenMinutesLater = now.plusMinutes(10); - return performanceRepository.findFirstByReservationStartDateBetween(now, tenMinutesLater); + return performanceRepository.findUpcomingPerformance(now, tenMinutesLater); } public void cacheAllSeatsForPerformance(UUID performanceId) { diff --git a/services/performance/src/main/java/com/ticketPing/performance/common/exception/MessageExceptionCase.java b/services/performance/src/main/java/com/ticketPing/performance/common/exception/MessageExceptionCase.java new file mode 100644 index 00000000..0840dbba --- /dev/null +++ b/services/performance/src/main/java/com/ticketPing/performance/common/exception/MessageExceptionCase.java @@ -0,0 +1,15 @@ +package com.ticketPing.performance.common.exception; + +import exception.ErrorCase; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum MessageExceptionCase implements ErrorCase { + MESSAGE_SEND_FAIL(HttpStatus.INTERNAL_SERVER_ERROR, "알람을 보낼 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/services/performance/src/main/java/com/ticketPing/performance/domain/repository/PerformanceRepository.java b/services/performance/src/main/java/com/ticketPing/performance/domain/repository/PerformanceRepository.java index 4cd988d8..0a33bcf0 100644 --- a/services/performance/src/main/java/com/ticketPing/performance/domain/repository/PerformanceRepository.java +++ b/services/performance/src/main/java/com/ticketPing/performance/domain/repository/PerformanceRepository.java @@ -19,5 +19,5 @@ public interface PerformanceRepository { Optional findByIdWithDetails(UUID id); - Performance findFirstByReservationStartDateBetween(LocalDateTime start, LocalDateTime end); + Performance findUpcomingPerformance(LocalDateTime start, LocalDateTime end); } diff --git a/services/performance/src/main/java/com/ticketPing/performance/infrastructure/config/RestTemplateConfig.java b/services/performance/src/main/java/com/ticketPing/performance/infrastructure/config/RestTemplateConfig.java new file mode 100644 index 00000000..7909ce92 --- /dev/null +++ b/services/performance/src/main/java/com/ticketPing/performance/infrastructure/config/RestTemplateConfig.java @@ -0,0 +1,25 @@ +package com.ticketPing.performance.infrastructure.config; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; + +@Configuration +public class RestTemplateConfig { + @Bean + public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { + return restTemplateBuilder + .requestFactory(() -> new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory())) + .setConnectTimeout(Duration.ofMillis(5000)) + .setReadTimeout(Duration.ofMillis(300000)) + .additionalMessageConverters(new StringHttpMessageConverter(StandardCharsets.UTF_8)) + .build(); + } +} diff --git a/services/performance/src/main/java/com/ticketPing/performance/infrastructure/repository/PerformanceJpaRepository.java b/services/performance/src/main/java/com/ticketPing/performance/infrastructure/repository/PerformanceJpaRepository.java index f1094e4d..8e75cd2f 100644 --- a/services/performance/src/main/java/com/ticketPing/performance/infrastructure/repository/PerformanceJpaRepository.java +++ b/services/performance/src/main/java/com/ticketPing/performance/infrastructure/repository/PerformanceJpaRepository.java @@ -2,13 +2,14 @@ import com.ticketPing.performance.domain.model.entity.Performance; +import java.time.LocalDateTime; import java.util.Optional; import java.util.UUID; import com.ticketPing.performance.domain.repository.PerformanceRepository; -import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository @@ -23,4 +24,10 @@ public interface PerformanceJpaRepository extends PerformanceRepository, JpaRepo "LEFT JOIN FETCH p.seatCosts sc " + "WHERE p.id = :id ") Optional findByIdWithDetails(UUID id); + + @Query("SELECT p " + + "FROM Performance p " + + "WHERE p.reservationStartDate > :now " + + "AND p.reservationStartDate <= :tenMinutesLater") + Performance findUpcomingPerformance(@Param("now") LocalDateTime now, @Param("tenMinutesLater") LocalDateTime tenMinutesLater); } diff --git a/services/performance/src/main/java/com/ticketPing/performance/infrastructure/service/DiscordNotificationService.java b/services/performance/src/main/java/com/ticketPing/performance/infrastructure/service/DiscordNotificationService.java new file mode 100644 index 00000000..a56cdcd4 --- /dev/null +++ b/services/performance/src/main/java/com/ticketPing/performance/infrastructure/service/DiscordNotificationService.java @@ -0,0 +1,40 @@ +package com.ticketPing.performance.infrastructure.service; + +import com.ticketPing.performance.common.exception.MessageExceptionCase; +import exception.ApplicationException; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestTemplate; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DiscordNotificationService { + + @Value("${discord.webhook-url}") + private String discordWebhookUrl; + + private final RestTemplate restTemplate; + + public void sendErrorNotification(String errorMessage) { + try { + String message = String.format("**Error occurred in SeatCacheScheduler**: %s", errorMessage); + String payload = String.format("{\"content\": \"%s\"}", message); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity entity = new HttpEntity<>(payload, headers); + + restTemplate.exchange(discordWebhookUrl, HttpMethod.POST, entity, String.class); + } catch (Exception e) { + log.error("Error occurred during execution: {}", e.getMessage(), e); + throw new ApplicationException(MessageExceptionCase.MESSAGE_SEND_FAIL); + } + } +} diff --git a/services/performance/src/main/resources/application.yml b/services/performance/src/main/resources/application.yml index 7b29e521..86470e92 100644 --- a/services/performance/src/main/resources/application.yml +++ b/services/performance/src/main/resources/application.yml @@ -22,3 +22,5 @@ server: seat: pre-reserve-ttl: 300 +discord: + webhook-url: ${DISCORD_WEBHOOK_URL} \ No newline at end of file