스프링 스케줄러를 이용하여 콘텐츠 예약 오픈 설정
This commit is contained in:
		| @@ -26,6 +26,7 @@ repositories { | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     implementation("org.springframework.boot:spring-boot-starter-aop") | ||||
|     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-security") | ||||
| @@ -33,6 +34,7 @@ dependencies { | ||||
|     implementation("com.fasterxml.jackson.module:jackson-module-kotlin") | ||||
|     implementation("org.springframework.retry:spring-retry") | ||||
|     implementation("org.jetbrains.kotlin:kotlin-reflect") | ||||
|     implementation("org.redisson:redisson-spring-boot-starter:3.17.7") | ||||
|  | ||||
|     // jwt | ||||
|     implementation("io.jsonwebtoken:jjwt-api:0.11.5") | ||||
|   | ||||
| @@ -4,8 +4,10 @@ import org.springframework.boot.autoconfigure.SpringBootApplication | ||||
| import org.springframework.boot.runApplication | ||||
| import org.springframework.retry.annotation.EnableRetry | ||||
| import org.springframework.scheduling.annotation.EnableAsync | ||||
| import org.springframework.scheduling.annotation.EnableScheduling | ||||
|  | ||||
| @SpringBootApplication | ||||
| @EnableScheduling | ||||
| @EnableAsync | ||||
| @EnableRetry | ||||
| class SodaLiveApplication | ||||
|   | ||||
| @@ -0,0 +1,26 @@ | ||||
| package kr.co.vividnext.sodalive.common.annotation | ||||
|  | ||||
| import org.aspectj.lang.annotation.Aspect | ||||
| import org.aspectj.lang.annotation.Before | ||||
| import org.springframework.stereotype.Component | ||||
|  | ||||
| @Target(AnnotationTarget.FUNCTION) | ||||
| @Retention(AnnotationRetention.RUNTIME) | ||||
| annotation class SchedulerOnly | ||||
|  | ||||
| @Aspect | ||||
| @Component | ||||
| class SchedulerOnlyAspect { | ||||
|  | ||||
|     @Before("@annotation(SchedulerOnly)") | ||||
|     fun checkSchedulerAccess() { | ||||
|         if (!isSchedulerThread()) { | ||||
|             throw IllegalStateException("잘못된 접근입니다.") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun isSchedulerThread(): Boolean { | ||||
|         // 스케줄러 스레드 여부를 판단하는 간단한 로직 | ||||
|         return Thread.currentThread().name.contains("scheduler") | ||||
|     } | ||||
| } | ||||
| @@ -1,5 +1,8 @@ | ||||
| package kr.co.vividnext.sodalive.configs | ||||
|  | ||||
| import org.redisson.Redisson | ||||
| import org.redisson.api.RedissonClient | ||||
| import org.redisson.config.Config | ||||
| import org.springframework.beans.factory.annotation.Value | ||||
| import org.springframework.cache.annotation.EnableCaching | ||||
| import org.springframework.context.annotation.Bean | ||||
| @@ -26,6 +29,17 @@ class RedisConfig( | ||||
|     @Value("\${spring.redis.port}") | ||||
|     private val port: Int | ||||
| ) { | ||||
|     @Bean | ||||
|     fun redissonClient(): RedissonClient { | ||||
|         val config = Config() | ||||
|         config.useSingleServer() | ||||
|             .setAddress("redis://$host:$port") | ||||
|             .setConnectionMinimumIdleSize(1) // 최소 유휴 연결: 1 | ||||
|             .setConnectionPoolSize(5) // 최대 연결 풀 크기: 5 | ||||
|  | ||||
|         return Redisson.create(config) | ||||
|     } | ||||
|  | ||||
|     @Bean | ||||
|     fun redisConnectionFactory(): RedisConnectionFactory { | ||||
|         val clientConfiguration = LettuceClientConfiguration.builder() | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper | ||||
| import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront | ||||
| import kr.co.vividnext.sodalive.aws.s3.S3Uploader | ||||
| import kr.co.vividnext.sodalive.common.SodaException | ||||
| import kr.co.vividnext.sodalive.common.annotation.SchedulerOnly | ||||
| import kr.co.vividnext.sodalive.content.comment.AudioContentCommentRepository | ||||
| import kr.co.vividnext.sodalive.content.hashtag.AudioContentHashTag | ||||
| import kr.co.vividnext.sodalive.content.hashtag.HashTag | ||||
| @@ -402,6 +403,7 @@ class AudioContentService( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @SchedulerOnly | ||||
|     @Transactional | ||||
|     fun releaseContent() { | ||||
|         val contentIdList = repository.getNotReleaseContentId() | ||||
|   | ||||
| @@ -0,0 +1,32 @@ | ||||
| package kr.co.vividnext.sodalive.scheduler | ||||
|  | ||||
| import kr.co.vividnext.sodalive.content.AudioContentService | ||||
| import org.redisson.api.RedissonClient | ||||
| import org.springframework.scheduling.annotation.Scheduled | ||||
| import org.springframework.stereotype.Service | ||||
| import java.util.concurrent.TimeUnit | ||||
|  | ||||
| @Service | ||||
| class AudioContentReleaseSchedulerService( | ||||
|     private val redissonClient: RedissonClient, | ||||
|     private val audioContentService: AudioContentService | ||||
| ) { | ||||
|     @Scheduled(fixedRate = 1000 * 60 * 5) | ||||
|     fun release() { | ||||
|         val lock = redissonClient.getLock("lock:audioContentRelease") | ||||
|  | ||||
|         if (lock.tryLock(10, TimeUnit.SECONDS)) { | ||||
|             try { | ||||
|                 println("락을 획득하여 배포를 시작합니다.") | ||||
|                 audioContentService.releaseContent() | ||||
|             } finally { | ||||
|                 if (lock.isHeldByCurrentThread) { | ||||
|                     lock.unlock() | ||||
|                     println("락 해제") | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             println("락을 획득하지 못해서 배포를 건너뜁니다") | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user