Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -1,6 +1,8 @@
package com.ticketPing.order.application.client;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import performance.OrderSeatResponse;
import performance.SeatResponse;
import response.CommonResponse;
Expand All @@ -10,5 +12,7 @@
public interface PerformanceClient {
ResponseEntity<CommonResponse<OrderSeatResponse>> getOrderInfo(UUID userId, UUID scheduleId, UUID seatId);

ResponseEntity<CommonResponse<Object>> extendPreReserveTTL(UUID scheduleId, UUID seatId);

ResponseEntity<CommonResponse<SeatResponse>> updateSeatState(UUID seatId, Boolean seatState);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.ticketPing.order.application.client.PerformanceClient;
import com.ticketPing.order.application.dtos.OrderResponse;
import com.ticketPing.order.common.exception.OrderExceptionCase;
import com.ticketPing.order.domain.model.entity.Order;
import com.ticketPing.order.domain.model.entity.OrderSeat;
import com.ticketPing.order.domain.model.enums.OrderStatus;
Expand Down Expand Up @@ -42,18 +43,17 @@ public OrderResponse createOrder(UUID scheduleId, UUID seatId, UUID userId) {
return OrderResponse.from(order);
}

@Transactional(readOnly = true)
public OrderResponse validateOrder(UUID orderId, UUID userId) {
Order order = findOrderById(orderId);
// TODO: 본인 좌석 인지 확인, 좌석 선점 TTL 갱신
return OrderResponse.from(order);
}

public List<OrderResponse> getUserOrders(UUID userId) {
List<Order> orders = orderRepository.findByUserId(userId);
return orders.stream().map(OrderResponse::from).toList();
}

public OrderResponse validateOrderAndExtendTTL(UUID orderId, UUID userId) {
Order order = validateOrder(orderId, userId);
performanceClient.extendPreReserveTTL(order.getScheduleId(), order.getOrderSeat().getSeatId());
return OrderResponse.from(order);
}

@Transactional
public void updateOrderStatus(UUID orderId, UUID paymentId) {
Order order = findOrderById(orderId);
Expand All @@ -63,7 +63,7 @@ public void updateOrderStatus(UUID orderId, UUID paymentId) {
UUID scheduleId = order.getScheduleId();
UUID seatId = order.getOrderSeat().getSeatId();

performanceClient.updateSeatState(order.getOrderSeat().getSeatId(), true); // 1. 좌석 db 업데이트 (kafka로 변경?)
performanceClient.updateSeatState(order.getOrderSeat().getSeatId(), true);

String ttlKey = TTL_PREFIX + scheduleId + ":" + seatId + ":" + orderId;
redisRepository.deleteKey(ttlKey);
Expand All @@ -74,11 +74,6 @@ public void updateOrderStatus(UUID orderId, UUID paymentId) {
publishOrderCompletedEvent(order.getUserId(), performanceId);
}

private void publishOrderCompletedEvent(UUID userId, UUID performanceId) {
val event = OrderCompletedEvent.create(userId, performanceId);
eventApplicationService.publishOrderCompletedEvent(event);
}

@Transactional
private Order saveOrderWithOrderSeat(UUID userId, OrderSeatResponse orderData) {
Order order = Order.from(userId, orderData);
Expand All @@ -98,12 +93,23 @@ private Order findOrderById(UUID orderId){
private void validateDuplicateOrder(UUID seatId) {
List<Order> duplicateOrders = orderRepository.findByOrderSeatSeatId(seatId)
.stream()
.filter(o -> o.getOrderStatus().equals(OrderStatus.PENDING) ||
o.getOrderStatus().equals(OrderStatus.COMPLETED))
.filter(o -> o.getOrderStatus().equals(OrderStatus.PENDING) || o.getOrderStatus().equals(OrderStatus.COMPLETED))
.toList();

if(!duplicateOrders.isEmpty())
throw new ApplicationException(SEAT_ALREADY_TAKEN);
}

private Order validateOrder(UUID orderId, UUID userId) {
Order order = findOrderById(orderId);
if(!order.getOrderStatus().equals(OrderStatus.PENDING) || !order.getUserId().equals(userId))
throw new ApplicationException(OrderExceptionCase.INVALID_ORDER);
return order;
}

private void publishOrderCompletedEvent(UUID userId, UUID performanceId) {
val event = OrderCompletedEvent.create(userId, performanceId);
eventApplicationService.publishOrderCompletedEvent(event);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum OrderExceptionCase implements ErrorCase {
INVALID_TTL_NAME(HttpStatus.BAD_REQUEST, "유효하지 않은 TTL 명입니다."),
NOT_FOUND_ORDER_ID_IN_TTL(HttpStatus.NOT_FOUND,"TTL 정보에서 order_id를 찾을 수 없습니다."),
SEAT_ALREADY_TAKEN(HttpStatus.BAD_REQUEST, "중복된 주문입니다."),
INVALID_ORDER(HttpStatus.BAD_REQUEST, "유효하지 않은 주문입니다."),
SEAT_CACHE_NOT_FOUND(HttpStatus.NOT_FOUND, "레디스에 공연관련 정보가 캐싱되어 있지 않습니다."),
ORDER_STATUS_UNKNOWN(HttpStatus.CONFLICT,"저장된 enum 상태값을 사용해야 합니다."),
INVALID_SEAT_STATUS(HttpStatus.BAD_REQUEST, "유효하지 않은 좌석 상태를 사용했습니다.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@
@FeignClient(name = "performance")
public interface PerformanceFeignClient extends PerformanceClient {

@GetMapping("/api/v1/seats/{seatId}/order-info")
@GetMapping("/api/v1/client/seats/{seatId}/order-info")
ResponseEntity<CommonResponse<OrderSeatResponse>> getOrderInfo(@RequestHeader("X_USER_ID") UUID userId,
@RequestParam("scheduleId") UUID scheduleId,
@PathVariable("seatId") UUID seatId);

@PostMapping("/api/v1/client/seats/{seatId}/extend-ttl")
ResponseEntity<CommonResponse<Object>> extendPreReserveTTL(@RequestParam("scheduleId") UUID scheduleId,
@PathVariable("seatId") UUID seatId);

@PutMapping("/api/v1/seats/{seatId}")
ResponseEntity<CommonResponse<SeatResponse>> updateSeatState(@PathVariable("seatId") UUID seatId,
@RequestParam("seatState") Boolean seatState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public CommonResponse<List<OrderResponse>> getUserReservation(@RequestHeader("X_
@PostMapping("/{orderId}/validate")
public CommonResponse<OrderResponse> validateOrder(@RequestHeader("X_USER_ID") UUID userId,
@PathVariable("orderId") UUID orderId) {
OrderResponse orderResponse = orderService.validateOrder(orderId, userId);
OrderResponse orderResponse = orderService.validateOrderAndExtendTTL(orderId, userId);
return CommonResponse.success(orderResponse);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.ticketPing.performance.infrastructure.service.LuaScriptService;
import exception.ApplicationException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.Duration;
Expand All @@ -29,6 +30,9 @@ public class SeatService {
private final CacheService cacheService;
private final LuaScriptService luaScriptService;

@Value("${seat.pre-reserve-ttl}")
private int PRE_RESERVE_TTL;

public SeatResponse getSeat(UUID id) {
Seat seat = seatRepository.findByIdWithSeatCost(id)
.orElseThrow(() -> new ApplicationException(SeatExceptionCase.SEAT_NOT_FOUND));
Expand All @@ -44,7 +48,7 @@ public void cancelPreReserveSeat(UUID scheduleId, UUID seatId, UUID userId) {
cacheService.canclePreReserveSeat(scheduleId, seatId);
}

public OrderSeatResponse getOrderInfo(UUID scheduleId, UUID seatId, UUID userId) {
public OrderSeatResponse getOrderSeatInfo(UUID scheduleId, UUID seatId, UUID userId) {
validatePreserve(scheduleId, seatId, userId);

Seat seat = seatRepository.findByIdWithAll(seatId)
Expand All @@ -53,6 +57,10 @@ public OrderSeatResponse getOrderInfo(UUID scheduleId, UUID seatId, UUID userId)
return OrderSeatResponse.of(seat);
}

public void extendPreReserveTTL(UUID scheduleId, UUID seatId) {
cacheService.extendPreReserveTTL(scheduleId, seatId, Duration.ofSeconds(PRE_RESERVE_TTL));
}

public long cacheSeatsForSchedule(Schedule schedule) {
List<Seat> seats = seatRepository.findByScheduleWithSeatCost(schedule);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.ticketPing.performance.common.constants;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class SeatConstants {

public static int PRE_RESERVE_TTL;

private SeatConstants(@Value("${seat.pre-reserve-ttl}") int preReserveTtl) {
PRE_RESERVE_TTL = preReserveTtl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public enum SeatExceptionCase implements ErrorCase {
PRE_RESERVE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "좌석 선점 과정에서 오류가 발생했습니다."),
SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류가 발생했습니다."),
USER_NOT_MATCH(HttpStatus.BAD_REQUEST, "본인이 선점한 좌석이 아닙니다."),
SEAT_NOT_PRE_RESERVED(HttpStatus.BAD_REQUEST, "좌석이 선점 상태가 아닙니다.");
SEAT_NOT_PRE_RESERVED(HttpStatus.BAD_REQUEST, "좌석이 선점 상태가 아닙니다."),
TTL_NOT_EXIST(HttpStatus.BAD_REQUEST, "좌석 선점 상태가 아닙니다.");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.ticketPing.performance.domain.model.entity.SeatCache;
import exception.ApplicationException;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RBucket;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
Expand Down Expand Up @@ -44,6 +45,15 @@ public SeatCache getSeatFromCache(UUID scheduleId, UUID seatId) {
.orElseThrow(() -> new ApplicationException(SeatExceptionCase.SEAT_CACHE_NOT_FOUND));
}

public void extendPreReserveTTL(UUID scheduleId, UUID seatId, Duration ttl) {
String ttlKey = "ttl:{" + scheduleId + "}:" + seatId;
RBucket<Object> bucket = redissonClient.getBucket(ttlKey);
if (!bucket.isExists()) {
throw new ApplicationException(SeatExceptionCase.TTL_NOT_EXIST);
}
bucket.expire(ttl);
}

public void canclePreReserveSeat(UUID scheduleId, UUID seatId) {
String key = "seat:{" + scheduleId + "}";
RMap<String, SeatCache> seatCacheMap = redissonClient.getMap(key, JsonJacksonCodec.INSTANCE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,19 @@
import org.redisson.api.RScript;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.UUID;

import static com.ticketPing.performance.common.constants.SeatConstants.PRE_RESERVE_TTL;

@Service
@RequiredArgsConstructor
public class LuaScriptService {
private final RedissonClient redissonClient;
private final String preReserveScript;

@Value("${seat.pre-reserve-ttl}")
private int PRE_RESERVE_TTL;

public void preReserveSeat(UUID scheduleId, UUID seatId, UUID userId) {
String hashKey = "seat:{" + scheduleId + "}";
String ttlKey = "ttl:{" + scheduleId + "}:" + seatId;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.ticketPing.performance.presentation.controller;

import com.ticketPing.performance.application.dtos.OrderSeatResponse;
import com.ticketPing.performance.application.service.SeatService;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import response.CommonResponse;

import java.util.UUID;

@RestController
@RequestMapping("/api/v1/client/seats")
@RequiredArgsConstructor
public class SeatClientController {

private final SeatService seatService;

@Operation(summary = "좌석 주문 정보 조회 (order 서비스에서 호출용)")
@GetMapping("/{seatId}/order-info")
public ResponseEntity<CommonResponse<OrderSeatResponse>> getOrderSeatInfo(@RequestHeader("X_USER_ID") UUID userId,
@RequestParam("scheduleId") UUID scheduleId,
@PathVariable("seatId") UUID seatId) {
OrderSeatResponse orderSeatResponse = seatService.getOrderSeatInfo(scheduleId, seatId, userId);
return ResponseEntity
.status(200)
.body(CommonResponse.success(orderSeatResponse));
}

@Operation(summary = "좌석 선점 ttl 연강 (order 서비스 호출용)")
@PostMapping("/{seatId}/extend-ttl")
ResponseEntity<CommonResponse<Object>> extendPreReserveTTL (@RequestParam("scheduleId") UUID scheduleId,
@PathVariable("seatId") UUID seatId) {
seatService.extendPreReserveTTL(scheduleId, seatId);
return ResponseEntity
.status(200)
.body(CommonResponse.success());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,4 @@ public ResponseEntity<CommonResponse<Object>> cancelPreReserveSeat(@RequestHeade
.status(200)
.body(CommonResponse.success());
}

@Operation(summary = "좌석 주문 정보 조회 (order 서비스에서 호출용)")
@GetMapping("/{seatId}/order-info")
public ResponseEntity<CommonResponse<OrderSeatResponse>> getOrderInfo(@RequestHeader("X_USER_ID") UUID userId,
@RequestParam("scheduleId") UUID scheduleId,
@PathVariable("seatId") UUID seatId) {
OrderSeatResponse orderSeatResponse = seatService.getOrderInfo(scheduleId, seatId, userId);
return ResponseEntity
.status(200)
.body(CommonResponse.success(orderSeatResponse));
}
}
Loading