-
Notifications
You must be signed in to change notification settings - Fork 1
Feature/198 모집글 스케쥴링 #205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/198 모집글 스케쥴링 #205
Changes from all commits
34f8483
27f0fdb
3cc7c55
93af619
b1f208c
cec4d91
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,53 @@ | ||||||||||||||||||||||
| package com.somemore.recruitboard.scheduler; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import static com.somemore.recruitboard.domain.RecruitStatus.CLOSED; | ||||||||||||||||||||||
| import static com.somemore.recruitboard.domain.RecruitStatus.RECRUITING; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import com.somemore.recruitboard.domain.RecruitBoard; | ||||||||||||||||||||||
| import com.somemore.recruitboard.repository.RecruitBoardRepository; | ||||||||||||||||||||||
| import java.time.LocalDate; | ||||||||||||||||||||||
| import java.time.LocalDateTime; | ||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||
| import org.springframework.scheduling.annotation.Scheduled; | ||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||
| import org.springframework.transaction.annotation.Transactional; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @Slf4j | ||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||
| @Component | ||||||||||||||||||||||
| public class RecruitBoardStatusUpdateScheduler { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private final RecruitBoardRepository recruitBoardRepository; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @Scheduled(cron = "${spring.schedules.cron.updateBoardsToClosed}") | ||||||||||||||||||||||
| public synchronized void transitionBoardsToClosed() { | ||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. synchronized 키워드는 왜 적으셨는지 궁금합니다! |
||||||||||||||||||||||
| log.info("봉사 시작일에 해당하는 모집글 상태를 CLOSED로 변경하는 작업 시작"); | ||||||||||||||||||||||
| LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); | ||||||||||||||||||||||
| LocalDateTime startOfNextDay = LocalDate.now().plusDays(1).atStartOfDay(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| List<RecruitBoard> boards = recruitBoardRepository.findByStartDateTimeBetweenAndStatus( | ||||||||||||||||||||||
| startOfDay, startOfNextDay, RECRUITING); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| boards.forEach(RecruitBoard::markAsClosed); | ||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아니면 여기서 실패 로직이 나오면 데드 레터 큐 방식으로 레디스로 서버 이벤트를 발행하고 처리할 수 있을 것 같아요~~
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| recruitBoardRepository.saveAll(boards); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @Scheduled(cron = "${spring.schedules.cron.updateBoardsToCompleted}") | ||||||||||||||||||||||
| public synchronized void transitionBoardsToCompleted() { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| log.info("봉사 종료일에 해당하는 모집글 상태를 COMPLETED로 변경하는 작업을 시작"); | ||||||||||||||||||||||
| LocalDateTime now = LocalDateTime.now(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| List<RecruitBoard> boards = recruitBoardRepository.findByEndDateTimeBeforeAndStatus( | ||||||||||||||||||||||
| now, CLOSED); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| boards.forEach(RecruitBoard::markAsCompleted); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| recruitBoardRepository.saveAll(boards); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,6 +68,11 @@ spring: | |
| max-file-size: 8MB | ||
| max-request-size: 8MB | ||
|
|
||
| schedules: | ||
| cron: | ||
| updateBoardsToClosed: "0 0 0 * * ?" # updateBoardsToClosed 스케줄링 cron 표현식 | ||
| updateBoardsToCompleted: "0 0 0 * * ?" # updateBoardsToCompleted 스케줄링 cron 표현식 | ||
|
|
||
|
Comment on lines
+71
to
+75
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 이것도 뺄수가 있군요 |
||
|
|
||
| #swagger | ||
| springdoc: | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 00시와 24시를 나눠주는 친절한 범수 님! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| package com.somemore.recruitboard.scheduler; | ||
|
|
||
| import static com.somemore.common.fixture.RecruitBoardFixture.createRecruitBoard; | ||
| import static com.somemore.recruitboard.domain.RecruitStatus.CLOSED; | ||
| import static com.somemore.recruitboard.domain.RecruitStatus.COMPLETED; | ||
| import static com.somemore.recruitboard.domain.RecruitStatus.RECRUITING; | ||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
||
| import com.somemore.IntegrationTestSupport; | ||
| import com.somemore.recruitboard.domain.RecruitBoard; | ||
| import com.somemore.recruitboard.domain.RecruitStatus; | ||
| import com.somemore.recruitboard.repository.RecruitBoardRepository; | ||
| import java.time.LocalDate; | ||
| import java.time.LocalDateTime; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.concurrent.TimeUnit; | ||
| import org.awaitility.Awaitility; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Transactional | ||
| class RecruitBoardStatusUpdateSchedulerTest extends IntegrationTestSupport { | ||
|
|
||
| @Autowired | ||
| private RecruitBoardRepository recruitBoardRepository; | ||
|
|
||
| @DisplayName("스케쥴링으로 봉사 시작일 00시에 모집글 상태가 모집 완료로 변경 된다.") | ||
| @Test | ||
| void transitionBoardsToClosed() { | ||
| // given | ||
| LocalDateTime start = LocalDate.now().atStartOfDay().plusHours(1); | ||
| LocalDateTime end = start.plusHours(2); | ||
|
|
||
| List<RecruitBoard> recruitBoards = getRecruitBoards(start, end, RECRUITING); | ||
| recruitBoardRepository.saveAll(recruitBoards); | ||
|
|
||
| // when | ||
| // then | ||
| Awaitility.await() | ||
| .atMost(3, TimeUnit.SECONDS) | ||
| .untilAsserted(() -> { | ||
| List<RecruitBoard> updatedBoards = recruitBoardRepository.findAllByIds( | ||
| recruitBoards.stream().map(RecruitBoard::getId).toList()); | ||
| assertThat(updatedBoards) | ||
| .allMatch(board -> board.getRecruitStatus() == CLOSED); | ||
| }); | ||
| } | ||
|
|
||
| @DisplayName("스케쥴링으로 봉사 종료일 24시에 모집글 상태가 종료로 변경 된다.") | ||
| @Test | ||
| void transitionBoardsToCompleted() { | ||
| // given | ||
| LocalDateTime start = LocalDate.now().atStartOfDay().minusHours(12); | ||
| LocalDateTime end = start.plusHours(2); | ||
|
|
||
| List<RecruitBoard> recruitBoards = getRecruitBoards(start, end, CLOSED); | ||
| recruitBoardRepository.saveAll(recruitBoards); | ||
|
|
||
| // when | ||
| // then | ||
| Awaitility.await() | ||
| .atMost(3, TimeUnit.SECONDS) | ||
| .untilAsserted(() -> { | ||
| List<RecruitBoard> updatedBoards = recruitBoardRepository.findAllByIds( | ||
| recruitBoards.stream().map(RecruitBoard::getId).toList()); | ||
| assertThat(updatedBoards) | ||
| .allMatch(board -> board.getRecruitStatus() == COMPLETED); | ||
| }); | ||
| } | ||
|
|
||
| private List<RecruitBoard> getRecruitBoards(LocalDateTime start, LocalDateTime end, | ||
| RecruitStatus status) { | ||
|
|
||
| List<RecruitBoard> boards = new ArrayList<>(); | ||
|
|
||
| for (int i = 0; i < 10; i++) { | ||
| RecruitBoard board = createRecruitBoard(start, end, status); | ||
| boards.add(board); | ||
| } | ||
|
|
||
| return boards; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이런 재시도 로직도 가능할 것 같아요!