Skip to content

Commit 6ebe90f

Browse files
committed
♻️ refactor: 예약 오픈 스케줄링 방식 개선
- 기존의 방식은 매 시 정각에만 예약이 오픈될 수 있었음. - 부팅 시 및 매 정각마다 스케줄에 등록하고, 비동기로 스케줄을 수행해 유연성을 확보함. Issue Resolved: #
1 parent 662a646 commit 6ebe90f

File tree

6 files changed

+79
-11
lines changed

6 files changed

+79
-11
lines changed

back/package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

back/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"class-validator": "^0.14.1",
3939
"connect-redis": "^7.1.1",
4040
"cookie-parser": "^1.4.7",
41+
"cron": "^3.1.7",
4142
"dotenv": "^16.4.5",
4243
"express-session": "^1.18.1",
4344
"ioredis": "^5.4.1",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const ONE_MINUTE_BEFORE_THE_HOUR = '0 59 * * * *';

back/src/domains/booking/controller/booking.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,6 @@ export class BookingController {
193193
@ApiOkResponse({ description: '확인 및 오픈 완료' })
194194
@ApiUnauthorizedResponse({ description: '인증 실패' })
195195
async reloadOpenTarget() {
196-
await this.openBookingService.checkAndOpenReservations();
196+
await this.openBookingService.scheduleUpcomingReservations();
197197
}
198198
}

back/src/domains/booking/service/open-booking.service.ts

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { RedisService } from '@liaoliaots/nestjs-redis';
22
import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
3-
import { Cron, CronExpression } from '@nestjs/schedule';
3+
import { Cron, SchedulerRegistry } from '@nestjs/schedule';
4+
import { CronJob } from 'cron';
45
import Redis from 'ioredis';
56

67
import { Event } from '../../event/entity/event.entity';
78
import { EventRepository } from '../../event/repository/event.reposiotry';
89
import { SectionRepository } from '../../place/repository/section.repository';
10+
import { ONE_MINUTE_BEFORE_THE_HOUR } from '../const/cronExpressions.const';
911
import { IN_BOOKING_DEFAULT_MAX_SIZE } from '../const/inBookingDefaultMaxSize.const';
1012

1113
import { BookingSeatsService } from './booking-seats.service';
@@ -23,24 +25,47 @@ export class OpenBookingService implements OnApplicationBootstrap {
2325
private inBookingService: InBookingService,
2426
private seatsUpdateService: BookingSeatsService,
2527
private enterBookingService: EnterBookingService,
28+
private schedulerRegistry: SchedulerRegistry,
2629
) {
2730
this.redis = this.redisService.getOrThrow();
2831
}
2932

3033
async onApplicationBootstrap() {
3134
await this.inBookingService.setInBookingSessionsDefaultMaxSize(IN_BOOKING_DEFAULT_MAX_SIZE);
32-
await this.checkAndOpenReservations();
35+
await this.scheduleUpcomingReservations();
3336
}
3437

35-
@Cron(CronExpression.EVERY_HOUR)
36-
async checkAndOpenReservations() {
37-
const events = await this.eventRepository.selectEvents();
38+
@Cron(ONE_MINUTE_BEFORE_THE_HOUR)
39+
async scheduleUpcomingReservations() {
40+
const comingEvents = await this.eventRepository.selectUpcomingEvents();
3841
const openedEventIds = new Set(await this.getOpenedEventIds());
39-
const eventsToOpen = events.filter((event) => {
40-
const now = new Date();
41-
return event.reservationOpenDate <= now && !openedEventIds.has(event.id);
42+
const now = new Date();
43+
const eventToOpen = comingEvents.filter((event) => event.reservationOpenDate <= now);
44+
const eventsToSchedule = comingEvents.filter(
45+
(event) => !openedEventIds.has(event.id) && event.reservationOpenDate > now,
46+
);
47+
48+
for (const event of eventToOpen) {
49+
await this.openReservation(event);
50+
}
51+
for (const event of eventsToSchedule) {
52+
this.scheduleReservationOpen(event);
53+
}
54+
}
55+
56+
private scheduleReservationOpen(event: Event) {
57+
const jobName = `reservation-open-${event.id}`;
58+
59+
if (this.schedulerRegistry.doesExist('cron', jobName)) {
60+
this.schedulerRegistry.deleteCronJob(jobName);
61+
}
62+
63+
const job = new CronJob(event.reservationOpenDate, async () => {
64+
await this.openReservationById(event.id);
4265
});
43-
await Promise.all(eventsToOpen.map((event) => this.openReservation(event)));
66+
67+
this.schedulerRegistry.addCronJob(jobName, job);
68+
job.start();
4469
}
4570

4671
async isEventOpened(eventId: number) {
@@ -53,7 +78,14 @@ export class OpenBookingService implements OnApplicationBootstrap {
5378
return eventIds;
5479
}
5580

81+
private async openReservationById(eventId: number) {
82+
const event = await this.eventRepository.selectEvent(eventId);
83+
await this.openReservation(event);
84+
}
85+
5686
private async openReservation(event: Event) {
87+
this.validateOpeningEvent(event);
88+
5789
const eventId = event.id;
5890
const place = await event.place;
5991
const sections = await Promise.all(
@@ -71,6 +103,25 @@ export class OpenBookingService implements OnApplicationBootstrap {
71103
await this.registerOpenedEvent(eventId);
72104
}
73105

106+
private validateOpeningEvent(event: Event) {
107+
if (!event) {
108+
return false;
109+
}
110+
111+
const openTime = event.reservationOpenDate;
112+
if (openTime > new Date()) {
113+
const jobName = `reservation-open-${event.id}`;
114+
const job = new CronJob(openTime, async () => {
115+
await this.openReservationById(event.id);
116+
});
117+
this.schedulerRegistry.addCronJob(jobName, job);
118+
job.start();
119+
return false;
120+
}
121+
122+
return true;
123+
}
124+
74125
private async registerOpenedEvent(eventId: number) {
75126
await this.redis.set(`open-booking:${eventId}:opened`, 'true');
76127
}

back/src/domains/event/repository/event.reposiotry.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ConflictException, Injectable } from '@nestjs/common';
22
import { InjectRepository } from '@nestjs/typeorm';
3-
import { Repository } from 'typeorm';
3+
import { MoreThan, Repository } from 'typeorm';
44

55
import { Event } from '../entity/event.entity';
66

@@ -30,6 +30,20 @@ export class EventRepository {
3030
});
3131
}
3232

33+
async selectUpcomingEvents(): Promise<Event[]> {
34+
const now = new Date();
35+
36+
return await this.EventRepository.find({
37+
where: {
38+
reservationCloseDate: MoreThan(now),
39+
},
40+
relations: ['place'],
41+
order: {
42+
reservationOpenDate: 'ASC',
43+
},
44+
});
45+
}
46+
3347
async storeEvent(data: any) {
3448
const event = this.EventRepository.create(data);
3549
return await this.EventRepository.save(event);

0 commit comments

Comments
 (0)