Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
be34533
feat : 회차별 좌석 정보 Redis 적재 스케줄링 기능 추가
crocusia May 22, 2025
565da36
feat : Redis 캐시 기반 좌석 조회 (DB와의 동기화 구현 필요)
crocusia May 22, 2025
55cb3a3
Merge branch 'dev' of https://github.com/pokerbearkr/nullnullTicket i…
crocusia May 22, 2025
4b0d255
Merge branch 'dev' of https://github.com/pokerbearkr/nullnullTicket i…
crocusia May 22, 2025
37f0d0c
Merge branch 'dev' of https://github.com/pokerbearkr/nullnullTicket i…
crocusia May 22, 2025
c2e5325
Merge branch 'dev' of https://github.com/pokerbearkr/nullnullTicket i…
crocusia May 22, 2025
56295b7
feat : 선점한 좌석에 대해 결제창으로 넘어갈 때, 예매 정보 생성 기능 추가 (결제가 완료되기 전의 상태를 나타냄)
crocusia May 22, 2025
e88f809
refactor : Redis key-value 구조를 성능을 위해 변경
crocusia May 22, 2025
9a17f03
refactor : 1인 1매 정책으로 변경
crocusia May 22, 2025
894e46e
refactor : seat, seatScheduleInfo 파일 패키지 위치 변경
crocusia May 22, 2025
53559df
refactor : Seat 관련 클래스 이름 변경 및 일부 위치 변경 (2)
crocusia May 22, 2025
f8ecfba
refactor : 누락된 패키지 이동 추가 (3)
crocusia May 22, 2025
296c2d6
refactor : redis key 오타 변경
crocusia May 23, 2025
3fddf2d
feat : 테스트를 위해 Redis에 데이터 적재용 api 추가
crocusia May 23, 2025
4b77db8
Merge branch 'dev' of https://github.com/pokerbearkr/nullnullTicket i…
crocusia May 23, 2025
ba2336e
Merge branch 'dev' of https://github.com/pokerbearkr/nullnullTicket i…
crocusia May 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,15 @@

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;
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;
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
Expand All @@ -36,7 +31,7 @@ public ResponseEntity<ResponseDto<Void>> updatePrice(

@DeleteMapping("/{reservationId}")
public ResponseEntity<ResponseDto<Void>> delete(
@AuthenticationPrincipal PrincipalDetails userDetails, @PathVariable Long reservationId) {
@AuthenticationPrincipal PrincipalDetails userDetails, @PathVariable Long reservationId) {
String username = userDetails.getUsername();
reservationService.delete(username, reservationId);
return ResponseEntity.ok(ResponseDto.success("예매 취소 완료", null));
Expand All @@ -49,4 +44,13 @@ public ResponseEntity<ResponseDto<ReservationInfoResponse>> findById(
ReservationInfoResponse dto = reservationService.findById(username, reservationId);
return ResponseEntity.ok(ResponseDto.success("예매 조회 성공", dto));
}

@PostMapping()
public ResponseEntity<ResponseDto<Void>> createReservation(
@RequestBody @Valid ReservationCreateRequest reservationCreateRequest,
@AuthenticationPrincipal PrincipalDetails userDetails
){
reservationService.createReservation(reservationCreateRequest, userDetails.getUserId());
return ResponseEntity.ok(ResponseDto.success("결제 진행하기", null));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.example.siljeun.domain.reservation.dto.request;

import jakarta.validation.constraints.NotNull;

public record ReservationCreateRequest(
@NotNull Long scheduleId
)
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package org.example.siljeun.domain.reservation.service;

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;
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.entity.SeatScheduleInfo;
import org.example.siljeun.domain.seatscheduleinfo.repository.SeatScheduleInfoRepository;
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;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;

@Slf4j
@Service
@RequiredArgsConstructor
public class ReservationService {
Expand All @@ -23,6 +30,8 @@ public class ReservationService {
private final UserRepository userRepository;
private final WaitingQueueService waitingQueueService;
private final SeatScheduleInfoRepository seatScheduleInfoRepository;
private final RedisTemplate<String, String> redisTemplate;


@Transactional
public void save(Long userId, Long seatScheduleInfoId) {
Expand Down Expand Up @@ -78,4 +87,45 @@ public ReservationInfoResponse findById(String username, Long reservationId) {

return ReservationInfoResponse.from(reservation);
}

@Transactional
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;
log.info("예매 정보 생성 시도 user : " + userId + "scheduleId : " + scheduleId + " key) " + redisSelectedKey );
String selectedId = redisTemplate.opsForValue().get(redisSelectedKey);

if (selectedId == null) {
throw new IllegalStateException("선택한 좌석이 없습니다.");
}

SeatScheduleInfo seatScheduleInfo = seatScheduleInfoRepository.findById(Long.valueOf(selectedId))
.orElseThrow(() -> new EntityNotFoundException("좌석 정보를 찾을 수 없습니다."));

//해당 좌석의 상태 검증
String redisStatusHashKey = "seatStatus:" + scheduleId;
Object redisStatusObj = redisTemplate.opsForHash().get(redisStatusHashKey, selectedId);

if (redisStatusObj == null || !redisStatusObj.toString().equals(SeatStatus.SELECTED.name())) {
throw new IllegalStateException("좌석 상태가 유효하지 않습니다. 다시 선택해주세요.");
}

//예매 정보 생성
Reservation reservation = new Reservation(user, seatScheduleInfo);
reservationRepository.save(reservation);

//좌석 상태 결제 진행 중으로 변경
seatScheduleInfo.updateSeatScheduleInfoStatus(SeatStatus.HOLD);
seatScheduleInfoRepository.save(seatScheduleInfo);
redisTemplate.opsForHash().put(redisStatusHashKey, selectedId, SeatStatus.HOLD.name());

//유저가 선점한 좌석 정보 - 결제 진행 상태일 때의 만료 시간 1시간
redisTemplate.expire(redisSelectedKey, Duration.ofMinutes(60));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -11,11 +13,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.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.venue.repository.VenueSeatRepository;
import org.example.siljeun.domain.seat.repository.SeatRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -26,7 +28,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
Expand All @@ -44,7 +46,7 @@ public ScheduleSimpleResponse createSchedule(ScheduleCreateRequest request) {
Schedule saved = scheduleRepository.save(schedule);

//회차 생성 시, 회차별 좌석 정보도 함께 생성
List<Seat> seats = venueSeatRepository.findByVenue(concert.getVenue());
List<Seat> seats = seatRepository.findByVenue(concert.getVenue());

List<SeatScheduleInfo> seatInfos = seats.stream()
.map(seat -> SeatScheduleInfo.from(
Expand Down Expand Up @@ -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<ScheduleSimpleResponse> getSchedulesByConcertId(Long concertId) {
List<Schedule> 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<ScheduleSimpleResponse> getSchedulesByConcertId(Long concertId) {
List<Schedule> schedules = scheduleRepository.findByConcertIdWithConcert(concertId);
return schedules.stream()
.map(
s -> new ScheduleSimpleResponse(s.getId(), s.getStartTime(), s.getTicketingStartTime()))
.toList();
}
}
Loading