diff --git a/build.gradle.kts b/build.gradle.kts index ee7d3df..eb1d19e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,6 +53,9 @@ dependencies { // Development tools developmentOnly("org.springframework.boot:spring-boot-devtools") + // REDIS + implementation("org.springframework.boot:spring-boot-starter-data-redis") + // Test dependencies testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") diff --git a/src/main/kotlin/io/hhplus/concertreservationservice/application/queue/QueueFacade.kt b/src/main/kotlin/io/hhplus/concertreservationservice/application/queue/QueueFacade.kt index 2c3fc5e..7b1c140 100644 --- a/src/main/kotlin/io/hhplus/concertreservationservice/application/queue/QueueFacade.kt +++ b/src/main/kotlin/io/hhplus/concertreservationservice/application/queue/QueueFacade.kt @@ -1,6 +1,7 @@ package io.hhplus.concertreservationservice.application.queue import io.hhplus.concertreservationservice.domain.queue.QueueService +import io.hhplus.concertreservationservice.domain.queue.QueueTokenService import io.hhplus.concertreservationservice.domain.user.UserService import io.hhplus.concertreservationservice.exception.ErrorType import io.hhplus.concertreservationservice.exception.QueueException @@ -11,27 +12,41 @@ import org.springframework.stereotype.Service @Service class QueueFacade( private val userService: UserService, - private val queueService: QueueService + private val queueService: QueueService, + private val queueTokenService: QueueTokenService ) { fun registerInQueue(userToken: String, request: QueueRegistrationRequest): QueueTokenResponse { val userId = userService.getUserIdFromToken(userToken) val concertScheduleId = request.concertScheduleId + + // 대기열 등록 val queueEntry = queueService.registerUserInQueue(userId, concertScheduleId) val estimatedWaitTime = queueService.calculateEstimatedWaitTime(queueEntry.queuePosition) + + // 토큰을 Redis에 저장 + queueTokenService.saveQueueToken(queueEntry.queueToken, userId) + + return QueueTokenResponse( queueToken = queueEntry.queueToken, queuePosition = queueEntry.queuePosition, - estimatedWaitTime = estimatedWaitTime) + estimatedWaitTime = estimatedWaitTime + ) } fun getQueueStatus(queueToken: String): QueueTokenResponse { - val queueEntry = queueService.getQueueStatus(queueToken) - ?: throw QueueException(ErrorType.INVALID_QUEUE_TOKEN, "queueToken: $queueToken") + val userId = queueTokenService.getUserIdByQueueToken(queueToken) + ?: throw QueueException(ErrorType.INVALID_QUEUE_TOKEN, "queueToken: $queueToken") + + val queueEntry = queueService.getQueueStatus(queueToken) + ?: throw QueueException(ErrorType.INVALID_QUEUE_TOKEN, "queueToken: $queueToken") + val estimatedWaitTime = queueService.calculateEstimatedWaitTime(queueEntry.queuePosition) return QueueTokenResponse( queueToken = queueEntry.queueToken, queuePosition = queueEntry.queuePosition, - estimatedWaitTime = estimatedWaitTime) + estimatedWaitTime = estimatedWaitTime + ) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/hhplus/concertreservationservice/domain/queue/QueueTokenService.kt b/src/main/kotlin/io/hhplus/concertreservationservice/domain/queue/QueueTokenService.kt new file mode 100644 index 0000000..dff87a8 --- /dev/null +++ b/src/main/kotlin/io/hhplus/concertreservationservice/domain/queue/QueueTokenService.kt @@ -0,0 +1,7 @@ +package io.hhplus.concertreservationservice.domain.queue + +interface QueueTokenService { + fun saveQueueToken(token: String, userId: Long) + fun getUserIdByQueueToken(token: String): Long? + fun deleteQueueToken(token: String) +} diff --git a/src/main/kotlin/io/hhplus/concertreservationservice/domain/queue/QueueTokenServiceImpl.kt b/src/main/kotlin/io/hhplus/concertreservationservice/domain/queue/QueueTokenServiceImpl.kt new file mode 100644 index 0000000..4f26f37 --- /dev/null +++ b/src/main/kotlin/io/hhplus/concertreservationservice/domain/queue/QueueTokenServiceImpl.kt @@ -0,0 +1,30 @@ +package io.hhplus.concertreservationservice.domain.queue + +import io.hhplus.concertreservationservice.infrastructure.redis.RedisClient +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service + +@Service +class QueueTokenServiceImpl( + private val redisClient: RedisClient, + @Value("\${queue.token.expirationMinutes}") private val tokenExpirationMinutes: Long +) : QueueTokenService { + + private val QUEUE_TOKEN_PREFIX = "queueToken:" + + override fun saveQueueToken(token: String, userId: Long) { + val key = "$QUEUE_TOKEN_PREFIX$token" + redisClient.set(key, userId.toString(), tokenExpirationMinutes) + } + + override fun getUserIdByQueueToken(token: String): Long? { + val key = "$QUEUE_TOKEN_PREFIX$token" + val userIdStr = redisClient.get(key) + return userIdStr?.toLong() + } + + override fun deleteQueueToken(token: String) { + val key = "$QUEUE_TOKEN_PREFIX$token" + redisClient.delete(key) + } +} diff --git a/src/main/kotlin/io/hhplus/concertreservationservice/infrastructure/redis/RedisClient.kt b/src/main/kotlin/io/hhplus/concertreservationservice/infrastructure/redis/RedisClient.kt new file mode 100644 index 0000000..75ad7ff --- /dev/null +++ b/src/main/kotlin/io/hhplus/concertreservationservice/infrastructure/redis/RedisClient.kt @@ -0,0 +1,7 @@ +package io.hhplus.concertreservationservice.infrastructure.redis + +interface RedisClient { + fun set(key: String, value: String, expirationMinutes: Long) + fun get(key: String): String? + fun delete(key: String) +} \ No newline at end of file diff --git a/src/main/kotlin/io/hhplus/concertreservationservice/infrastructure/redis/RedisClientImpl.kt b/src/main/kotlin/io/hhplus/concertreservationservice/infrastructure/redis/RedisClientImpl.kt new file mode 100644 index 0000000..4b902d4 --- /dev/null +++ b/src/main/kotlin/io/hhplus/concertreservationservice/infrastructure/redis/RedisClientImpl.kt @@ -0,0 +1,23 @@ +package io.hhplus.concertreservationservice.infrastructure.redis + +import org.springframework.data.redis.core.StringRedisTemplate +import org.springframework.stereotype.Component +import java.util.concurrent.TimeUnit + +@Component +class RedisClientImpl( + private val redisTemplate: StringRedisTemplate +) : RedisClient { + + override fun set(key: String, value: String, expirationMinutes: Long) { + redisTemplate.opsForValue().set(key, value, expirationMinutes, TimeUnit.MINUTES) + } + + override fun get(key: String): String? { + return redisTemplate.opsForValue().get(key) + } + + override fun delete(key: String) { + redisTemplate.delete(key) + } +} diff --git a/src/main/resources/application-mysql.yml b/src/main/resources/application-mysql.yml index 10536ee..063fd33 100644 --- a/src/main/resources/application-mysql.yml +++ b/src/main/resources/application-mysql.yml @@ -1,4 +1,7 @@ spring: + redis: + host: localhost + port: 6379 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://db.bluebrewlab.com:3306/dbkimjusubbl