Skip to content

Commit

Permalink
feat: 이탈된 예매에 대한 좌석 상태 변경 배치 (#157)
Browse files Browse the repository at this point in the history
* feat: 이탈된 예매에 대한 좌석 상태 변경 배치

* chore

---------

Co-authored-by: byulcode <[email protected]>
  • Loading branch information
annahxxl and byulcode committed Jan 8, 2024
1 parent 7eac749 commit 900bfd7
Show file tree
Hide file tree
Showing 17 changed files with 264 additions and 27 deletions.
3 changes: 1 addition & 2 deletions api/api-booking/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

//TODO: security 의존성 core-security 에서 추가
implementation 'org.springframework.boot:spring-boot-starter-security'

implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
Expand Down
57 changes: 57 additions & 0 deletions api/api-booking/http/test.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
### 세션 아이디 발급
POST http://localhost:8082/api/v1/bookings/issue-session-id
#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ

### 대기열 진입
POST http://localhost:8082/api/v1/bookings/enter-queue
Content-Type: application/json
#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Booking-Session-Id: {세션아이디}

{
"eventId": 1
}

### 대기열 조회
GET http://localhost:8082/api/v1/bookings/order-in-queue?eventId=1
#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Booking-Session-Id: {세션아이디}


### 예매 토큰 발급
POST http://localhost:8082/api/v1/bookings/issue-token
Content-Type: application/json
#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Booking-Session-Id: {세션아이디}

{
"eventId": 1
}

### 좌석 목록 조회
GET http://localhost:8082/api/v1/seats?eventTimeId=1
#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Booking-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJib29raW5nIiwiaWF0IjoxNzA0NjkzNDQ4LCJleHAiOjE3MDQ2OTUyNDgsInNlc3Npb25JZCI6Ins_Pz8_P30ifQ.IFqm66RBlLw6ZVbogZdTvClpH2arxAfWTZ1-DNFtT-H7EH0OSYUoICgnpMjbI2aSObsdl1h3oVSQPw28XlmJzg

### 예매
POST http://localhost:8082/api/v1/bookings
Content-Type: application/json
#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Booking-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJib29raW5nIiwiaWF0IjoxNzA0NjkzNDQ4LCJleHAiOjE3MDQ2OTUyNDgsInNlc3Npb25JZCI6Ins_Pz8_P30ifQ.IFqm66RBlLw6ZVbogZdTvClpH2arxAfWTZ1-DNFtT-H7EH0OSYUoICgnpMjbI2aSObsdl1h3oVSQPw28XlmJzg

{
"timeId": 1,
"seatIds": [3, 4],
"receiptType": "배송",
"buyerName": "이한나",
"buyerPhoneNumber": "010-1234-5678",
"deliveryAddress": {
"recipientName": "이한나",
"recipientPhoneNumber": "010-1234-5678",
"streetAddress": "서울시 강남구 테헤란로",
"detailAddress": "000동 000호",
"zipCode": "00000"
},
"method": "카드"
}

Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ public class SecurityConfig {
@Bean
public SecurityFilterChain bookingFilterChain(HttpSecurity http) throws Exception {
List<RequestMatcher> permitAllMatchers = List.of(
new AntPathRequestMatcher("/api/*/bookings/issue-session-id", HttpMethod.POST.toString()),
new AntPathRequestMatcher("/api/*/bookings/enter-queue", HttpMethod.POST.toString()),
new AntPathRequestMatcher("/api/*/bookings/order-in-queue", HttpMethod.GET.toString()),
new AntPathRequestMatcher("/api/*/bookings/issue-token", HttpMethod.GET.toString())
new AntPathRequestMatcher("/api/*/bookings/issue-token", HttpMethod.POST.toString())
);

return http
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@
@Controller
@RequestMapping("/api/v1/bookings")
@RequiredArgsConstructor
public class BookingController {
public class BookingController { //TODO: 인증된 멤버 연동

private final BookingService bookingService;

@PostMapping
public ResponseEntity<ApiResponse<BookingCreateResponse>> createBooking(
@CurrentAccount Long memberId,
//@CurrentAccount Long memberId,
@RequestBody @Valid BookingCreateRequest request,
HttpServletRequest httpRequest) {
BookingCreateResponse createdBooking = bookingService.createBooking(request, memberId);
BookingCreateResponse createdBooking = bookingService.createBooking(request, 1L);
ApiResponse<BookingCreateResponse> response = ApiResponse.ok(createdBooking);
URI location = UriComponentsBuilder
.fromHttpUrl(httpRequest.getRequestURL().toString())
Expand All @@ -45,10 +45,10 @@ public ResponseEntity<ApiResponse<BookingCreateResponse>> createBooking(

@PostMapping("/{id}/cancel")
public ResponseEntity<Void> cancelBooking(
@CurrentAccount Long memberId,
//@CurrentAccount Long memberId,
@PathVariable String id,
@RequestBody @Valid BookingCancelRequest request) {
bookingService.cancelBooking(id, request, memberId);
bookingService.cancelBooking(id, request, 1L);
return ResponseEntity.ok().build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
Expand All @@ -17,7 +18,6 @@
import com.pgms.apibooking.domain.bookingqueue.service.BookingQueueService;
import com.pgms.coredomain.response.ApiResponse;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

Expand All @@ -35,32 +35,27 @@ public ResponseEntity<ApiResponse<SessionIdIssueResponse>> issueSessionId() {
}

@PostMapping("/enter-queue")
public ResponseEntity<Void> enterQueue(@RequestBody @Valid BookingQueueEnterRequest request, HttpServletRequest httpServletRequest) {
String bookingSessionId = getBookingSessionId(httpServletRequest);
public ResponseEntity<Void> enterQueue(@RequestBody @Valid BookingQueueEnterRequest request, @RequestAttribute("bookingSessionId") String bookingSessionId) {
bookingQueueService.enterQueue(request, bookingSessionId);
return ResponseEntity.ok().build();
}

@GetMapping("/order-in-queue")
public ResponseEntity<ApiResponse<OrderInQueueGetResponse>> getOrderInQueue(@RequestParam Long eventId) {
public ResponseEntity<ApiResponse<OrderInQueueGetResponse>> getOrderInQueue(@RequestParam Long eventId, @RequestAttribute("bookingSessionId") String bookingSessionId) {
ApiResponse<OrderInQueueGetResponse> response =
ApiResponse.ok(bookingQueueService.getOrderInQueue(eventId, null));
ApiResponse.ok(bookingQueueService.getOrderInQueue(eventId, bookingSessionId));
return ResponseEntity.ok(response);
}

@PostMapping("/issue-token")
public ResponseEntity<ApiResponse<TokenIssueResponse>> issueToken(@RequestBody @Valid TokenIssueRequest request) {
ApiResponse<TokenIssueResponse> response = ApiResponse.ok(bookingQueueService.issueToken(request, null));
public ResponseEntity<ApiResponse<TokenIssueResponse>> issueToken(@RequestBody @Valid TokenIssueRequest request, @RequestAttribute("bookingSessionId") String bookingSessionId) {
ApiResponse<TokenIssueResponse> response = ApiResponse.ok(bookingQueueService.issueToken(request, bookingSessionId));
return ResponseEntity.ok(response);
}

@PostMapping("/exit-queue")
public ResponseEntity<Void> exitQueue(@RequestBody @Valid BookingQueueExitRequest request) {
bookingQueueService.exitQueue(request, null);
public ResponseEntity<Void> exitQueue(@RequestBody @Valid BookingQueueExitRequest request, @RequestAttribute("bookingSessionId") String bookingSessionId) {
bookingQueueService.exitQueue(request, bookingSessionId);
return ResponseEntity.ok().build();
}

private String getBookingSessionId(HttpServletRequest httpServletRequest) {
return (String) httpServletRequest.getAttribute("bookingSessionId");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@

import org.springframework.stereotype.Service;

import com.pgms.apibooking.common.exception.BookingErrorCode;
import com.pgms.apibooking.common.exception.BookingException;
import com.pgms.apibooking.common.jwt.BookingJwtPayload;
import com.pgms.apibooking.common.jwt.BookingJwtProvider;
import com.pgms.apibooking.domain.bookingqueue.dto.request.BookingQueueEnterRequest;
import com.pgms.apibooking.domain.bookingqueue.dto.request.BookingQueueExitRequest;
import com.pgms.apibooking.domain.bookingqueue.dto.request.TokenIssueRequest;
import com.pgms.apibooking.domain.bookingqueue.dto.response.OrderInQueueGetResponse;
import com.pgms.apibooking.domain.bookingqueue.dto.response.SessionIdIssueResponse;
import com.pgms.apibooking.domain.bookingqueue.dto.response.TokenIssueResponse;
import com.pgms.apibooking.common.exception.BookingErrorCode;
import com.pgms.apibooking.common.exception.BookingException;
import com.pgms.apibooking.common.jwt.BookingJwtPayload;
import com.pgms.apibooking.common.jwt.BookingJwtProvider;
import com.pgms.apibooking.domain.bookingqueue.repository.BookingQueueRepository;

import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -49,8 +49,6 @@ public TokenIssueResponse issueToken(TokenIssueRequest request, String sessionId
BookingJwtPayload payload = new BookingJwtPayload(sessionId);
String token = bookingJwtProvider.generateToken(payload);

bookingQueueRepository.remove(request.eventId(), sessionId);

return TokenIssueResponse.from(token);
}

Expand Down
15 changes: 15 additions & 0 deletions batch/batch-booking/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 실행가능한 jar로 생성하는 옵션, main이 없는 라이브러리에서는 false로 비활성화함
// 스프링 부트 2.0 이상이라면 bootRepackage.enabled를 사용해야 함
bootJar { enabled = true }

// 외부에서 의존하기 위한 jar로 생성하는 옵션, main이 없는 라이브러리에서는 true로 비활성화함
jar { enabled = false }

dependencies {
implementation project(':core:core-domain');
implementation project(':core:core-infra');

implementation 'org.springframework.boot:spring-boot-starter-batch'
}

tasks.register("prepareKotlinBuildScriptModel") {}
Binary file not shown.
7 changes: 7 additions & 0 deletions batch/batch-booking/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.pgms.batchbooking;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan("com.pgms")
public class BatchBookingApplication {

public static void main(String[] args) {
SpringApplication.run(BatchBookingApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.pgms.batchbooking.seat;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

import com.pgms.coredomain.domain.booking.repository.TicketRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Configuration
@RequiredArgsConstructor
@Slf4j
public class SeatReleaseConfig {

private final JobRepository jobRepository;
private final PlatformTransactionManager transactionManager;
private final TicketRepository ticketRepository;

@Bean
public Job seatReleaseJob() {
return new JobBuilder("seatReleaseJob", jobRepository)
.start(seatReleaseStep())
.build();
}

@Bean
public Step seatReleaseStep(){
return new StepBuilder("seatReleaseStep", jobRepository)
.tasklet(seatReleaseTasklet(), transactionManager)
.build();
}

@Bean
public Tasklet seatReleaseTasklet(){
return new SeatReleaseTasklet(ticketRepository);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.pgms.batchbooking.seat;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/*
* 테스트를 위한 스케줄러
*/
@Component
@EnableScheduling
@RequiredArgsConstructor
@Slf4j
public class SeatReleaseScheduler {

private final JobLauncher jobLauncher;
private final Job lockAdminJob;

@Scheduled(cron = "0 0 0 * * ?")
public void runJob() {
try {
JobParameters jobParameters = new JobParametersBuilder()
.addLong("executedTime", System.currentTimeMillis())
.toJobParameters();

jobLauncher.run(lockAdminJob, jobParameters);
} catch (JobExecutionException e) {
log.error("SeatReleaseScheduler error : ", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.pgms.batchbooking.seat;

import java.time.LocalDateTime;
import java.util.List;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.transaction.annotation.Transactional;

import com.pgms.coredomain.domain.booking.BookingStatus;
import com.pgms.coredomain.domain.booking.Ticket;
import com.pgms.coredomain.domain.booking.repository.TicketRepository;
import com.pgms.coredomain.domain.event.EventSeatStatus;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class SeatReleaseTasklet implements Tasklet{

private final TicketRepository ticketRepository;

@Transactional
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
List<Ticket> tickets = ticketRepository.findAll(LocalDateTime.now(), BookingStatus.WAITING_FOR_PAYMENT);
tickets.forEach(ticket -> ticket.getSeat().updateStatus(EventSeatStatus.AVAILABLE));
return RepeatStatus.FINISHED;
}
}
10 changes: 10 additions & 0 deletions batch/batch-booking/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
server:
port: 8086

spring:
profiles:
include: infra
active: dev
batch:
jdbc:
initialize-schema: always
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.pgms.batchbooking;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class BatchBookingApplicationTests {

@Test
void contextLoads() {
}

}
Loading

0 comments on commit 900bfd7

Please sign in to comment.