-
Notifications
You must be signed in to change notification settings - Fork 1
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
feat : 예매대기 처리 스케줄러 구현 #43
Changes from all commits
a384874
b350dfa
239049b
6cff378
0b86696
7901712
8921b03
566ab5d
5aa39f2
0924434
47d2ab0
6c771ce
5d75bc6
b84ab45
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,40 @@ | ||
package dev.hooon.show.application; | ||
|
||
import java.util.Collection; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import dev.hooon.show.domain.entity.seat.Seat; | ||
import dev.hooon.show.domain.entity.seat.SeatStatus; | ||
import dev.hooon.show.domain.repository.SeatRepository; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class SeatService { | ||
|
||
private final SeatRepository seatRepository; | ||
|
||
// 취소 상태인 좌석의 ID 를 모두 조회 | ||
public Set<Long> getCanceledSeatIds() { | ||
return seatRepository.findByStatusIsCanceled() | ||
.stream() | ||
.map(Seat::getId) | ||
.collect(Collectors.toSet()); | ||
} | ||
|
||
// 좌석을 대기중 상태로 변경 | ||
@Transactional | ||
public void updateSeatToWaiting(Collection<Long> targetIds) { | ||
seatRepository.updateStatusByIdIn(targetIds, SeatStatus.WAITING); | ||
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. 이거 뒤에 In 은 무슨의미인가요?! 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. In 절을 통해 가져온다는 의미에요! |
||
} | ||
|
||
// 좌석을 대기중 예약가능 상태로 변경 | ||
@Transactional | ||
public void updateSeatToAvailable(Collection<Long> targetIds) { | ||
seatRepository.updateStatusByIdIn(targetIds, SeatStatus.AVAILABLE); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,9 @@ public enum SeatStatus { | |
|
||
AVAILABLE("예매 가능"), | ||
BOOKED("예매 완료"), | ||
CANCELED("예매 취소"); | ||
CANCELED("예매 취소"), | ||
// 취소된 좌석이 예매대기로 인해 6시간동안 예매대기를 한 사용자에 한해 예약이 가능한 상태 | ||
WAITING("예매 대기"); | ||
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. 👍🏻👍🏻 |
||
|
||
private final String description; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,22 @@ | ||
package dev.hooon.show.domain.repository; | ||
|
||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
import dev.hooon.show.domain.entity.seat.Seat; | ||
import dev.hooon.show.domain.entity.seat.SeatStatus; | ||
import dev.hooon.show.dto.query.SeatDateRoundDto; | ||
|
||
public interface SeatRepository { | ||
|
||
void saveAll(Iterable<Seat> seats); | ||
|
||
Optional<Seat> findById(Long id); | ||
|
||
List<SeatDateRoundDto> findSeatDateRoundInfoByShowId(Long showId); | ||
|
||
List<Seat> findByStatusIsCanceled(); | ||
|
||
void updateStatusByIdIn(Collection<Long> ids, SeatStatus status); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,5 +6,7 @@ | |
|
||
public interface UserRepository { | ||
|
||
User save(User user); | ||
|
||
Optional<User> findById(Long id); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,9 @@ | ||
package dev.hooon.waitingbooking.application; | ||
|
||
import java.util.List; | ||
|
||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import dev.hooon.user.domain.entity.User; | ||
import dev.hooon.waitingbooking.domain.entity.WaitingBooking; | ||
|
@@ -20,4 +23,15 @@ public WaitingBooking createWaitingBooking(User user, WaitingRegisterRequest req | |
|
||
return waitingBooking; | ||
} | ||
|
||
// 대기 상태인 WaitingBooking 조회 | ||
public List<WaitingBooking> getWaitingBookingsByStatusIsWaiting() { | ||
return waitingBookingRepository.findByStatusIsWaiting(); | ||
} | ||
|
||
// ID 에 해당하는 WaitingBooking ACTIVATION 상태로 변경하고 expireAt 6시간뒤로 설정 | ||
@Transactional | ||
public void activateWaitingBooking(Long waitingBookingId) { | ||
waitingBookingRepository.updateToActiveById(waitingBookingId); | ||
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. 이것도 updateStatusToActivateById 는 어떠신가요? ㅎㅎ (이거도 단순히 제안 ㅎㅎㅎ) 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. 의미상으로 "예약대기를 활성화한다" 라는 의미를 담은 서비스로직이라고 생각해서 제 생각에는 나은거같다고 생각이 듭니다! 수진님이 강력히 원하시는건 아닌거같으니...ㅎㅎ 이대로 가볼게요 ㅎㅎ |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,12 @@ | ||
package dev.hooon.waitingbooking.application.facade; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Set; | ||
|
||
import org.springframework.stereotype.Component; | ||
|
||
import dev.hooon.show.application.SeatService; | ||
import dev.hooon.user.application.UserService; | ||
import dev.hooon.user.domain.entity.User; | ||
import dev.hooon.waitingbooking.application.WaitingBookingService; | ||
|
@@ -16,11 +21,63 @@ public class WaitingBookingFacade { | |
|
||
private final WaitingBookingService waitingBookingService; | ||
private final UserService userService; | ||
private final SeatService seatService; | ||
|
||
// 선택한 좌석중에서 취소좌석에 포함되는 좌석 ID 를 LIST 로 응답 | ||
private List<Long> fetchMatchingSeatIds( | ||
Set<Long> canceledSeatIds, | ||
List<Long> selectedSeatIds, | ||
int seatCount | ||
) { | ||
List<Long> matchSeatIds = new ArrayList<>(); | ||
for (Long selectedSeatId : selectedSeatIds) { | ||
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. 이부분 stream으로 처리는 어려운건가용?? 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. 그 바로 아랫줄에 사이즈가 seatCount 랑 같아지면 break 치는 부분이 있는데 stream forEach 에선 break 를 지원안해서 이렇게 구현했습니다~! |
||
if (canceledSeatIds.contains(selectedSeatId)) { | ||
matchSeatIds.add(selectedSeatId); | ||
} | ||
if (matchSeatIds.size() == seatCount) { | ||
break; | ||
} | ||
} | ||
return matchSeatIds; | ||
} | ||
|
||
public WaitingRegisterResponse registerWaitingBooking(Long userId, WaitingRegisterRequest request) { | ||
User user = userService.getUserById(userId); | ||
WaitingBooking waitingBooking = waitingBookingService.createWaitingBooking(user, request); | ||
|
||
return new WaitingRegisterResponse(waitingBooking.getId()); | ||
} | ||
|
||
public void processWaitingBooking() { | ||
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. 주석으로 설명을 잘 해놓아서 어떤 흐름인지 이해가 잘 되네요 👍🏻👍🏻 |
||
// 1. 취소된 좌석을 모두 조회한다 (PK Set 으로) | ||
Set<Long> canceledSeatIds = seatService.getCanceledSeatIds(); | ||
// 2. 대기중 상태인 예약대기 목록을 날짜순으로 조회한다 | ||
List<WaitingBooking> waitingList = waitingBookingService.getWaitingBookingsByStatusIsWaiting(); | ||
// 3. waitingList 반복하면서 아래 작업을 수행 | ||
/* | ||
* (1) 대기목록에 포함된 좌석의 PK 가 취소된 좌석 Set 에 존재하는지 확인 | ||
* (2) 대기목록의 좌석중에서 취소목록에 포함되는 좌석 아이디 가져옴 | ||
* (3) 가져온 좌석 아이디 사이즈가 선택좌석개수와 같으면 취소좌석 SET 에서 해당 PK 지우고 좌석 상태를 취소에서 대기로 바꾸고 예약대기를 활성화 | ||
* + 사용자에게 메일 발송 (구현 예정) | ||
*/ | ||
waitingList.forEach(waitingBooking -> { | ||
// (1) | ||
List<Long> selectedSeatIds = waitingBooking.getSelectedSeatIds(); | ||
// (2) | ||
List<Long> matchSeatIds = fetchMatchingSeatIds( | ||
canceledSeatIds, | ||
selectedSeatIds, | ||
waitingBooking.getSeatCount() | ||
); | ||
// (3) | ||
if (matchSeatIds.size() == waitingBooking.getSeatCount()) { | ||
matchSeatIds.forEach(canceledSeatIds::remove); | ||
seatService.updateSeatToWaiting(matchSeatIds); | ||
waitingBookingService.activateWaitingBooking(waitingBooking.getId()); | ||
// 메일 알림 이벤트 발행 | ||
} | ||
}); | ||
// 4. 반복이 끝났는데 남아있는 취소 좌석들은 예약가능 상태로 변경 | ||
seatService.updateSeatToAvailable(canceledSeatIds); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
import static jakarta.persistence.FetchType.*; | ||
import static jakarta.persistence.GenerationType.*; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
|
@@ -52,6 +53,8 @@ public class WaitingBooking extends TimeBaseEntity { | |
|
||
private int seatCount; | ||
|
||
private LocalDateTime expiredAt; | ||
|
||
@OneToMany(mappedBy = "waitingBooking", cascade = {REMOVE, PERSIST}) | ||
List<WaitingBookingSeat> waitingBookingSeats = new ArrayList<>(); | ||
|
||
|
@@ -108,4 +111,10 @@ public static WaitingBooking of( | |
) { | ||
return new WaitingBooking(user, seatCount, seatIds); | ||
} | ||
|
||
public List<Long> getSelectedSeatIds() { | ||
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. 예약 대기 좌석에 해당하는 좌석 id값을 list로 반환하는 함수가 맞을까요? 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. 네 맞습니다~ 👍 |
||
return waitingBookingSeats.stream() | ||
.map(WaitingBookingSeat::getSeatId) | ||
.toList(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,20 @@ | ||
package dev.hooon.waitingbooking.domain.repository; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
import dev.hooon.waitingbooking.domain.entity.WaitingBooking; | ||
|
||
public interface WaitingBookingRepository { | ||
|
||
void save(WaitingBooking waitingBooking); | ||
|
||
Optional<WaitingBooking> findById(Long id); | ||
|
||
List<WaitingBooking> findAll(); | ||
|
||
// WaitingStatus 가 WAITING 인 데이터를 최신순으로 조회하는 쿼리 | ||
List<WaitingBooking> findByStatusIsWaiting(); | ||
|
||
void updateToActiveById(Long id); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,13 @@ | ||
package dev.hooon.waitingbooking.infrastructure.adaptor; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
import org.springframework.stereotype.Repository; | ||
|
||
import dev.hooon.waitingbooking.domain.entity.WaitingBooking; | ||
import dev.hooon.waitingbooking.domain.entity.WaitingStatus; | ||
import dev.hooon.waitingbooking.domain.repository.WaitingBookingRepository; | ||
import dev.hooon.waitingbooking.infrastructure.repository.WaitingBookingJpaRepository; | ||
import lombok.RequiredArgsConstructor; | ||
|
@@ -20,9 +23,27 @@ public void save(WaitingBooking waitingBooking) { | |
waitingBookingJpaRepository.save(waitingBooking); | ||
} | ||
|
||
@Override | ||
public Optional<WaitingBooking> findById(Long id) { | ||
return waitingBookingJpaRepository.findById(id); | ||
} | ||
|
||
@Override | ||
public List<WaitingBooking> findAll() { | ||
return waitingBookingJpaRepository.findAll(); | ||
} | ||
|
||
@Override | ||
public List<WaitingBooking> findByStatusIsWaiting() { | ||
return waitingBookingJpaRepository.findByStatusOrderByIdDesc(WaitingStatus.WAITING); | ||
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. 네이밍 굿굿 한번에 바로 파악했네요 |
||
} | ||
|
||
@Override | ||
public void updateToActiveById(Long id) { | ||
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. updateStatusToActiveById 는 어떨까요?! (이것도 사소해~) 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. 이 쿼리가 어댑터쪽 코드보면 상태변경도있는데 expireAt 도 6시간 뒤로 설정해주는 쿼리라서 이렇게 네이밍했어요! |
||
waitingBookingJpaRepository.updateStatusAndExpireAt( | ||
id, | ||
WaitingStatus.ACTIVATION, | ||
LocalDateTime.now().plusHours(6) | ||
); | ||
} | ||
} |
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.
최소 단위 기능으로 메서드로 분리한거 굿굿