From be34533687b3e9eb3723bdedffd5191dd40d6afd Mon Sep 17 00:00:00 2001 From: crocusia <132359536+crocusia@users.noreply.github.com> Date: Thu, 22 May 2025 15:15:57 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat=20:=20=ED=9A=8C=EC=B0=A8=EB=B3=84=20?= =?UTF-8?q?=EC=A2=8C=EC=84=9D=20=EC=A0=95=EB=B3=B4=20Redis=20=EC=A0=81?= =?UTF-8?q?=EC=9E=AC=20=EC=8A=A4=EC=BC=80=EC=A4=84=EB=A7=81=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SeatScheduleInfoController.java | 11 ++++ .../repository/ScheduleRepository.java | 3 ++ .../SeatScheduleInfoRepository.java | 4 ++ .../scheduler/TicketingRedisScheduler.java | 50 +++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java diff --git a/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java b/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java index 1beca6b..9a6ec66 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java +++ b/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java @@ -2,12 +2,16 @@ import lombok.RequiredArgsConstructor; import org.example.siljeun.domain.schedule.service.SeatScheduleInfoService; +import org.example.siljeun.domain.seat.dto.response.SeatScheduleInfoResponse; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import java.util.List; + @Controller @RequiredArgsConstructor public class SeatScheduleInfoController { @@ -22,4 +26,11 @@ public ResponseEntity selectSeat( return ResponseEntity.ok("좌석이 선택되었습니다."); } + +// @GetMapping("/schedule/{scheduleId}/seat-schedule-info") +// public ResponseEntity> getSeatScheduleInfos( +// @PathVariable Long scheduleId +// ){ +// return ResponseEntity.ok(new List seats); +// } } diff --git a/src/main/java/org/example/siljeun/domain/schedule/repository/ScheduleRepository.java b/src/main/java/org/example/siljeun/domain/schedule/repository/ScheduleRepository.java index 32a9266..4969b50 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/repository/ScheduleRepository.java +++ b/src/main/java/org/example/siljeun/domain/schedule/repository/ScheduleRepository.java @@ -1,5 +1,6 @@ package org.example.siljeun.domain.schedule.repository; +import java.time.LocalDateTime; import java.util.List; import org.example.siljeun.domain.schedule.entity.Schedule; import org.springframework.data.jpa.repository.JpaRepository; @@ -7,4 +8,6 @@ public interface ScheduleRepository extends JpaRepository, ScheduleQueryRepository { List findByConcertId(Long concertId); + + List findAllByTicketingStartTimeBetween(LocalDateTime ticketingStartTimeAfter, LocalDateTime ticketingStartTimeBefore); } diff --git a/src/main/java/org/example/siljeun/domain/schedule/repository/SeatScheduleInfoRepository.java b/src/main/java/org/example/siljeun/domain/schedule/repository/SeatScheduleInfoRepository.java index 50d55f4..20f047d 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/repository/SeatScheduleInfoRepository.java +++ b/src/main/java/org/example/siljeun/domain/schedule/repository/SeatScheduleInfoRepository.java @@ -1,8 +1,12 @@ package org.example.siljeun.domain.schedule.repository; +import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface SeatScheduleInfoRepository extends JpaRepository { + List findAllBySchedule(Schedule schedule); } diff --git a/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java b/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java new file mode 100644 index 0000000..2ba9492 --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java @@ -0,0 +1,50 @@ +package org.example.siljeun.domain.schedule.scheduler; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.example.siljeun.domain.schedule.entity.Schedule; +import org.example.siljeun.domain.schedule.repository.ScheduleRepository; +import org.example.siljeun.domain.schedule.repository.SeatScheduleInfoRepository; +import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Component +@RequiredArgsConstructor +public class TicketingRedisScheduler { + private final ScheduleRepository scheduleRepository; + private final SeatScheduleInfoRepository seatScheduleInfoRepository; + private final RedisTemplate redisTemplate; + + @Scheduled(fixedRate = 60_000) + public void loadSeatStatusToRedis() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime fiveMinutesLater = now.plusMinutes(5); //티켓팅 시작 시간이 임박한 회차에 대해 미리 Redis에 정보 적재 + + List openedSchedules = scheduleRepository.findAllByTicketingStartTimeBetween(now, fiveMinutesLater); + + for (Schedule schedule : openedSchedules) { + Long scheduleId = schedule.getId(); + String redisKey = "seatStatus:" + scheduleId; + + List seatScheduleInfos = seatScheduleInfoRepository.findAllBySchedule(schedule); + + Map seatStatusMap = seatScheduleInfos.stream() + .collect(Collectors.toMap( + seat -> String.valueOf(seat.getId()), + seat -> seat.getStatus().name() + )); + + redisTemplate.opsForHash().putAll(redisKey, seatStatusMap); + log.info("✅ Redis에 좌석 상태 저장 완료 [key: {}] seatCount: {}", redisKey, seatStatusMap.size()); + } + } + +} From 565da36a60c2654792c6c9e4ed954ce8305c5a7d Mon Sep 17 00:00:00 2001 From: crocusia <132359536+crocusia@users.noreply.github.com> Date: Thu, 22 May 2025 17:25:05 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat=20:=20Redis=20=EC=BA=90=EC=8B=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=A2=8C=EC=84=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?(DB=EC=99=80=EC=9D=98=20=EB=8F=99=EA=B8=B0=ED=99=94=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=ED=95=84=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SeatScheduleInfoController.java | 13 ++--- .../scheduler/TicketingRedisScheduler.java | 22 ++++---- .../service/SeatScheduleInfoService.java | 54 +++++++++++++++++-- .../siljeun/global/config/RedisConfig.java | 9 ++++ .../service/SeatScheduleInfoServiceTest.java | 2 +- 5 files changed, 76 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java b/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java index 9a6ec66..319fb0f 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java +++ b/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import java.util.List; +import java.util.Map; @Controller @RequiredArgsConstructor @@ -27,10 +28,10 @@ public ResponseEntity selectSeat( return ResponseEntity.ok("좌석이 선택되었습니다."); } -// @GetMapping("/schedule/{scheduleId}/seat-schedule-info") -// public ResponseEntity> getSeatScheduleInfos( -// @PathVariable Long scheduleId -// ){ -// return ResponseEntity.ok(new List seats); -// } + @GetMapping("/schedule/{scheduleId}/seat-schedule-info") + public ResponseEntity> getSeatScheduleInfos( + @PathVariable Long scheduleId + ){ + return ResponseEntity.ok(seatScheduleInfoService.getSeatStatusMap(scheduleId)); + } } diff --git a/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java b/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java index 2ba9492..dd3d486 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java +++ b/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java @@ -6,6 +6,7 @@ import org.example.siljeun.domain.schedule.repository.ScheduleRepository; import org.example.siljeun.domain.schedule.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -21,7 +22,9 @@ public class TicketingRedisScheduler { private final ScheduleRepository scheduleRepository; private final SeatScheduleInfoRepository seatScheduleInfoRepository; - private final RedisTemplate redisTemplate; + + @Qualifier("redisStatusTemplate") + private final RedisTemplate redisStatusTemplate; @Scheduled(fixedRate = 60_000) public void loadSeatStatusToRedis() { @@ -31,20 +34,13 @@ public void loadSeatStatusToRedis() { List openedSchedules = scheduleRepository.findAllByTicketingStartTimeBetween(now, fiveMinutesLater); for (Schedule schedule : openedSchedules) { - Long scheduleId = schedule.getId(); - String redisKey = "seatStatus:" + scheduleId; - List seatScheduleInfos = seatScheduleInfoRepository.findAllBySchedule(schedule); - Map seatStatusMap = seatScheduleInfos.stream() - .collect(Collectors.toMap( - seat -> String.valueOf(seat.getId()), - seat -> seat.getStatus().name() - )); - - redisTemplate.opsForHash().putAll(redisKey, seatStatusMap); - log.info("✅ Redis에 좌석 상태 저장 완료 [key: {}] seatCount: {}", redisKey, seatStatusMap.size()); + for(SeatScheduleInfo seatScheduleInfo : seatScheduleInfos){ + String key = "seatStatus:" + seatScheduleInfo.getId().toString(); + String value = seatScheduleInfo.getStatus().name(); + redisStatusTemplate.opsForValue().set(key, value); + } } } - } diff --git a/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java b/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java index 5a6a92d..0cc13e0 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java +++ b/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java @@ -1,40 +1,86 @@ package org.example.siljeun.domain.schedule.service; +import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.example.siljeun.domain.schedule.entity.Schedule; +import org.example.siljeun.domain.schedule.repository.ScheduleRepository; import org.example.siljeun.domain.schedule.repository.SeatScheduleInfoRepository; +import org.example.siljeun.domain.seat.entity.QSeatScheduleInfo; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; import org.example.siljeun.global.lock.DistributedLock; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; import java.time.Duration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + @Slf4j @Service @RequiredArgsConstructor public class SeatScheduleInfoService { private final SeatScheduleInfoRepository seatScheduleInfoRepository; + private final ScheduleRepository scheduleRepository; + private final RedisTemplate redisTemplate; + @Qualifier("redisStatusTemplate") + private final RedisTemplate redisStatusTemplate; + @DistributedLock(key = "'seat:' + #seatScheduleInfoId") public void selectSeat(Long userId, Long seatScheduleInfoId) { - SeatScheduleInfo seatScheduleInfo = seatScheduleInfoRepository.findById(seatScheduleInfoId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "해당 회차의 좌석 정보를 찾을 수 없습니다.")); + SeatScheduleInfo seatScheduleInfo = seatScheduleInfoRepository.findById(seatScheduleInfoId). + orElseThrow(() -> new EntityNotFoundException("해당 회차별 좌석 정보가 존재하지 않습니다.")); if (!seatScheduleInfo.isAvailable()) { //log.info("이미 선점된 좌석입니다."); throw new ResponseStatusException(HttpStatus.CONFLICT, "이미 선점된 좌석입니다."); } - seatScheduleInfo.updateSeatScheduleInfoStatus(SeatStatus.HOLD); + seatScheduleInfo.updateSeatScheduleInfoStatus(SeatStatus.SELECTED); seatScheduleInfoRepository.save(seatScheduleInfo); String redisKey = "seat:" + seatScheduleInfoId; redisTemplate.opsForValue().set(redisKey, userId, Duration.ofMinutes(5)); - Object redisValue = redisTemplate.opsForValue().get(redisKey); - //log.info("좌석 선택 성공 [redis 저장 : {} = {}]", redisKey, redisValue); + + String redisStatusKey = "seatStatus:" + seatScheduleInfoId; + redisStatusTemplate.opsForValue().set(redisStatusKey, seatScheduleInfo.getStatus().name(), Duration.ofMinutes(5)); + } + + public Map getSeatStatusMap(Long scheduleId) { + + Schedule schedule = scheduleRepository.findById(scheduleId) + .orElseThrow(() -> new EntityNotFoundException("해당 회차가 존재하지 않습니다.")); + + List seatScheduleInfos = + seatScheduleInfoRepository.findAllBySchedule(schedule); + + Map result = new HashMap<>(); + + for (SeatScheduleInfo info : seatScheduleInfos) { + String redisKey = "seatStatus:" + info.getId(); + String redisStatus = redisStatusTemplate.opsForValue().get(redisKey); + + String status; + if (redisStatus != null) { + status = redisStatus; + } else if (info.getStatus() == SeatStatus.SELECTED) { //TTL에 의해서 Redis에서는 만료되었으나 DB에 Selected로 저장된 경우 + status = SeatStatus.AVAILABLE.name(); + } else { + status = info.getStatus().name(); + } + + result.put("seatScheduleInfo-" + info.getId().toString(), status); + } + + return result; } } diff --git a/src/main/java/org/example/siljeun/global/config/RedisConfig.java b/src/main/java/org/example/siljeun/global/config/RedisConfig.java index a42ca70..fe361b9 100644 --- a/src/main/java/org/example/siljeun/global/config/RedisConfig.java +++ b/src/main/java/org/example/siljeun/global/config/RedisConfig.java @@ -39,4 +39,13 @@ public RedisTemplate redisTemplate(RedisConnectionFactory connecti redisTemplate.setValueSerializer(new GenericToStringSerializer<>(Long.class)); // Long 값 직렬화 return redisTemplate; } + + @Bean + public RedisTemplate redisStatusTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(connectionFactory); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + return redisTemplate; + } } diff --git a/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java b/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java index bec536c..569775f 100644 --- a/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java +++ b/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java @@ -110,7 +110,7 @@ void sameSeatConcurrentAccessTest() throws InterruptedException { assertEquals(totalThreads - 1, conflictCount); SeatScheduleInfo updated = seatScheduleInfoRepository.findById(seatScheduleInfoId).orElseThrow(); - assertEquals(SeatStatus.HOLD, updated.getStatus()); + assertEquals(SeatStatus.SELECTED, updated.getStatus()); Long storedUserId = redisTemplate.opsForValue().get("seat:" + seatScheduleInfoId); assertNotNull(storedUserId); From 56295b7b73e5e9778319d2f8ad2cbaacc2db32c0 Mon Sep 17 00:00:00 2001 From: crocusia <132359536+crocusia@users.noreply.github.com> Date: Fri, 23 May 2025 02:28:06 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat=20:=20=EC=84=A0=EC=A0=90=ED=95=9C=20?= =?UTF-8?q?=EC=A2=8C=EC=84=9D=EC=97=90=20=EB=8C=80=ED=95=B4=20=EA=B2=B0?= =?UTF-8?q?=EC=A0=9C=EC=B0=BD=EC=9C=BC=EB=A1=9C=20=EB=84=98=EC=96=B4?= =?UTF-8?q?=EA=B0=88=20=EB=95=8C,=20=EC=98=88=EB=A7=A4=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=83=9D=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(=EA=B2=B0=EC=A0=9C=EA=B0=80=20=EC=99=84=EB=A3=8C?= =?UTF-8?q?=EB=90=98=EA=B8=B0=20=EC=A0=84=EC=9D=98=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EB=A5=BC=20=EB=82=98=ED=83=80=EB=83=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 25 ++++++---- .../service/ReservationService.java | 50 +++++++++++++++++++ .../SeatScheduleInfoController.java | 20 ++++---- .../scheduler/TicketingRedisScheduler.java | 18 ++----- .../service/SeatScheduleInfoService.java | 44 ++++++++-------- .../siljeun/global/config/RedisConfig.java | 12 ----- .../service/SeatScheduleInfoServiceTest.java | 9 +--- 7 files changed, 101 insertions(+), 77 deletions(-) diff --git a/src/main/java/org/example/siljeun/domain/reservation/controller/ReservationController.java b/src/main/java/org/example/siljeun/domain/reservation/controller/ReservationController.java index d934107..a1815e5 100644 --- a/src/main/java/org/example/siljeun/domain/reservation/controller/ReservationController.java +++ b/src/main/java/org/example/siljeun/domain/reservation/controller/ReservationController.java @@ -6,16 +6,10 @@ import org.example.siljeun.domain.reservation.dto.response.ReservationInfoResponse; import org.example.siljeun.domain.reservation.service.ReservationService; import org.example.siljeun.global.dto.ResponseDto; -import org.example.siljeun.global.security.CustomUserDetails; +import org.example.siljeun.global.security.PrincipalDetails; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -26,7 +20,7 @@ public class ReservationController { @PatchMapping("/{reservationId}/discount") public ResponseEntity> updatePrice( - @AuthenticationPrincipal CustomUserDetails userDetails, + @AuthenticationPrincipal PrincipalDetails userDetails, @PathVariable Long reservationId, @RequestBody @Valid UpdatePriceRequest requestDto) { String username = userDetails.getUsername(); @@ -36,7 +30,7 @@ public ResponseEntity> updatePrice( @DeleteMapping("/{reservationId}") public ResponseEntity> delete( - @AuthenticationPrincipal CustomUserDetails userDetails, @PathVariable Long reservationId) { + @AuthenticationPrincipal PrincipalDetails userDetails, @PathVariable Long reservationId) { String username = userDetails.getUsername(); reservationService.delete(username, reservationId); return ResponseEntity.ok(ResponseDto.success("예매 취소 완료", null)); @@ -44,9 +38,18 @@ public ResponseEntity> delete( @GetMapping("/{reservationId}") public ResponseEntity> findById( - @AuthenticationPrincipal CustomUserDetails userDetails, @PathVariable Long reservationId) { + @AuthenticationPrincipal PrincipalDetails userDetails, @PathVariable Long reservationId) { String username = userDetails.getUsername(); ReservationInfoResponse dto = reservationService.findById(username, reservationId); return ResponseEntity.ok(ResponseDto.success("예매 조회 성공", dto)); } + + @PostMapping() + public ResponseEntity> createReservation( + @PathVariable Long scheduleId, + @AuthenticationPrincipal PrincipalDetails userDetails + ){ + reservationService.createReservation(scheduleId, userDetails.getUserId()); + return ResponseEntity.ok(ResponseDto.success("결제 진행하기", null)); + } } diff --git a/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java b/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java index 3a03fa6..73a9c0a 100644 --- a/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java +++ b/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java @@ -1,5 +1,6 @@ package org.example.siljeun.domain.reservation.service; +import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import org.example.siljeun.domain.reservation.dto.request.UpdatePriceRequest; import org.example.siljeun.domain.reservation.dto.response.ReservationInfoResponse; @@ -9,11 +10,18 @@ import org.example.siljeun.domain.reservation.repository.ReservationRepository; import org.example.siljeun.domain.schedule.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; +import org.example.siljeun.domain.seat.enums.SeatStatus; import org.example.siljeun.domain.user.entity.User; import org.example.siljeun.domain.user.repository.UserRepository; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.Duration; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + @Service @RequiredArgsConstructor public class ReservationService { @@ -22,6 +30,8 @@ public class ReservationService { private final UserRepository userRepository; private final WaitingQueueService waitingQueueService; private final SeatScheduleInfoRepository seatScheduleInfoRepository; + private final RedisTemplate redisTemplate; + @Transactional public void save(Long userId, Long seatScheduleInfoId) { @@ -76,4 +86,44 @@ public ReservationInfoResponse findById(String username, Long reservationId) { return ReservationInfoResponse.from(reservation); } + + @Transactional + public void createReservation(Long scheduleId, Long userId){ + + //유저 확인 + User user = userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException("유저를 찾을 수 없습니다.")); + + //유저가 해당 회차에 선택한 좌석 검증 + String redisSelectedKey = "user:scheduleSelected:" + userId + ":" + scheduleId; + Set seatScheduleIds = redisTemplate.opsForSet().members(redisSelectedKey); + + if (seatScheduleIds == null || seatScheduleIds.isEmpty()) { + throw new IllegalStateException("선택한 좌석이 없습니다."); + } + + List seatsScheduleInfos = seatScheduleInfoRepository.findAllById( + seatScheduleIds.stream().map(Long::valueOf).collect(Collectors.toList()) + ); + + for (SeatScheduleInfo seatScheduleInfo : seatsScheduleInfos) { + String key = "seatStatus:" + seatScheduleInfo.getId(); + String status = redisTemplate.opsForValue().get(key); + if (!"SELECTED".equals(status)) { + throw new IllegalStateException("좌석 상태가 유효하지 않습니다. 다시 선택해주세요."); + } + } + + //검증 끝난 데이터에 대해 예매 정보 생성 및 상태 업데이트 + for (SeatScheduleInfo seatScheduleInfo : seatsScheduleInfos) { + Reservation reservation = new Reservation(user, seatScheduleInfo); + reservationRepository.save(reservation); + + seatScheduleInfo.updateSeatScheduleInfoStatus(SeatStatus.HOLD); //결제 진행 중! + seatScheduleInfoRepository.save(seatScheduleInfo); + + String redisSeatKey = "seatStatus:" + seatScheduleInfo.getId(); + redisTemplate.opsForValue().set(redisSeatKey, SeatStatus.HOLD.name(), Duration.ofMinutes(60)); + } + } } diff --git a/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java b/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java index a450ff9..7497bcc 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java +++ b/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java @@ -2,8 +2,7 @@ import lombok.RequiredArgsConstructor; import org.example.siljeun.domain.schedule.service.SeatScheduleInfoService; -import org.example.siljeun.domain.seat.dto.response.SeatScheduleInfoResponse; -import org.example.siljeun.global.security.CustomUserDetails; +import org.example.siljeun.global.security.PrincipalDetails; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.stereotype.Controller; @@ -12,28 +11,29 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import java.util.List; import java.util.Map; @Controller @RequiredArgsConstructor +@RequestMapping("/schedules/{scheduleId}") public class SeatScheduleInfoController { private final SeatScheduleInfoService seatScheduleInfoService; - @PostMapping("/seat-schedule-info/{seatScheduleInfoId}") + @PostMapping("/seat-schedule-infos/{seatScheduleInfoId}") public ResponseEntity selectSeat( - @PathVariable Long seatScheduleInfoId, - @AuthenticationPrincipal CustomUserDetails userDetails - ){ - seatScheduleInfoService.selectSeat(userDetails.getUserId(), seatScheduleInfoId); + @PathVariable Long scheduleId, + @PathVariable Long seatScheduleInfoId, + @AuthenticationPrincipal PrincipalDetails userDetails + ) { + seatScheduleInfoService.selectSeat(userDetails.getUserId(), scheduleId, seatScheduleInfoId); return ResponseEntity.ok("좌석이 선택되었습니다."); } - @GetMapping("/schedule/{scheduleId}/seat-schedule-info") + @GetMapping("/seat-schedule-infos") public ResponseEntity> getSeatScheduleInfos( @PathVariable Long scheduleId - ){ + ) { return ResponseEntity.ok(seatScheduleInfoService.getSeatStatusMap(scheduleId)); } } diff --git a/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java b/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java index 4aba080..652c794 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java +++ b/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java @@ -6,32 +6,20 @@ import org.example.siljeun.domain.schedule.repository.ScheduleRepository; import org.example.siljeun.domain.schedule.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; @Slf4j @Component +@RequiredArgsConstructor public class TicketingRedisScheduler { private final ScheduleRepository scheduleRepository; private final SeatScheduleInfoRepository seatScheduleInfoRepository; - private final RedisTemplate redisStatusTemplate; - - public TicketingRedisScheduler( - ScheduleRepository scheduleRepository, - SeatScheduleInfoRepository seatScheduleInfoRepository, - @Qualifier("redisStringTemplate") RedisTemplate redisStatusTemplate - ){ - this.scheduleRepository = scheduleRepository; - this.seatScheduleInfoRepository = seatScheduleInfoRepository; - this.redisStatusTemplate = redisStatusTemplate; - } + private final RedisTemplate redisTemplate; @Scheduled(fixedRate = 60_000) public void loadSeatStatusToRedis() { @@ -46,7 +34,7 @@ public void loadSeatStatusToRedis() { for(SeatScheduleInfo seatScheduleInfo : seatScheduleInfos){ String key = "seatStatus:" + seatScheduleInfo.getId().toString(); String value = seatScheduleInfo.getStatus().name(); - redisStatusTemplate.opsForValue().set(key, value); + redisTemplate.opsForValue().set(key, value); } } } diff --git a/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java b/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java index 0e5bfdc..db5e8f7 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java +++ b/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java @@ -1,6 +1,7 @@ package org.example.siljeun.domain.schedule.service; import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.schedule.repository.ScheduleRepository; @@ -18,29 +19,18 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; @Slf4j @Service +@RequiredArgsConstructor public class SeatScheduleInfoService { private final SeatScheduleInfoRepository seatScheduleInfoRepository; private final ScheduleRepository scheduleRepository; - private final RedisTemplate redisSeatUserTemplate; - private final RedisTemplate redisStatusTemplate; - - public SeatScheduleInfoService( - SeatScheduleInfoRepository seatScheduleInfoRepository, - ScheduleRepository scheduleRepository, - @Qualifier("redisLongTemplate") RedisTemplate redisSeatUserTemplate, - @Qualifier("redisStringTemplate") RedisTemplate redisStatusTemplate - ){ - this.seatScheduleInfoRepository = seatScheduleInfoRepository; - this.scheduleRepository = scheduleRepository; - this.redisSeatUserTemplate = redisSeatUserTemplate; - this.redisStatusTemplate = redisStatusTemplate; - } + private final RedisTemplate redisTemplate; @DistributedLock(key = "'seat:' + #seatScheduleInfoId") - public void selectSeat(Long userId, Long seatScheduleInfoId) { + public void selectSeat(Long userId, Long scheduleId, Long seatScheduleInfoId) { SeatScheduleInfo seatScheduleInfo = seatScheduleInfoRepository.findById(seatScheduleInfoId). orElseThrow(() -> new EntityNotFoundException("해당 회차별 좌석 정보가 존재하지 않습니다.")); @@ -53,11 +43,23 @@ public void selectSeat(Long userId, Long seatScheduleInfoId) { seatScheduleInfo.updateSeatScheduleInfoStatus(SeatStatus.SELECTED); seatScheduleInfoRepository.save(seatScheduleInfo); - String redisKey = "seat:" + seatScheduleInfoId; - redisSeatUserTemplate.opsForValue().set(redisKey, userId, Duration.ofMinutes(5)); - - String redisStatusKey = "seatStatus:" + seatScheduleInfoId; - redisStatusTemplate.opsForValue().set(redisStatusKey, seatScheduleInfo.getStatus().name(), Duration.ofMinutes(5)); + // userId와 schedule Id가 key이고 seatSchduleInfoId로 구성된 Set이 value인 형태로 저장 + String redisSelectedKey = "user:scheduleSelected" + userId + ":" + scheduleId; + redisTemplate.opsForSet().add(redisSelectedKey, seatScheduleInfoId.toString()); + //key에 해당하는 set 데이터를 TTL 5분으로 업데이트 + redisTemplate.expire(redisSelectedKey, Duration.ofMinutes(5)); + + //seatScheduleInfoId의 seatStatus 상태 변경 + redisTemplate.opsForValue().set("seatStatus:"+seatScheduleInfoId.toString(), SeatStatus.SELECTED.name()); + + //user가 선점한 좌석들 TTL 5분으로 재설정 + Set seatScheduleInfoIds = redisTemplate.opsForSet().members(redisSelectedKey); + if (seatScheduleInfoIds != null) { + for (String seatId : seatScheduleInfoIds) { + String redisStatusKey = "seatStatus:" + seatId; + redisTemplate.expire(redisStatusKey, Duration.ofMinutes(5)); // TTL 재설정 + } + } } public Map getSeatStatusMap(Long scheduleId) { @@ -72,7 +74,7 @@ public Map getSeatStatusMap(Long scheduleId) { for (SeatScheduleInfo info : seatScheduleInfos) { String redisKey = "seatStatus:" + info.getId(); - String redisStatus = redisStatusTemplate.opsForValue().get(redisKey); + String redisStatus = redisTemplate.opsForValue().get(redisKey); String status; if (redisStatus != null) { diff --git a/src/main/java/org/example/siljeun/global/config/RedisConfig.java b/src/main/java/org/example/siljeun/global/config/RedisConfig.java index bd2c0e8..e280366 100644 --- a/src/main/java/org/example/siljeun/global/config/RedisConfig.java +++ b/src/main/java/org/example/siljeun/global/config/RedisConfig.java @@ -57,16 +57,4 @@ public RedisTemplate redisJsonTemplate(RedisConnectionFactory co template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // JSON 직렬화 return template; } - - /** - * String 타입 RedisTemplate - */ - @Bean - public RedisTemplate redisStringTemplate(RedisConnectionFactory connectionFactory) { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(connectionFactory); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new StringRedisSerializer()); - return redisTemplate; - } } diff --git a/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java b/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java index e225c9d..a839b08 100644 --- a/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java +++ b/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java @@ -89,7 +89,7 @@ void sameSeatConcurrentAccessTest() throws InterruptedException { IntStream.range(0, totalThreads).forEach(i -> { executor.submit(() -> { try { - seatScheduleInfoService.selectSeat((long) i + 1, seatScheduleInfoId); + seatScheduleInfoService.selectSeat((long) i + 1, schedule.getId(), seatScheduleInfoId); resultMessages.add("SUCCESS"); } catch (ResponseStatusException e) { resultMessages.add(e.getReason()); @@ -110,12 +110,5 @@ void sameSeatConcurrentAccessTest() throws InterruptedException { assertEquals(1, successCount); assertEquals(totalThreads - 1, conflictCount); - - SeatScheduleInfo updated = seatScheduleInfoRepository.findById(seatScheduleInfoId).orElseThrow(); - assertEquals(SeatStatus.SELECTED, updated.getStatus()); - - Long storedUserId = redisTemplate.opsForValue().get("seat:" + seatScheduleInfoId); - assertNotNull(storedUserId); - System.out.println("Redis에 저장된 유저 ID: " + storedUserId); } } \ No newline at end of file From e88f80967f05e025a643c529cbc698803cc29ef2 Mon Sep 17 00:00:00 2001 From: crocusia <132359536+crocusia@users.noreply.github.com> Date: Fri, 23 May 2025 05:46:05 +0900 Subject: [PATCH 04/10] =?UTF-8?q?refactor=20:=20Redis=20key-value=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=EB=A5=BC=20=EC=84=B1=EB=8A=A5=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheduler/TicketingRedisScheduler.java | 13 +++-- .../service/SeatScheduleInfoService.java | 49 ++++++++----------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java b/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java index 652c794..6257c11 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java +++ b/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java @@ -11,7 +11,9 @@ import org.springframework.stereotype.Component; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Slf4j @Component @@ -31,11 +33,14 @@ public void loadSeatStatusToRedis() { for (Schedule schedule : openedSchedules) { List seatScheduleInfos = seatScheduleInfoRepository.findAllBySchedule(schedule); - for(SeatScheduleInfo seatScheduleInfo : seatScheduleInfos){ - String key = "seatStatus:" + seatScheduleInfo.getId().toString(); - String value = seatScheduleInfo.getStatus().name(); - redisTemplate.opsForValue().set(key, value); + String key = "seatStatus:" + schedule.getId(); + Map seatStatusMap = new HashMap<>(); + + for (SeatScheduleInfo seat : seatScheduleInfos) { + seatStatusMap.put(seat.getId().toString(), seat.getStatus().name()); } + + redisTemplate.opsForHash().putAll(key, seatStatusMap); } } } diff --git a/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java b/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java index db5e8f7..b1b2512 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java +++ b/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java @@ -16,10 +16,7 @@ import org.springframework.web.server.ResponseStatusException; import java.time.Duration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; @Slf4j @Service @@ -43,23 +40,15 @@ public void selectSeat(Long userId, Long scheduleId, Long seatScheduleInfoId) { seatScheduleInfo.updateSeatScheduleInfoStatus(SeatStatus.SELECTED); seatScheduleInfoRepository.save(seatScheduleInfo); - // userId와 schedule Id가 key이고 seatSchduleInfoId로 구성된 Set이 value인 형태로 저장 + //userId와 schedule Id가 key이고 seatSchduleInfoId로 구성된 Set이 value인 형태로 저장 String redisSelectedKey = "user:scheduleSelected" + userId + ":" + scheduleId; redisTemplate.opsForSet().add(redisSelectedKey, seatScheduleInfoId.toString()); //key에 해당하는 set 데이터를 TTL 5분으로 업데이트 redisTemplate.expire(redisSelectedKey, Duration.ofMinutes(5)); //seatScheduleInfoId의 seatStatus 상태 변경 - redisTemplate.opsForValue().set("seatStatus:"+seatScheduleInfoId.toString(), SeatStatus.SELECTED.name()); - - //user가 선점한 좌석들 TTL 5분으로 재설정 - Set seatScheduleInfoIds = redisTemplate.opsForSet().members(redisSelectedKey); - if (seatScheduleInfoIds != null) { - for (String seatId : seatScheduleInfoIds) { - String redisStatusKey = "seatStatus:" + seatId; - redisTemplate.expire(redisStatusKey, Duration.ofMinutes(5)); // TTL 재설정 - } - } + String redisHashKey = "schedule:seatStatus:" + scheduleId; + redisTemplate.opsForHash().put(redisHashKey + scheduleId, seatScheduleInfoId.toString(), SeatStatus.SELECTED.name()); } public Map getSeatStatusMap(Long scheduleId) { @@ -70,24 +59,26 @@ public Map getSeatStatusMap(Long scheduleId) { List seatScheduleInfos = seatScheduleInfoRepository.findAllBySchedule(schedule); - Map result = new HashMap<>(); + List fieldKeys = seatScheduleInfos.stream() + .map(info -> info.getId().toString()) + .toList(); + + String redisHashKey = "seatStatus:" + scheduleId; + List redisStatuses = redisTemplate.opsForHash().multiGet(redisHashKey, new ArrayList<>(fieldKeys)); + + Map seatStatusMap = new HashMap<>(); - for (SeatScheduleInfo info : seatScheduleInfos) { - String redisKey = "seatStatus:" + info.getId(); - String redisStatus = redisTemplate.opsForValue().get(redisKey); + for (int i = 0; i < seatScheduleInfos.size(); i++) { + SeatScheduleInfo info = seatScheduleInfos.get(i); + Object redisStatusObj = redisStatuses.get(i); - String status; - if (redisStatus != null) { - status = redisStatus; - } else if (info.getStatus() == SeatStatus.SELECTED) { //TTL에 의해서 Redis에서는 만료되었으나 DB에 Selected로 저장된 경우 - status = SeatStatus.AVAILABLE.name(); - } else { - status = info.getStatus().name(); - } + String status = redisStatusObj != null + ? redisStatusObj.toString() + : seatScheduleInfos.get(i).getStatus().name(); - result.put("seatScheduleInfo-" + info.getId().toString(), status); + seatStatusMap.put("seatScheduleInfo-" + info.getId().toString(), status); } - return result; + return seatStatusMap; } } From 9a17f03d1e72a3ebb8c2a2f8959f03667161ee48 Mon Sep 17 00:00:00 2001 From: crocusia <132359536+crocusia@users.noreply.github.com> Date: Fri, 23 May 2025 06:38:54 +0900 Subject: [PATCH 05/10] =?UTF-8?q?refactor=20:=201=EC=9D=B8=201=EB=A7=A4=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReservationService.java | 39 +++++++++---------- ...java => SeatStatusPreloaderScheduler.java} | 4 +- .../service/SeatScheduleInfoService.java | 8 +++- 3 files changed, 27 insertions(+), 24 deletions(-) rename src/main/java/org/example/siljeun/domain/schedule/scheduler/{TicketingRedisScheduler.java => SeatStatusPreloaderScheduler.java} (95%) diff --git a/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java b/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java index 73a9c0a..e0067a9 100644 --- a/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java +++ b/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java @@ -96,34 +96,33 @@ public void createReservation(Long scheduleId, Long userId){ //유저가 해당 회차에 선택한 좌석 검증 String redisSelectedKey = "user:scheduleSelected:" + userId + ":" + scheduleId; - Set seatScheduleIds = redisTemplate.opsForSet().members(redisSelectedKey); + String selectedId = redisTemplate.opsForValue().get(redisSelectedKey); - if (seatScheduleIds == null || seatScheduleIds.isEmpty()) { + if (selectedId == null) { throw new IllegalStateException("선택한 좌석이 없습니다."); } - List seatsScheduleInfos = seatScheduleInfoRepository.findAllById( - seatScheduleIds.stream().map(Long::valueOf).collect(Collectors.toList()) - ); + SeatScheduleInfo seatScheduleInfo = seatScheduleInfoRepository.findById(Long.valueOf(selectedId)) + .orElseThrow(() -> new EntityNotFoundException("좌석 정보를 찾을 수 없습니다.")); - for (SeatScheduleInfo seatScheduleInfo : seatsScheduleInfos) { - String key = "seatStatus:" + seatScheduleInfo.getId(); - String status = redisTemplate.opsForValue().get(key); - if (!"SELECTED".equals(status)) { - throw new IllegalStateException("좌석 상태가 유효하지 않습니다. 다시 선택해주세요."); - } + //해당 좌석의 상태 검증 + String redisStatusHashKey = "seatStatus:" + scheduleId; + Object redisStatusObj = redisTemplate.opsForHash().get(redisStatusHashKey, selectedId); + + if (redisStatusObj == null || !redisStatusObj.toString().equals(SeatStatus.SELECTED.name())) { + throw new IllegalStateException("좌석 상태가 유효하지 않습니다. 다시 선택해주세요."); } - //검증 끝난 데이터에 대해 예매 정보 생성 및 상태 업데이트 - for (SeatScheduleInfo seatScheduleInfo : seatsScheduleInfos) { - Reservation reservation = new Reservation(user, seatScheduleInfo); - reservationRepository.save(reservation); + //예매 정보 생성 + Reservation reservation = new Reservation(user, seatScheduleInfo); + reservationRepository.save(reservation); - seatScheduleInfo.updateSeatScheduleInfoStatus(SeatStatus.HOLD); //결제 진행 중! - seatScheduleInfoRepository.save(seatScheduleInfo); + //좌석 상태 결제 진행 중으로 변경 + seatScheduleInfo.updateSeatScheduleInfoStatus(SeatStatus.HOLD); + seatScheduleInfoRepository.save(seatScheduleInfo); + redisTemplate.opsForHash().put(redisStatusHashKey, selectedId, SeatStatus.HOLD.name()); - String redisSeatKey = "seatStatus:" + seatScheduleInfo.getId(); - redisTemplate.opsForValue().set(redisSeatKey, SeatStatus.HOLD.name(), Duration.ofMinutes(60)); - } + //유저가 선점한 좌석 정보 - 결제 진행 상태일 때의 만료 시간 1시간 + redisTemplate.expire(redisSelectedKey, Duration.ofMinutes(60)); } } diff --git a/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java b/src/main/java/org/example/siljeun/domain/schedule/scheduler/SeatStatusPreloaderScheduler.java similarity index 95% rename from src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java rename to src/main/java/org/example/siljeun/domain/schedule/scheduler/SeatStatusPreloaderScheduler.java index 6257c11..73b269f 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/scheduler/TicketingRedisScheduler.java +++ b/src/main/java/org/example/siljeun/domain/schedule/scheduler/SeatStatusPreloaderScheduler.java @@ -18,12 +18,12 @@ @Slf4j @Component @RequiredArgsConstructor -public class TicketingRedisScheduler { +public class SeatStatusPreloaderScheduler { private final ScheduleRepository scheduleRepository; private final SeatScheduleInfoRepository seatScheduleInfoRepository; private final RedisTemplate redisTemplate; - @Scheduled(fixedRate = 60_000) + @Scheduled(fixedRate = 300_000) public void loadSeatStatusToRedis() { LocalDateTime now = LocalDateTime.now(); LocalDateTime fiveMinutesLater = now.plusMinutes(5); //티켓팅 시작 시간이 임박한 회차에 대해 미리 Redis에 정보 적재 diff --git a/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java b/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java index b1b2512..b57b853 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java +++ b/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java @@ -42,8 +42,12 @@ public void selectSeat(Long userId, Long scheduleId, Long seatScheduleInfoId) { //userId와 schedule Id가 key이고 seatSchduleInfoId로 구성된 Set이 value인 형태로 저장 String redisSelectedKey = "user:scheduleSelected" + userId + ":" + scheduleId; - redisTemplate.opsForSet().add(redisSelectedKey, seatScheduleInfoId.toString()); - //key에 해당하는 set 데이터를 TTL 5분으로 업데이트 + + if (Boolean.TRUE.equals(redisTemplate.hasKey(redisSelectedKey))) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "1인당 1개의 좌석만 예약 가능합니다."); + } + + redisTemplate.opsForValue().set(redisSelectedKey, seatScheduleInfoId.toString()); redisTemplate.expire(redisSelectedKey, Duration.ofMinutes(5)); //seatScheduleInfoId의 seatStatus 상태 변경 From 894e46e93551bcfaf26013ec806e2318098da1dd Mon Sep 17 00:00:00 2001 From: crocusia <132359536+crocusia@users.noreply.github.com> Date: Fri, 23 May 2025 07:04:29 +0900 Subject: [PATCH 06/10] =?UTF-8?q?refactor=20:=20seat,=20seatScheduleInfo?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/reservation/service/ReservationService.java | 5 +---- .../domain/schedule/service/ScheduleServiceImpl.java | 4 ++-- .../{venue => seat}/controller/VenueSeatController.java | 6 +++--- .../repository/SeatScheduleInfoRepository.java | 2 +- .../{venue => seat}/repository/VenueSeatRepository.java | 2 +- .../service/SeatService.java} | 7 +++---- .../controller/SeatScheduleInfoController.java | 4 ++-- .../dto/request/SeatScheduleInfoCreateRequest.java | 2 +- .../dto/request/SeatScheduleUpdateInfoRequest.java | 2 +- .../dto/request/SeatScheduleUpdateStatusRequest.java | 2 +- .../dto/response/SeatScheduleInfoResponse.java | 2 +- .../scheduler/SeatStatusPreloaderScheduler.java | 4 ++-- .../service/SeatScheduleInfoService.java | 5 ++--- .../schedule/service/SeatScheduleInfoServiceTest.java | 6 +++--- 14 files changed, 24 insertions(+), 29 deletions(-) rename src/main/java/org/example/siljeun/domain/{venue => seat}/controller/VenueSeatController.java (85%) rename src/main/java/org/example/siljeun/domain/{schedule => seat}/repository/SeatScheduleInfoRepository.java (86%) rename src/main/java/org/example/siljeun/domain/{venue => seat}/repository/VenueSeatRepository.java (85%) rename src/main/java/org/example/siljeun/domain/{venue/service/VenueSeatService.java => seat/service/SeatService.java} (88%) rename src/main/java/org/example/siljeun/domain/{schedule => seatscheduleinfo}/controller/SeatScheduleInfoController.java (90%) rename src/main/java/org/example/siljeun/domain/{seat => seatscheduleinfo}/dto/request/SeatScheduleInfoCreateRequest.java (85%) rename src/main/java/org/example/siljeun/domain/{seat => seatscheduleinfo}/dto/request/SeatScheduleUpdateInfoRequest.java (85%) rename src/main/java/org/example/siljeun/domain/{seat => seatscheduleinfo}/dto/request/SeatScheduleUpdateStatusRequest.java (79%) rename src/main/java/org/example/siljeun/domain/{seat => seatscheduleinfo}/dto/response/SeatScheduleInfoResponse.java (71%) rename src/main/java/org/example/siljeun/domain/{schedule => seatscheduleinfo}/scheduler/SeatStatusPreloaderScheduler.java (92%) rename src/main/java/org/example/siljeun/domain/{schedule => seatscheduleinfo}/service/SeatScheduleInfoService.java (95%) diff --git a/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java b/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java index e0067a9..fd0e8c6 100644 --- a/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java +++ b/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java @@ -8,7 +8,7 @@ import org.example.siljeun.domain.reservation.exception.CustomException; import org.example.siljeun.domain.reservation.exception.ErrorCode; import org.example.siljeun.domain.reservation.repository.ReservationRepository; -import org.example.siljeun.domain.schedule.repository.SeatScheduleInfoRepository; +import org.example.siljeun.domain.seat.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; import org.example.siljeun.domain.user.entity.User; @@ -18,9 +18,6 @@ import org.springframework.transaction.annotation.Transactional; import java.time.Duration; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor diff --git a/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java b/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java index b421d43..237b64a 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java +++ b/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java @@ -11,11 +11,11 @@ import org.example.siljeun.domain.schedule.dto.response.ScheduleSimpleResponse; import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.schedule.repository.ScheduleRepository; -import org.example.siljeun.domain.schedule.repository.SeatScheduleInfoRepository; +import org.example.siljeun.domain.seat.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.Seat; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; -import org.example.siljeun.domain.venue.repository.VenueSeatRepository; +import org.example.siljeun.domain.seat.repository.VenueSeatRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/org/example/siljeun/domain/venue/controller/VenueSeatController.java b/src/main/java/org/example/siljeun/domain/seat/controller/VenueSeatController.java similarity index 85% rename from src/main/java/org/example/siljeun/domain/venue/controller/VenueSeatController.java rename to src/main/java/org/example/siljeun/domain/seat/controller/VenueSeatController.java index 8805978..bbcc9ae 100644 --- a/src/main/java/org/example/siljeun/domain/venue/controller/VenueSeatController.java +++ b/src/main/java/org/example/siljeun/domain/seat/controller/VenueSeatController.java @@ -1,9 +1,9 @@ -package org.example.siljeun.domain.venue.controller; +package org.example.siljeun.domain.seat.controller; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.example.siljeun.domain.seat.dto.request.SeatCreateRequest; -import org.example.siljeun.domain.venue.service.VenueSeatService; +import org.example.siljeun.domain.seat.service.SeatService; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; @@ -18,7 +18,7 @@ @RequestMapping("/venues") public class VenueSeatController { - private final VenueSeatService venueSeatService; + private final SeatService venueSeatService; //좌석 정보를 CSV 파일 또는 GUI로 다수의 정보를 한번에 등록한다. @PostMapping("/{venueId}/seats") diff --git a/src/main/java/org/example/siljeun/domain/schedule/repository/SeatScheduleInfoRepository.java b/src/main/java/org/example/siljeun/domain/seat/repository/SeatScheduleInfoRepository.java similarity index 86% rename from src/main/java/org/example/siljeun/domain/schedule/repository/SeatScheduleInfoRepository.java rename to src/main/java/org/example/siljeun/domain/seat/repository/SeatScheduleInfoRepository.java index 20f047d..7b6780d 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/repository/SeatScheduleInfoRepository.java +++ b/src/main/java/org/example/siljeun/domain/seat/repository/SeatScheduleInfoRepository.java @@ -1,4 +1,4 @@ -package org.example.siljeun.domain.schedule.repository; +package org.example.siljeun.domain.seat.repository; import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; diff --git a/src/main/java/org/example/siljeun/domain/venue/repository/VenueSeatRepository.java b/src/main/java/org/example/siljeun/domain/seat/repository/VenueSeatRepository.java similarity index 85% rename from src/main/java/org/example/siljeun/domain/venue/repository/VenueSeatRepository.java rename to src/main/java/org/example/siljeun/domain/seat/repository/VenueSeatRepository.java index 8fbffa1..04a3561 100644 --- a/src/main/java/org/example/siljeun/domain/venue/repository/VenueSeatRepository.java +++ b/src/main/java/org/example/siljeun/domain/seat/repository/VenueSeatRepository.java @@ -1,4 +1,4 @@ -package org.example.siljeun.domain.venue.repository; +package org.example.siljeun.domain.seat.repository; import org.example.siljeun.domain.seat.entity.Seat; import org.example.siljeun.domain.venue.entity.Venue; diff --git a/src/main/java/org/example/siljeun/domain/venue/service/VenueSeatService.java b/src/main/java/org/example/siljeun/domain/seat/service/SeatService.java similarity index 88% rename from src/main/java/org/example/siljeun/domain/venue/service/VenueSeatService.java rename to src/main/java/org/example/siljeun/domain/seat/service/SeatService.java index bc6fe4b..ae577bc 100644 --- a/src/main/java/org/example/siljeun/domain/venue/service/VenueSeatService.java +++ b/src/main/java/org/example/siljeun/domain/seat/service/SeatService.java @@ -1,22 +1,21 @@ -package org.example.siljeun.domain.venue.service; +package org.example.siljeun.domain.seat.service; import lombok.RequiredArgsConstructor; import org.example.siljeun.domain.seat.dto.request.SeatCreateRequest; import org.example.siljeun.domain.seat.entity.Seat; import org.example.siljeun.domain.venue.entity.Venue; import org.example.siljeun.domain.venue.repository.VenueRepository; -import org.example.siljeun.domain.venue.repository.VenueSeatRepository; +import org.example.siljeun.domain.seat.repository.VenueSeatRepository; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; -import java.util.ArrayList; import java.util.List; @Service @RequiredArgsConstructor -public class VenueSeatService { +public class SeatService { private final VenueRepository venueRepository; private final VenueSeatRepository venueSeatRepository; diff --git a/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/controller/SeatScheduleInfoController.java similarity index 90% rename from src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java rename to src/main/java/org/example/siljeun/domain/seatscheduleinfo/controller/SeatScheduleInfoController.java index 7497bcc..ddf7028 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/controller/SeatScheduleInfoController.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/controller/SeatScheduleInfoController.java @@ -1,7 +1,7 @@ -package org.example.siljeun.domain.schedule.controller; +package org.example.siljeun.domain.seatscheduleinfo.controller; import lombok.RequiredArgsConstructor; -import org.example.siljeun.domain.schedule.service.SeatScheduleInfoService; +import org.example.siljeun.domain.seatscheduleinfo.service.SeatScheduleInfoService; import org.example.siljeun.global.security.PrincipalDetails; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; diff --git a/src/main/java/org/example/siljeun/domain/seat/dto/request/SeatScheduleInfoCreateRequest.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/dto/request/SeatScheduleInfoCreateRequest.java similarity index 85% rename from src/main/java/org/example/siljeun/domain/seat/dto/request/SeatScheduleInfoCreateRequest.java rename to src/main/java/org/example/siljeun/domain/seatscheduleinfo/dto/request/SeatScheduleInfoCreateRequest.java index 6abf4db..f1014ba 100644 --- a/src/main/java/org/example/siljeun/domain/seat/dto/request/SeatScheduleInfoCreateRequest.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/dto/request/SeatScheduleInfoCreateRequest.java @@ -1,4 +1,4 @@ -package org.example.siljeun.domain.seat.dto.request; +package org.example.siljeun.domain.seatscheduleinfo.dto.request; import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/org/example/siljeun/domain/seat/dto/request/SeatScheduleUpdateInfoRequest.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/dto/request/SeatScheduleUpdateInfoRequest.java similarity index 85% rename from src/main/java/org/example/siljeun/domain/seat/dto/request/SeatScheduleUpdateInfoRequest.java rename to src/main/java/org/example/siljeun/domain/seatscheduleinfo/dto/request/SeatScheduleUpdateInfoRequest.java index 2098038..395843d 100644 --- a/src/main/java/org/example/siljeun/domain/seat/dto/request/SeatScheduleUpdateInfoRequest.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/dto/request/SeatScheduleUpdateInfoRequest.java @@ -1,4 +1,4 @@ -package org.example.siljeun.domain.seat.dto.request; +package org.example.siljeun.domain.seatscheduleinfo.dto.request; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/org/example/siljeun/domain/seat/dto/request/SeatScheduleUpdateStatusRequest.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/dto/request/SeatScheduleUpdateStatusRequest.java similarity index 79% rename from src/main/java/org/example/siljeun/domain/seat/dto/request/SeatScheduleUpdateStatusRequest.java rename to src/main/java/org/example/siljeun/domain/seatscheduleinfo/dto/request/SeatScheduleUpdateStatusRequest.java index 58005be..b3fbf42 100644 --- a/src/main/java/org/example/siljeun/domain/seat/dto/request/SeatScheduleUpdateStatusRequest.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/dto/request/SeatScheduleUpdateStatusRequest.java @@ -1,4 +1,4 @@ -package org.example.siljeun.domain.seat.dto.request; +package org.example.siljeun.domain.seatscheduleinfo.dto.request; import jakarta.validation.constraints.NotNull; import org.example.siljeun.domain.seat.enums.SeatStatus; diff --git a/src/main/java/org/example/siljeun/domain/seat/dto/response/SeatScheduleInfoResponse.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/dto/response/SeatScheduleInfoResponse.java similarity index 71% rename from src/main/java/org/example/siljeun/domain/seat/dto/response/SeatScheduleInfoResponse.java rename to src/main/java/org/example/siljeun/domain/seatscheduleinfo/dto/response/SeatScheduleInfoResponse.java index 15ecc82..2955274 100644 --- a/src/main/java/org/example/siljeun/domain/seat/dto/response/SeatScheduleInfoResponse.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/dto/response/SeatScheduleInfoResponse.java @@ -1,4 +1,4 @@ -package org.example.siljeun.domain.seat.dto.response; +package org.example.siljeun.domain.seatscheduleinfo.dto.response; public record SeatScheduleInfoResponse( Long seatScheduleInfoId, diff --git a/src/main/java/org/example/siljeun/domain/schedule/scheduler/SeatStatusPreloaderScheduler.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java similarity index 92% rename from src/main/java/org/example/siljeun/domain/schedule/scheduler/SeatStatusPreloaderScheduler.java rename to src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java index 73b269f..18be774 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/scheduler/SeatStatusPreloaderScheduler.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java @@ -1,10 +1,10 @@ -package org.example.siljeun.domain.schedule.scheduler; +package org.example.siljeun.domain.seatscheduleinfo.scheduler; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.schedule.repository.ScheduleRepository; -import org.example.siljeun.domain.schedule.repository.SeatScheduleInfoRepository; +import org.example.siljeun.domain.seat.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; diff --git a/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java similarity index 95% rename from src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java rename to src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java index b57b853..90015a2 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoService.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java @@ -1,15 +1,14 @@ -package org.example.siljeun.domain.schedule.service; +package org.example.siljeun.domain.seatscheduleinfo.service; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.schedule.repository.ScheduleRepository; -import org.example.siljeun.domain.schedule.repository.SeatScheduleInfoRepository; +import org.example.siljeun.domain.seat.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; import org.example.siljeun.global.lock.DistributedLock; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; diff --git a/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java b/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java index a839b08..356aee0 100644 --- a/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java +++ b/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java @@ -5,13 +5,14 @@ import org.example.siljeun.domain.concert.repository.ConcertRepository; import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.schedule.repository.ScheduleRepository; -import org.example.siljeun.domain.schedule.repository.SeatScheduleInfoRepository; +import org.example.siljeun.domain.seat.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.Seat; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; +import org.example.siljeun.domain.seatscheduleinfo.service.SeatScheduleInfoService; import org.example.siljeun.domain.venue.entity.Venue; import org.example.siljeun.domain.venue.repository.VenueRepository; -import org.example.siljeun.domain.venue.repository.VenueSeatRepository; +import org.example.siljeun.domain.seat.repository.VenueSeatRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -21,7 +22,6 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; From 53559df2ee64cf6de1d9778fd4cdf2f8d99ca739 Mon Sep 17 00:00:00 2001 From: crocusia <132359536+crocusia@users.noreply.github.com> Date: Fri, 23 May 2025 07:24:26 +0900 Subject: [PATCH 07/10] =?UTF-8?q?refactor=20:=20Seat=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20=EC=9D=BC=EB=B6=80=20=EC=9C=84=EC=B9=98?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/reservation/service/ReservationService.java | 2 +- .../domain/schedule/service/ScheduleServiceImpl.java | 8 ++++---- .../{VenueSeatController.java => SeatController.java} | 6 +++--- .../{VenueSeatRepository.java => SeatRepository.java} | 2 +- .../example/siljeun/domain/seat/service/SeatService.java | 6 +++--- .../repository/SeatScheduleInfoRepository.java | 2 +- .../scheduler/SeatStatusPreloaderScheduler.java | 2 +- .../seatscheduleinfo/service/SeatScheduleInfoService.java | 2 +- .../schedule/service/SeatScheduleInfoServiceTest.java | 8 ++++---- 9 files changed, 19 insertions(+), 19 deletions(-) rename src/main/java/org/example/siljeun/domain/seat/controller/{VenueSeatController.java => SeatController.java} (87%) rename src/main/java/org/example/siljeun/domain/seat/repository/{VenueSeatRepository.java => SeatRepository.java} (79%) rename src/main/java/org/example/siljeun/domain/{seat => seatscheduleinfo}/repository/SeatScheduleInfoRepository.java (85%) diff --git a/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java b/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java index fd0e8c6..ee4daf8 100644 --- a/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java +++ b/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java @@ -8,7 +8,7 @@ import org.example.siljeun.domain.reservation.exception.CustomException; import org.example.siljeun.domain.reservation.exception.ErrorCode; import org.example.siljeun.domain.reservation.repository.ReservationRepository; -import org.example.siljeun.domain.seat.repository.SeatScheduleInfoRepository; +import org.example.siljeun.domain.seatscheduleinfo.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; import org.example.siljeun.domain.user.entity.User; diff --git a/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java b/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java index 237b64a..daf46d8 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java +++ b/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java @@ -11,11 +11,11 @@ import org.example.siljeun.domain.schedule.dto.response.ScheduleSimpleResponse; import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.schedule.repository.ScheduleRepository; -import org.example.siljeun.domain.seat.repository.SeatScheduleInfoRepository; +import org.example.siljeun.domain.seatscheduleinfo.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.Seat; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; -import org.example.siljeun.domain.seat.repository.VenueSeatRepository; +import org.example.siljeun.domain.seat.repository.SeatRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,7 +26,7 @@ public class ScheduleServiceImpl implements ScheduleService { private final ScheduleRepository scheduleRepository; private final ConcertRepository concertRepository; - private final VenueSeatRepository venueSeatRepository; + private final SeatRepository seatRepository; private final SeatScheduleInfoRepository seatScheduleInfoRepository; @Override @@ -44,7 +44,7 @@ public ScheduleSimpleResponse createSchedule(ScheduleCreateRequest request) { Schedule saved = scheduleRepository.save(schedule); //회차 생성 시, 회차별 좌석 정보도 함께 생성 - List seats = venueSeatRepository.findByVenue(concert.getVenue()); + List seats = seatRepository.findByVenue(concert.getVenue()); List seatInfos = seats.stream() .map(seat -> SeatScheduleInfo.from( diff --git a/src/main/java/org/example/siljeun/domain/seat/controller/VenueSeatController.java b/src/main/java/org/example/siljeun/domain/seat/controller/SeatController.java similarity index 87% rename from src/main/java/org/example/siljeun/domain/seat/controller/VenueSeatController.java rename to src/main/java/org/example/siljeun/domain/seat/controller/SeatController.java index bbcc9ae..c802e4f 100644 --- a/src/main/java/org/example/siljeun/domain/seat/controller/VenueSeatController.java +++ b/src/main/java/org/example/siljeun/domain/seat/controller/SeatController.java @@ -16,9 +16,9 @@ @Controller @RequiredArgsConstructor @RequestMapping("/venues") -public class VenueSeatController { +public class SeatController { - private final SeatService venueSeatService; + private final SeatService seatService; //좌석 정보를 CSV 파일 또는 GUI로 다수의 정보를 한번에 등록한다. @PostMapping("/{venueId}/seats") @@ -26,7 +26,7 @@ public ResponseEntity createVenueSeats( @PathVariable Long venueId, @RequestBody @Valid List seatCreateRequests ){ - venueSeatService.createSeats(venueId, seatCreateRequests); + seatService.createSeats(venueId, seatCreateRequests); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/org/example/siljeun/domain/seat/repository/VenueSeatRepository.java b/src/main/java/org/example/siljeun/domain/seat/repository/SeatRepository.java similarity index 79% rename from src/main/java/org/example/siljeun/domain/seat/repository/VenueSeatRepository.java rename to src/main/java/org/example/siljeun/domain/seat/repository/SeatRepository.java index 04a3561..cf44e06 100644 --- a/src/main/java/org/example/siljeun/domain/seat/repository/VenueSeatRepository.java +++ b/src/main/java/org/example/siljeun/domain/seat/repository/SeatRepository.java @@ -6,7 +6,7 @@ import java.util.List; -public interface VenueSeatRepository extends JpaRepository { +public interface SeatRepository extends JpaRepository { List findByVenue(Venue venue); } diff --git a/src/main/java/org/example/siljeun/domain/seat/service/SeatService.java b/src/main/java/org/example/siljeun/domain/seat/service/SeatService.java index ae577bc..aee0a14 100644 --- a/src/main/java/org/example/siljeun/domain/seat/service/SeatService.java +++ b/src/main/java/org/example/siljeun/domain/seat/service/SeatService.java @@ -5,7 +5,7 @@ import org.example.siljeun.domain.seat.entity.Seat; import org.example.siljeun.domain.venue.entity.Venue; import org.example.siljeun.domain.venue.repository.VenueRepository; -import org.example.siljeun.domain.seat.repository.VenueSeatRepository; +import org.example.siljeun.domain.seat.repository.SeatRepository; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -18,7 +18,7 @@ public class SeatService { private final VenueRepository venueRepository; - private final VenueSeatRepository venueSeatRepository; + private final SeatRepository seatRepository; @Transactional public void createSeats(Long venueId, List seatCreateRequests){ @@ -31,6 +31,6 @@ public void createSeats(Long venueId, List seatCreateRequests .map(request -> Seat.from(venue, request)) .toList(); - venueSeatRepository.saveAll(seats); + seatRepository.saveAll(seats); } } diff --git a/src/main/java/org/example/siljeun/domain/seat/repository/SeatScheduleInfoRepository.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/repository/SeatScheduleInfoRepository.java similarity index 85% rename from src/main/java/org/example/siljeun/domain/seat/repository/SeatScheduleInfoRepository.java rename to src/main/java/org/example/siljeun/domain/seatscheduleinfo/repository/SeatScheduleInfoRepository.java index 7b6780d..914d4d6 100644 --- a/src/main/java/org/example/siljeun/domain/seat/repository/SeatScheduleInfoRepository.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/repository/SeatScheduleInfoRepository.java @@ -1,4 +1,4 @@ -package org.example.siljeun.domain.seat.repository; +package org.example.siljeun.domain.seatscheduleinfo.repository; import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; diff --git a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java index 18be774..ca12ae6 100644 --- a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java @@ -4,7 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.schedule.repository.ScheduleRepository; -import org.example.siljeun.domain.seat.repository.SeatScheduleInfoRepository; +import org.example.siljeun.domain.seatscheduleinfo.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; diff --git a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java index 90015a2..8bf1dba 100644 --- a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java @@ -5,7 +5,7 @@ import lombok.extern.slf4j.Slf4j; import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.schedule.repository.ScheduleRepository; -import org.example.siljeun.domain.seat.repository.SeatScheduleInfoRepository; +import org.example.siljeun.domain.seatscheduleinfo.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; import org.example.siljeun.global.lock.DistributedLock; diff --git a/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java b/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java index 356aee0..5f4766b 100644 --- a/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java +++ b/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java @@ -5,14 +5,14 @@ import org.example.siljeun.domain.concert.repository.ConcertRepository; import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.schedule.repository.ScheduleRepository; -import org.example.siljeun.domain.seat.repository.SeatScheduleInfoRepository; +import org.example.siljeun.domain.seatscheduleinfo.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.Seat; import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; import org.example.siljeun.domain.seatscheduleinfo.service.SeatScheduleInfoService; import org.example.siljeun.domain.venue.entity.Venue; import org.example.siljeun.domain.venue.repository.VenueRepository; -import org.example.siljeun.domain.seat.repository.VenueSeatRepository; +import org.example.siljeun.domain.seat.repository.SeatRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -50,7 +50,7 @@ class SeatScheduleInfoServiceTest { private VenueRepository venueRepository; @Autowired - private VenueSeatRepository venueSeatRepository; + private SeatRepository seatRepository; @Autowired private ScheduleRepository scheduleRepository; @@ -69,7 +69,7 @@ class SeatScheduleInfoServiceTest { void setUp() { redisTemplate.getConnectionFactory().getConnection().flushAll(); Venue venue = venueRepository.save(new Venue("샤롯데씨어터", "잠실 어딘가", 1)); - seat = venueSeatRepository.save(new Seat(venue, "A", "1", "1", "VIP", 180000)); + seat = seatRepository.save(new Seat(venue, "A", "1", "1", "VIP", 180000)); Concert concert = concertRepository.save(new Concert("위키드", "엘파바와 글린다", ConcertCategory.MUSICAL, venue, 1000)); schedule = scheduleRepository.save(new Schedule(concert, LocalDateTime.of(2025, 6, 6, 14, 30), LocalDateTime.of(2025, 5, 6, 10, 0))); } From f8ecfba89dd4b1619c6a8f7a6102e4700cc5c5ac Mon Sep 17 00:00:00 2001 From: crocusia <132359536+crocusia@users.noreply.github.com> Date: Fri, 23 May 2025 07:27:01 +0900 Subject: [PATCH 08/10] =?UTF-8?q?refactor=20:=20=EB=88=84=EB=9D=BD?= =?UTF-8?q?=EB=90=9C=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/siljeun/domain/reservation/entity/Reservation.java | 2 +- .../siljeun/domain/reservation/service/ReservationService.java | 2 +- .../siljeun/domain/schedule/service/ScheduleServiceImpl.java | 2 +- .../{seat => seatscheduleinfo}/entity/SeatScheduleInfo.java | 3 ++- .../repository/SeatScheduleInfoRepository.java | 2 +- .../scheduler/SeatStatusPreloaderScheduler.java | 2 +- .../seatscheduleinfo/service/SeatScheduleInfoService.java | 2 +- .../domain/schedule/service/SeatScheduleInfoServiceTest.java | 2 +- 8 files changed, 9 insertions(+), 8 deletions(-) rename src/main/java/org/example/siljeun/domain/{seat => seatscheduleinfo}/entity/SeatScheduleInfo.java (94%) diff --git a/src/main/java/org/example/siljeun/domain/reservation/entity/Reservation.java b/src/main/java/org/example/siljeun/domain/reservation/entity/Reservation.java index b1ed8b5..b4a11e8 100644 --- a/src/main/java/org/example/siljeun/domain/reservation/entity/Reservation.java +++ b/src/main/java/org/example/siljeun/domain/reservation/entity/Reservation.java @@ -19,7 +19,7 @@ import org.example.siljeun.domain.reservation.dto.request.UpdatePriceRequest; import org.example.siljeun.domain.reservation.enums.Discount; import org.example.siljeun.domain.reservation.enums.TicketReceipt; -import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; +import org.example.siljeun.domain.seatscheduleinfo.entity.SeatScheduleInfo; import org.example.siljeun.domain.user.entity.User; import org.hibernate.annotations.DynamicUpdate; import org.springframework.data.annotation.CreatedDate; diff --git a/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java b/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java index ee4daf8..01c7c25 100644 --- a/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java +++ b/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java @@ -9,7 +9,7 @@ import org.example.siljeun.domain.reservation.exception.ErrorCode; import org.example.siljeun.domain.reservation.repository.ReservationRepository; import org.example.siljeun.domain.seatscheduleinfo.repository.SeatScheduleInfoRepository; -import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; +import org.example.siljeun.domain.seatscheduleinfo.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; import org.example.siljeun.domain.user.entity.User; import org.example.siljeun.domain.user.repository.UserRepository; diff --git a/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java b/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java index daf46d8..0605591 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java +++ b/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java @@ -13,7 +13,7 @@ import org.example.siljeun.domain.schedule.repository.ScheduleRepository; import org.example.siljeun.domain.seatscheduleinfo.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.Seat; -import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; +import org.example.siljeun.domain.seatscheduleinfo.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; import org.example.siljeun.domain.seat.repository.SeatRepository; import org.springframework.stereotype.Service; diff --git a/src/main/java/org/example/siljeun/domain/seat/entity/SeatScheduleInfo.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/entity/SeatScheduleInfo.java similarity index 94% rename from src/main/java/org/example/siljeun/domain/seat/entity/SeatScheduleInfo.java rename to src/main/java/org/example/siljeun/domain/seatscheduleinfo/entity/SeatScheduleInfo.java index c97daab..0fbfb98 100644 --- a/src/main/java/org/example/siljeun/domain/seat/entity/SeatScheduleInfo.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/entity/SeatScheduleInfo.java @@ -1,4 +1,4 @@ -package org.example.siljeun.domain.seat.entity; +package org.example.siljeun.domain.seatscheduleinfo.entity; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -12,6 +12,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.example.siljeun.domain.schedule.entity.Schedule; +import org.example.siljeun.domain.seat.entity.Seat; import org.example.siljeun.domain.seat.enums.SeatStatus; import org.example.siljeun.global.entity.BaseEntity; diff --git a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/repository/SeatScheduleInfoRepository.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/repository/SeatScheduleInfoRepository.java index 914d4d6..c4b5ba7 100644 --- a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/repository/SeatScheduleInfoRepository.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/repository/SeatScheduleInfoRepository.java @@ -1,7 +1,7 @@ package org.example.siljeun.domain.seatscheduleinfo.repository; import org.example.siljeun.domain.schedule.entity.Schedule; -import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; +import org.example.siljeun.domain.seatscheduleinfo.entity.SeatScheduleInfo; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java index ca12ae6..72d1529 100644 --- a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java @@ -5,7 +5,7 @@ import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.schedule.repository.ScheduleRepository; import org.example.siljeun.domain.seatscheduleinfo.repository.SeatScheduleInfoRepository; -import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; +import org.example.siljeun.domain.seatscheduleinfo.entity.SeatScheduleInfo; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; diff --git a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java index 8bf1dba..9413a04 100644 --- a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java @@ -6,7 +6,7 @@ import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.schedule.repository.ScheduleRepository; import org.example.siljeun.domain.seatscheduleinfo.repository.SeatScheduleInfoRepository; -import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; +import org.example.siljeun.domain.seatscheduleinfo.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; import org.example.siljeun.global.lock.DistributedLock; import org.springframework.data.redis.core.RedisTemplate; diff --git a/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java b/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java index 5f4766b..b4ab1b6 100644 --- a/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java +++ b/src/test/java/org/example/siljeun/domain/schedule/service/SeatScheduleInfoServiceTest.java @@ -7,7 +7,7 @@ import org.example.siljeun.domain.schedule.repository.ScheduleRepository; import org.example.siljeun.domain.seatscheduleinfo.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seat.entity.Seat; -import org.example.siljeun.domain.seat.entity.SeatScheduleInfo; +import org.example.siljeun.domain.seatscheduleinfo.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; import org.example.siljeun.domain.seatscheduleinfo.service.SeatScheduleInfoService; import org.example.siljeun.domain.venue.entity.Venue; From 296c2d6bad3410847977fbf1bed6b3af0c823b78 Mon Sep 17 00:00:00 2001 From: crocusia <132359536+crocusia@users.noreply.github.com> Date: Fri, 23 May 2025 12:15:28 +0900 Subject: [PATCH 09/10] =?UTF-8?q?refactor=20:=20redis=20key=20=EC=98=A4?= =?UTF-8?q?=ED=83=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 5 ++- .../dto/request/ReservationCreateRequest.java | 9 +++++ .../service/ReservationService.java | 9 ++++- .../domain/schedule/entity/Schedule.java | 15 +++----- .../schedule/service/ScheduleServiceImpl.java | 37 ++++++++++--------- .../seat/controller/SeatController.java | 5 ++- .../siljeun/domain/seat/entity/Seat.java | 4 ++ .../domain/seat/service/SeatService.java | 3 ++ .../SeatScheduleInfoController.java | 5 ++- .../SeatScheduleInfoRepository.java | 2 + .../SeatStatusPreloaderScheduler.java | 5 ++- .../service/SeatScheduleInfoService.java | 18 +++++++-- 12 files changed, 77 insertions(+), 40 deletions(-) create mode 100644 src/main/java/org/example/siljeun/domain/reservation/dto/request/ReservationCreateRequest.java diff --git a/src/main/java/org/example/siljeun/domain/reservation/controller/ReservationController.java b/src/main/java/org/example/siljeun/domain/reservation/controller/ReservationController.java index a1815e5..02e9343 100644 --- a/src/main/java/org/example/siljeun/domain/reservation/controller/ReservationController.java +++ b/src/main/java/org/example/siljeun/domain/reservation/controller/ReservationController.java @@ -2,6 +2,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.example.siljeun.domain.reservation.dto.request.ReservationCreateRequest; import org.example.siljeun.domain.reservation.dto.request.UpdatePriceRequest; import org.example.siljeun.domain.reservation.dto.response.ReservationInfoResponse; import org.example.siljeun.domain.reservation.service.ReservationService; @@ -46,10 +47,10 @@ public ResponseEntity> findById( @PostMapping() public ResponseEntity> createReservation( - @PathVariable Long scheduleId, + @RequestBody @Valid ReservationCreateRequest reservationCreateRequest, @AuthenticationPrincipal PrincipalDetails userDetails ){ - reservationService.createReservation(scheduleId, userDetails.getUserId()); + reservationService.createReservation(reservationCreateRequest, userDetails.getUserId()); return ResponseEntity.ok(ResponseDto.success("결제 진행하기", null)); } } diff --git a/src/main/java/org/example/siljeun/domain/reservation/dto/request/ReservationCreateRequest.java b/src/main/java/org/example/siljeun/domain/reservation/dto/request/ReservationCreateRequest.java new file mode 100644 index 0000000..177d799 --- /dev/null +++ b/src/main/java/org/example/siljeun/domain/reservation/dto/request/ReservationCreateRequest.java @@ -0,0 +1,9 @@ +package org.example.siljeun.domain.reservation.dto.request; + +import jakarta.validation.constraints.NotNull; + +public record ReservationCreateRequest( + @NotNull Long scheduleId +) +{ +} diff --git a/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java b/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java index 01c7c25..bb9607b 100644 --- a/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java +++ b/src/main/java/org/example/siljeun/domain/reservation/service/ReservationService.java @@ -2,6 +2,8 @@ import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.example.siljeun.domain.reservation.dto.request.ReservationCreateRequest; import org.example.siljeun.domain.reservation.dto.request.UpdatePriceRequest; import org.example.siljeun.domain.reservation.dto.response.ReservationInfoResponse; import org.example.siljeun.domain.reservation.entity.Reservation; @@ -19,6 +21,7 @@ import java.time.Duration; +@Slf4j @Service @RequiredArgsConstructor public class ReservationService { @@ -85,14 +88,16 @@ public ReservationInfoResponse findById(String username, Long reservationId) { } @Transactional - public void createReservation(Long scheduleId, Long userId){ + public void createReservation(ReservationCreateRequest reservationCreateRequest, Long userId){ //유저 확인 User user = userRepository.findById(userId) .orElseThrow(() -> new EntityNotFoundException("유저를 찾을 수 없습니다.")); + Long scheduleId = reservationCreateRequest.scheduleId(); //유저가 해당 회차에 선택한 좌석 검증 - String redisSelectedKey = "user:scheduleSelected:" + userId + ":" + scheduleId; + String redisSelectedKey = "user:scheduleSelected" + userId + ":" + scheduleId; + log.info("예매 정보 생성 시도 user : " + userId + "scheduleId : " + scheduleId + " key) " + redisSelectedKey ); String selectedId = redisTemplate.opsForValue().get(redisSelectedKey); if (selectedId == null) { diff --git a/src/main/java/org/example/siljeun/domain/schedule/entity/Schedule.java b/src/main/java/org/example/siljeun/domain/schedule/entity/Schedule.java index d5b7129..d294cf0 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/entity/Schedule.java +++ b/src/main/java/org/example/siljeun/domain/schedule/entity/Schedule.java @@ -1,18 +1,15 @@ package org.example.siljeun.domain.schedule.entity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; +import jakarta.persistence.*; + import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + import lombok.Getter; import lombok.NoArgsConstructor; import org.example.siljeun.domain.concert.entity.Concert; +import org.example.siljeun.domain.seatscheduleinfo.entity.SeatScheduleInfo; import org.example.siljeun.global.entity.BaseEntity; @Entity diff --git a/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java b/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java index 0605591..d205557 100644 --- a/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java +++ b/src/main/java/org/example/siljeun/domain/schedule/service/ScheduleServiceImpl.java @@ -1,8 +1,10 @@ package org.example.siljeun.domain.schedule.service; import jakarta.persistence.EntityNotFoundException; + import java.util.List; import java.util.NoSuchElementException; + import lombok.RequiredArgsConstructor; import org.example.siljeun.domain.concert.entity.Concert; import org.example.siljeun.domain.concert.repository.ConcertRepository; @@ -73,22 +75,23 @@ public ScheduleSimpleResponse updateSchedule(Long id, ScheduleUpdateRequest requ schedule.getTicketingStartTime()); } - @Override - @Transactional - public void deleteSchedule(Long id) { - if (!scheduleRepository.existsById(id)) { - throw new EntityNotFoundException("해당 회차가 존재하지 않습니다."); + @Override + @Transactional + public void deleteSchedule(Long id) { + Schedule schedule = scheduleRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("해당 회차가 존재하지 않습니다.")); + + seatScheduleInfoRepository.deleteBySchedule(schedule); + scheduleRepository.deleteById(id); + } + + @Override + @Transactional(readOnly = true) + public List getSchedulesByConcertId(Long concertId) { + List schedules = scheduleRepository.findByConcertIdWithConcert(concertId); + return schedules.stream() + .map( + s -> new ScheduleSimpleResponse(s.getId(), s.getStartTime(), s.getTicketingStartTime())) + .toList(); } - scheduleRepository.deleteById(id); - } - - @Override - @Transactional(readOnly = true) - public List getSchedulesByConcertId(Long concertId) { - List schedules = scheduleRepository.findByConcertIdWithConcert(concertId); - return schedules.stream() - .map( - s -> new ScheduleSimpleResponse(s.getId(), s.getStartTime(), s.getTicketingStartTime())) - .toList(); - } } diff --git a/src/main/java/org/example/siljeun/domain/seat/controller/SeatController.java b/src/main/java/org/example/siljeun/domain/seat/controller/SeatController.java index c802e4f..8e81078 100644 --- a/src/main/java/org/example/siljeun/domain/seat/controller/SeatController.java +++ b/src/main/java/org/example/siljeun/domain/seat/controller/SeatController.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.example.siljeun.domain.seat.dto.request.SeatCreateRequest; import org.example.siljeun.domain.seat.service.SeatService; +import org.example.siljeun.global.dto.ResponseDto; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; @@ -22,11 +23,11 @@ public class SeatController { //좌석 정보를 CSV 파일 또는 GUI로 다수의 정보를 한번에 등록한다. @PostMapping("/{venueId}/seats") - public ResponseEntity createVenueSeats( + public ResponseEntity> createVenueSeats( @PathVariable Long venueId, @RequestBody @Valid List seatCreateRequests ){ seatService.createSeats(venueId, seatCreateRequests); - return ResponseEntity.ok().build(); + return ResponseEntity.ok(ResponseDto.success("좌석 정보가 등록되었습니다.", null)); } } diff --git a/src/main/java/org/example/siljeun/domain/seat/entity/Seat.java b/src/main/java/org/example/siljeun/domain/seat/entity/Seat.java index 47a230c..ac04e55 100644 --- a/src/main/java/org/example/siljeun/domain/seat/entity/Seat.java +++ b/src/main/java/org/example/siljeun/domain/seat/entity/Seat.java @@ -48,4 +48,8 @@ public Seat(Venue venue, String section, String row, String column, String defau public static Seat from(Venue venue, SeatCreateRequest request) { return new Seat(venue, request.section(), request.row(), request.column(), request.defaultGrade(), request.defaultPrice()); } + + public String seatNumber(){ + return section + "구역 " + row + "열 " + column + "번"; + } } diff --git a/src/main/java/org/example/siljeun/domain/seat/service/SeatService.java b/src/main/java/org/example/siljeun/domain/seat/service/SeatService.java index aee0a14..13e60b3 100644 --- a/src/main/java/org/example/siljeun/domain/seat/service/SeatService.java +++ b/src/main/java/org/example/siljeun/domain/seat/service/SeatService.java @@ -25,6 +25,9 @@ public void createSeats(Long venueId, List seatCreateRequests Venue venue = venueRepository.findById(venueId) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "해당 공연장을 찾을 수 없습니다.")); //Throw 예외 설정 필요 + if (seatCreateRequests.size() > venue.getSeatCapacity()) { + throw new IllegalArgumentException("좌석 수가 공연장 수용 인원(capacity)을 초과했습니다."); + } //공연장 ID, 구역, 열, 번호를 바탕으로 고유하도록 설정하였으나 //좌석 정보가 중복되는 경우를 다루지 않아 추후 리팩토링이 필요함 List seats = seatCreateRequests.stream() diff --git a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/controller/SeatScheduleInfoController.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/controller/SeatScheduleInfoController.java index ddf7028..78e96b9 100644 --- a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/controller/SeatScheduleInfoController.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/controller/SeatScheduleInfoController.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import org.example.siljeun.domain.seatscheduleinfo.service.SeatScheduleInfoService; +import org.example.siljeun.global.dto.ResponseDto; import org.example.siljeun.global.security.PrincipalDetails; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -21,13 +22,13 @@ public class SeatScheduleInfoController { private final SeatScheduleInfoService seatScheduleInfoService; @PostMapping("/seat-schedule-infos/{seatScheduleInfoId}") - public ResponseEntity selectSeat( + public ResponseEntity> selectSeat( @PathVariable Long scheduleId, @PathVariable Long seatScheduleInfoId, @AuthenticationPrincipal PrincipalDetails userDetails ) { seatScheduleInfoService.selectSeat(userDetails.getUserId(), scheduleId, seatScheduleInfoId); - return ResponseEntity.ok("좌석이 선택되었습니다."); + return ResponseEntity.ok(ResponseDto.success( "좌석이 선택되었습니다. seatScheduleInfoId : " + seatScheduleInfoId.toString(), null)); } @GetMapping("/seat-schedule-infos") diff --git a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/repository/SeatScheduleInfoRepository.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/repository/SeatScheduleInfoRepository.java index c4b5ba7..e7d0d2e 100644 --- a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/repository/SeatScheduleInfoRepository.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/repository/SeatScheduleInfoRepository.java @@ -9,4 +9,6 @@ public interface SeatScheduleInfoRepository extends JpaRepository { List findAllBySchedule(Schedule schedule); + + void deleteBySchedule(Schedule schedule); } diff --git a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java index 72d1529..6c05267 100644 --- a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/scheduler/SeatStatusPreloaderScheduler.java @@ -23,10 +23,10 @@ public class SeatStatusPreloaderScheduler { private final SeatScheduleInfoRepository seatScheduleInfoRepository; private final RedisTemplate redisTemplate; - @Scheduled(fixedRate = 300_000) + @Scheduled(fixedRate = 60_000) public void loadSeatStatusToRedis() { LocalDateTime now = LocalDateTime.now(); - LocalDateTime fiveMinutesLater = now.plusMinutes(5); //티켓팅 시작 시간이 임박한 회차에 대해 미리 Redis에 정보 적재 + LocalDateTime fiveMinutesLater = now.plusMinutes(5); List openedSchedules = scheduleRepository.findAllByTicketingStartTimeBetween(now, fiveMinutesLater); @@ -41,6 +41,7 @@ public void loadSeatStatusToRedis() { } redisTemplate.opsForHash().putAll(key, seatStatusMap); + log.info("✅ scheduleId: {}의 seatSchedulerInfos 적재 완료", schedule.getId()); } } } diff --git a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java index 9413a04..3976a3e 100644 --- a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java @@ -5,6 +5,8 @@ import lombok.extern.slf4j.Slf4j; import org.example.siljeun.domain.schedule.entity.Schedule; import org.example.siljeun.domain.schedule.repository.ScheduleRepository; +import org.example.siljeun.domain.seat.entity.Seat; +import org.example.siljeun.domain.seat.repository.SeatRepository; import org.example.siljeun.domain.seatscheduleinfo.repository.SeatScheduleInfoRepository; import org.example.siljeun.domain.seatscheduleinfo.entity.SeatScheduleInfo; import org.example.siljeun.domain.seat.enums.SeatStatus; @@ -15,6 +17,7 @@ import org.springframework.web.server.ResponseStatusException; import java.time.Duration; +import java.time.LocalDateTime; import java.util.*; @Slf4j @@ -31,15 +34,21 @@ public void selectSeat(Long userId, Long scheduleId, Long seatScheduleInfoId) { SeatScheduleInfo seatScheduleInfo = seatScheduleInfoRepository.findById(seatScheduleInfoId). orElseThrow(() -> new EntityNotFoundException("해당 회차별 좌석 정보가 존재하지 않습니다.")); + Schedule schedule = seatScheduleInfo.getSchedule(); + + if(schedule.getTicketingStartTime().isAfter(LocalDateTime.now())){ + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "예매 불가능한 시간입니다. 예매 오픈 시간 : " + schedule.getTicketingStartTime()); + } + if (!seatScheduleInfo.isAvailable()) { - //log.info("이미 선점된 좌석입니다."); + log.info("이미 선점된 좌석입니다."); throw new ResponseStatusException(HttpStatus.CONFLICT, "이미 선점된 좌석입니다."); } seatScheduleInfo.updateSeatScheduleInfoStatus(SeatStatus.SELECTED); seatScheduleInfoRepository.save(seatScheduleInfo); - //userId와 schedule Id가 key이고 seatSchduleInfoId로 구성된 Set이 value인 형태로 저장 + //userId와 schedule Id가 key이고 seatSchduleInfoId로 구성된 value인 형태로 저장 String redisSelectedKey = "user:scheduleSelected" + userId + ":" + scheduleId; if (Boolean.TRUE.equals(redisTemplate.hasKey(redisSelectedKey))) { @@ -50,8 +59,9 @@ public void selectSeat(Long userId, Long scheduleId, Long seatScheduleInfoId) { redisTemplate.expire(redisSelectedKey, Duration.ofMinutes(5)); //seatScheduleInfoId의 seatStatus 상태 변경 - String redisHashKey = "schedule:seatStatus:" + scheduleId; - redisTemplate.opsForHash().put(redisHashKey + scheduleId, seatScheduleInfoId.toString(), SeatStatus.SELECTED.name()); + String redisHashKey = "seatStatus:" + scheduleId; + redisTemplate.opsForHash().put(redisHashKey, seatScheduleInfoId.toString(), SeatStatus.SELECTED.name()); + log.info("redisHashKey : " + redisHashKey + " = " + " redisSelectedKey : " + redisSelectedKey); } public Map getSeatStatusMap(Long scheduleId) { From 3fddf2d46034ca2c538f9fa9e41f9e28181931be Mon Sep 17 00:00:00 2001 From: crocusia <132359536+crocusia@users.noreply.github.com> Date: Fri, 23 May 2025 12:40:50 +0900 Subject: [PATCH 10/10] =?UTF-8?q?feat=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=B4=20Redis=EC=97=90=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=A0=81=EC=9E=AC=EC=9A=A9=20api=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SeatScheduleInfoController.java | 9 +++++++++ .../service/SeatScheduleInfoService.java | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/controller/SeatScheduleInfoController.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/controller/SeatScheduleInfoController.java index 78e96b9..e19e85f 100644 --- a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/controller/SeatScheduleInfoController.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/controller/SeatScheduleInfoController.java @@ -21,6 +21,15 @@ public class SeatScheduleInfoController { private final SeatScheduleInfoService seatScheduleInfoService; + @PostMapping("/seat-schedule-infos") + public ResponseEntity> forceSeatScheduleInfoInRedis( + @PathVariable Long scheduleId + ) + { + seatScheduleInfoService.forceSeatScheduleInfoInRedis(scheduleId); + return ResponseEntity.ok(ResponseDto.success("Redis 적재 완료 scheduleId : " + scheduleId, null)); + } + @PostMapping("/seat-schedule-infos/{seatScheduleInfoId}") public ResponseEntity> selectSeat( @PathVariable Long scheduleId, diff --git a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java index 3976a3e..56cc1d9 100644 --- a/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java +++ b/src/main/java/org/example/siljeun/domain/seatscheduleinfo/service/SeatScheduleInfoService.java @@ -37,6 +37,7 @@ public void selectSeat(Long userId, Long scheduleId, Long seatScheduleInfoId) { Schedule schedule = seatScheduleInfo.getSchedule(); if(schedule.getTicketingStartTime().isAfter(LocalDateTime.now())){ + log.info("예매 미오픈."); throw new ResponseStatusException(HttpStatus.FORBIDDEN, "예매 불가능한 시간입니다. 예매 오픈 시간 : " + schedule.getTicketingStartTime()); } @@ -94,4 +95,20 @@ public Map getSeatStatusMap(Long scheduleId) { return seatStatusMap; } + + public void forceSeatScheduleInfoInRedis(Long scheduleId){ + Schedule schedule = scheduleRepository.findById(scheduleId) + .orElseThrow(() -> new EntityNotFoundException("해당 회차가 존재하지 않습니다.")); + + List seatInfos = seatScheduleInfoRepository.findAllBySchedule(schedule); + + String redisHashKey = "seatStatus:" + schedule.getId(); + Map seatStatusMap = new HashMap<>(); + + for (SeatScheduleInfo seat : seatInfos) { + seatStatusMap.put(seat.getId().toString(), seat.getStatus().name()); + } + + redisTemplate.opsForHash().putAll(redisHashKey, seatStatusMap); + } }