11package org .example .educheck .domain .meetingroomreservation .service ;
22
33import lombok .RequiredArgsConstructor ;
4- import lombok .extern .slf4j .Slf4j ;
54import org .example .educheck .domain .meetingroom .entity .MeetingRoom ;
65import org .example .educheck .domain .meetingroom .repository .MeetingRoomRepository ;
7- import org .example .educheck .domain .meetingroomreservation .TimeSlot ;
86import org .example .educheck .domain .meetingroomreservation .dto .request .MeetingRoomReservationRequestDto ;
97import org .example .educheck .domain .meetingroomreservation .entity .MeetingRoomReservation ;
108import org .example .educheck .domain .meetingroomreservation .repository .MeetingRoomReservationRepository ;
119import org .example .educheck .domain .member .entity .Member ;
1210import org .example .educheck .domain .member .repository .MemberRepository ;
11+ import org .example .educheck .global .common .exception .custom .common .ResourceMismatchException ;
1312import org .example .educheck .global .common .exception .custom .common .ResourceNotFoundException ;
1413import org .example .educheck .global .common .exception .custom .reservation .ReservationConflictException ;
15- import org .springframework .data .redis .core .RedisTemplate ;
1614import org .springframework .security .core .userdetails .UserDetails ;
1715import org .springframework .stereotype .Service ;
1816import org .springframework .transaction .annotation .Transactional ;
1917
2018import java .time .LocalDate ;
2119import java .time .LocalDateTime ;
2220import java .time .LocalTime ;
23- import java .time .format .DateTimeFormatter ;
2421import java .time .temporal .ChronoUnit ;
25- import java .util .List ;
2622
27- @ Slf4j
2823@ Service
2924@ Transactional (readOnly = true )
3025@ RequiredArgsConstructor
@@ -33,12 +28,11 @@ public class MeetingRoomReservationService {
3328 private final MeetingRoomReservationRepository meetingRoomReservationRepository ;
3429 private final MemberRepository memberRepository ;
3530 private final MeetingRoomRepository meetingRoomRepository ;
36- private final RedisTemplate <String , Object > redisTemplate ;
3731
3832 @ Transactional
3933 public void createReservation (UserDetails user , Long campusId , MeetingRoomReservationRequestDto requestDto ) {
4034
41- Member findMember = memberRepository .findByEmail (user .getUsername ()).orElseThrow (() -> new ResourceNotFoundException ("존재하지 않는 사용자입니다 ." ));
35+ Member findMember = memberRepository .findByEmail (user .getUsername ()).orElseThrow (() -> new IllegalArgumentException ("존재하지 않는 member입니다 ." ));
4236
4337 MeetingRoom meetingRoom = meetingRoomRepository .findById (requestDto .getMeetingRoomId ())
4438 .orElseThrow (() -> new ResourceNotFoundException ("해당 회의실이 존재하지 않습니다." ));
@@ -47,45 +41,13 @@ public void createReservation(UserDetails user, Long campusId, MeetingRoomReserv
4741
4842 validateReservationTime (requestDto .getStartTime (), requestDto .getEndTime ());
4943
50- TimeSlot timeSlot = TimeSlot .from (requestDto );
5144
52- //신버전
53- if (!isAvailable (meetingRoom .getId (), timeSlot )) {
54- throw new ReservationConflictException ();
55- }
45+ validateReservableTime (meetingRoom , requestDto .getStartTime (), requestDto .getEndTime ());
5646
57- //RDB에 예약 -> Redis 슬롯 처리
5847 MeetingRoomReservation meetingRoomReservation = requestDto .toEntity (findMember , meetingRoom );
5948 meetingRoomReservationRepository .save (meetingRoomReservation );
60-
61- updateRedisSlots (meetingRoom .getId (), timeSlot , true );
62-
63- log .info ("createReservation 메서드, 예약 성공" );
64- }
65-
66- private void updateRedisSlots (Long roomId , TimeSlot timeSlot , boolean isReserved ) {
67-
68- String redisKey = generateSlotKey (roomId , timeSlot .getDate ());
69-
70- Boolean [] slots = (Boolean []) redisTemplate .opsForValue ().get (redisKey );
71-
72- if (slots == null ) {
73- initDailyReservationsSlots (timeSlot .getDate ());
74- slots = (Boolean []) redisTemplate .opsForValue ().get (redisKey );
75- }
76-
77- int startSlotIndex = calculateSlotIndex (timeSlot .getStartTime ());
78- int endSlotIndex = calculateSlotIndex (timeSlot .getEndTime ());
79-
80- for (int i = startSlotIndex ; i <= endSlotIndex ; i ++) {
81- slots [i ] = isReserved ;
82- }
83-
84- redisTemplate .opsForValue ().set (redisKey , slots );
85-
8649 }
8750
88-
8951 /**
9052 * 예약은 9시부터 22시까지 가능
9153 */
@@ -96,84 +58,38 @@ private void validateReservationTime(LocalDateTime startTime, LocalDateTime endT
9658
9759
9860 if (endTime .isBefore (startTime )) {
99- throw new IllegalArgumentException ("시작 시간이 종료 시간보다 늦을 수 없습니다." );
61+ throw new ReservationConflictException ("시작 시간이 종료 시간보다 늦을 수 없습니다." );
10062 }
10163
10264 if (startTime .isAfter (endTime )) {
103- throw new IllegalArgumentException ("종료 시간이 시작 시간보다 빠를 수 없습니다." );
65+ throw new ReservationConflictException ("종료 시간이 시작 시간보다 빠를 수 없습니다." );
10466 }
10567
10668 if (ChronoUnit .MINUTES .between (startTime , endTime ) < 15 ) {
107- throw new IllegalArgumentException ("최소 예약 시간은 15분입니다." );
69+ throw new ReservationConflictException ("최소 예약 시간은 15분입니다." );
10870 }
10971
11072 if (startTime .toLocalTime ().isBefore (startOfDay ) || endTime .toLocalTime ().isAfter (endOfDay )) {
111- throw new IllegalArgumentException ("예약 가능 시간은 오전 9시부터 오후 10시까지입니다." );
73+ throw new ReservationConflictException ("예약 가능 시간은 오전 9시부터 오후 10시까지입니다." );
11274 }
11375
11476 }
11577
78+ private void validateReservableTime (MeetingRoom meetingRoom , LocalDateTime startTime , LocalDateTime endTime ) {
79+ LocalDate date = startTime .toLocalDate ();
80+ boolean result = meetingRoomReservationRepository .existsOverlappingReservation (meetingRoom ,
81+ date , startTime , endTime );
11682
117- //TODO: 쿼리 발생하는거 확인 후, FETCH JOIN 처리 등 고려 하기
118- private void validateUserCampusMatchMeetingRoom (Long campusId , MeetingRoom meetingRoom ) {
119-
120- if (!campusId .equals (meetingRoom .getCampusId ())) {
121- throw new IllegalArgumentException ("해당 회의실은 캠퍼스내 회의실이 아닙니다." );
122- }
123- }
124-
125- private String generateSlotKey (Long roomId , LocalDate date ) {
126- return String .format ("mettingRoom:%d:date:%s" , roomId , date .format (DateTimeFormatter .ofPattern ("yyyyMMdd" )));
127- }
128-
129- private int calculateSlots (int startHour , int endHour , int slotDurationMinutes ) {
130- int totalMinutes = (endHour - startHour ) * 60 ;
131- return totalMinutes / slotDurationMinutes ;
132-
133- }
134-
135- private void initDailyReservationsSlots (LocalDate date ) {
136- List <MeetingRoom > roomList = meetingRoomRepository .findAll ();
137-
138- for (MeetingRoom room : roomList ) {
139- String redisKey = generateSlotKey (room .getId (), date );
140-
141- int slotsCount = calculateSlots (9 , 22 , 15 );
142- Boolean [] slots = new Boolean [slotsCount ];
143- for (int i = 0 ; i < slotsCount ; i ++) {
144- slots [i ] = false ;
145- }
146-
147- redisTemplate .opsForValue ().set (redisKey , slots );
83+ if (result ) {
84+ throw new ReservationConflictException ();
14885 }
14986 }
15087
151- public boolean isAvailable (Long meetingRoomId , TimeSlot timeSlot ) {
152- String redisKey = generateSlotKey (meetingRoomId , timeSlot .getDate ());
153- Boolean [] slots = (Boolean []) redisTemplate .opsForValue ().get (redisKey );
154-
155- if (slots == null ) {
156- initDailyReservationsSlots (timeSlot .getDate ());
157- slots = (Boolean []) redisTemplate .opsForValue ().get (redisKey );
158- }
159-
160- int startSlotIndex = calculateSlotIndex (timeSlot .getStartTime ());
161- int endSlotIndex = calculateSlotIndex (timeSlot .getStartTime ());
88+ //TODO: 쿼리 발생하는거 확인 후, FETCH JOIN 처리 등 고려 하기
89+ private void validateUserCampusMatchMeetingRoom (Long campusId , MeetingRoom meetingRoom ) {
16290
163- for (int i = startSlotIndex ; i <= endSlotIndex ; i ++) {
164- if (slots [i ]) {
165- return false ;
166- }
91+ if (!campusId .equals (meetingRoom .getCampusId ())) {
92+ throw new ResourceMismatchException ("해당 회의실은 캠퍼스내 회의실이 아닙니다." );
16793 }
168-
169- return true ;
170- }
171-
172- private int calculateSlotIndex (LocalTime localTime ) {
173- int hour = localTime .getHour ();
174- int minute = localTime .getMinute ();
175-
176- return (hour - 9 ) * 4 + (minute / 15 );
177-
17894 }
17995}
0 commit comments