diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 92bf45a9..f013b8ab 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -2,6 +2,7 @@ tasks.jar { enabled = true } tasks.bootJar { enabled = true } dependencies { + implementation(project(":batch")) implementation(project(":common")) implementation(project(":client")) implementation(project(":domain:core")) diff --git a/api/src/main/kotlin/dnd11th/blooming/api/controller/exception/GlobalExceptionHandler.kt b/api/src/main/kotlin/dnd11th/blooming/api/controller/exception/GlobalExceptionHandler.kt index 76e53389..f454d2d5 100644 --- a/api/src/main/kotlin/dnd11th/blooming/api/controller/exception/GlobalExceptionHandler.kt +++ b/api/src/main/kotlin/dnd11th/blooming/api/controller/exception/GlobalExceptionHandler.kt @@ -105,7 +105,7 @@ class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentTypeMismatchException::class) fun handleArgumentTypeMismatchException(exception: MethodArgumentTypeMismatchException): ResponseEntity { - val errorType = ErrorType.ARUGMENT_TYPE_MISMATCH + val errorType = ErrorType.ARGUMENT_TYPE_MISMATCH logException(errorType, exception, ExceptionSource.HTTP) diff --git a/api/src/main/resources/application.yml b/api/src/main/resources/application.yml index 12c7dd3b..140a77ab 100644 --- a/api/src/main/resources/application.yml +++ b/api/src/main/resources/application.yml @@ -20,6 +20,7 @@ spring: activate: on-profile: local import: + - batch-local.yml - client.yml - redis-local.yml - core-local.yml @@ -39,6 +40,7 @@ spring: activate: on-profile: prod import: + - batch-prod.yml - client.yml - redis-prod.yml - core-prod.yml diff --git a/api/src/test/resources/application-test.yml b/api/src/test/resources/application-test.yml index 4c727a09..22c75bca 100644 --- a/api/src/test/resources/application-test.yml +++ b/api/src/test/resources/application-test.yml @@ -18,6 +18,12 @@ spring: dialect: org.hibernate.dialect.H2Dialect defer-datasource-initialization: true + batch: + jdbc: + initialize-schema: always + job: + enabled: false + data: redis: host: localhost diff --git a/batch/build.gradle.kts b/batch/build.gradle.kts index 9f1b1c98..0103abd6 100644 --- a/batch/build.gradle.kts +++ b/batch/build.gradle.kts @@ -1,5 +1,5 @@ tasks.jar { enabled = true } -tasks.bootJar { enabled = true } +tasks.bootJar { enabled = false } dependencies { implementation(project(":common")) diff --git a/batch/src/main/kotlin/dnd11th/blooming/Dnd11th8BackendBatchApplication.kt b/batch/src/main/kotlin/dnd11th/blooming/Dnd11th8BackendBatchApplication.kt deleted file mode 100644 index 8c2320d8..00000000 --- a/batch/src/main/kotlin/dnd11th/blooming/Dnd11th8BackendBatchApplication.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dnd11th.blooming - -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.context.properties.ConfigurationPropertiesScan -import org.springframework.boot.runApplication - -@SpringBootApplication -@ConfigurationPropertiesScan -class Dnd11th8BackendBatchApplication - -fun main(args: Array) { - runApplication(*args) -} diff --git a/batch/src/main/kotlin/dnd11th/blooming/batch/PlantNotificationReader.kt b/batch/src/main/kotlin/dnd11th/blooming/batch/PlantNotificationReader.kt deleted file mode 100644 index e96d6737..00000000 --- a/batch/src/main/kotlin/dnd11th/blooming/batch/PlantNotificationReader.kt +++ /dev/null @@ -1,28 +0,0 @@ -package dnd11th.blooming.batch - -import dnd11th.blooming.core.entity.myplant.UserPlantDto -import dnd11th.blooming.core.entity.user.AlarmTime -import dnd11th.blooming.core.repository.myplant.MyPlantRepository -import org.springframework.batch.core.configuration.annotation.StepScope -import org.springframework.batch.item.support.ListItemReader -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import java.time.LocalTime - -@Configuration -class PlantNotificationReader( - private val myPlantRepository: MyPlantRepository, -) { - @Bean - @StepScope - fun waterNotificationItemReader(): ListItemReader { - val now: LocalTime = LocalTime.now() - val alarmTime = AlarmTime.fromHour(now) - - val userPlantByAlarmTime: List = - myPlantRepository.findPlantsByAlarmTimeInBatch( - alarmTime, - ) - return ListItemReader(userPlantByAlarmTime) - } -} diff --git a/batch/src/main/kotlin/dnd11th/blooming/batch/BatchConfig.kt b/batch/src/main/kotlin/dnd11th/blooming/batch/config/BatchConfig.kt similarity index 94% rename from batch/src/main/kotlin/dnd11th/blooming/batch/BatchConfig.kt rename to batch/src/main/kotlin/dnd11th/blooming/batch/config/BatchConfig.kt index 11ea064c..87b2f36e 100644 --- a/batch/src/main/kotlin/dnd11th/blooming/batch/BatchConfig.kt +++ b/batch/src/main/kotlin/dnd11th/blooming/batch/config/BatchConfig.kt @@ -1,4 +1,4 @@ -package dnd11th.blooming.batch +package dnd11th.blooming.batch.config import org.springframework.beans.factory.support.BeanDefinitionRegistry import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor diff --git a/batch/src/main/kotlin/dnd11th/blooming/batch/controller/DailyWeatherCallController.kt b/batch/src/main/kotlin/dnd11th/blooming/batch/controller/DailyWeatherCallController.kt deleted file mode 100644 index 4544e021..00000000 --- a/batch/src/main/kotlin/dnd11th/blooming/batch/controller/DailyWeatherCallController.kt +++ /dev/null @@ -1,17 +0,0 @@ -package dnd11th.blooming.batch.controller - -import dnd11th.blooming.batch.weather.WeatherCareMessageScheduler -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RestController - -@RestController -class DailyWeatherCallController( - private val weatherCareMessageScheduler: WeatherCareMessageScheduler, -) { - @GetMapping("/daily-weather-call") - fun runWeatherCareMessageJob(): ResponseEntity { - weatherCareMessageScheduler.run() - return ResponseEntity.noContent().build() - } -} diff --git a/batch/src/main/kotlin/dnd11th/blooming/batch/NotificationScheduler.kt b/batch/src/main/kotlin/dnd11th/blooming/batch/notification/NotificationScheduler.kt similarity index 95% rename from batch/src/main/kotlin/dnd11th/blooming/batch/NotificationScheduler.kt rename to batch/src/main/kotlin/dnd11th/blooming/batch/notification/NotificationScheduler.kt index e581512f..3c98ccd3 100644 --- a/batch/src/main/kotlin/dnd11th/blooming/batch/NotificationScheduler.kt +++ b/batch/src/main/kotlin/dnd11th/blooming/batch/notification/NotificationScheduler.kt @@ -1,4 +1,4 @@ -package dnd11th.blooming.batch +package dnd11th.blooming.batch.notification import dnd11th.blooming.common.util.Logger.Companion.log import org.springframework.batch.core.Job diff --git a/batch/src/main/kotlin/dnd11th/blooming/batch/PlantNotificationJobConfig.kt b/batch/src/main/kotlin/dnd11th/blooming/batch/notification/PlantNotificationJobConfig.kt similarity index 53% rename from batch/src/main/kotlin/dnd11th/blooming/batch/PlantNotificationJobConfig.kt rename to batch/src/main/kotlin/dnd11th/blooming/batch/notification/PlantNotificationJobConfig.kt index bdcf8801..7acf5605 100644 --- a/batch/src/main/kotlin/dnd11th/blooming/batch/PlantNotificationJobConfig.kt +++ b/batch/src/main/kotlin/dnd11th/blooming/batch/notification/PlantNotificationJobConfig.kt @@ -1,4 +1,4 @@ -package dnd11th.blooming.batch +package dnd11th.blooming.batch.notification import dnd11th.blooming.client.fcm.PushNotification import dnd11th.blooming.core.entity.myplant.UserPlantDto @@ -20,7 +20,7 @@ import org.springframework.transaction.PlatformTransactionManager @Configuration class PlantNotificationJobConfig { companion object { - const val CHUNK_SIZE: Int = 100 + const val CHUNK_SIZE: Int = 1000 } @Bean @@ -28,10 +28,14 @@ class PlantNotificationJobConfig { fun notificationJob( jobRepository: JobRepository, waterNotificationStep: Step, + fertilizerNotificationStep: Step, + healthCheckNotificationStep: Step, ): Job { return JobBuilder("notificationJob", jobRepository) .incrementer(RunIdIncrementer()) .start(waterNotificationStep) + .next(fertilizerNotificationStep) + .next(healthCheckNotificationStep) .build() } @@ -51,4 +55,38 @@ class PlantNotificationJobConfig { .writer(waterNotificationItemWriter) .build() } + + @Bean + @JobScope + fun fertilizerNotificationStep( + jobRepository: JobRepository, + transactionManager: PlatformTransactionManager, + fertilizerNotificationItemReader: ItemReader, + waterNotificationItemProcessor: ItemProcessor, + waterNotificationItemWriter: ItemWriter, + ): Step { + return StepBuilder("fertilizerNotificationStep", jobRepository) + .chunk(CHUNK_SIZE, transactionManager) + .reader(fertilizerNotificationItemReader) + .processor(waterNotificationItemProcessor) + .writer(waterNotificationItemWriter) + .build() + } + + @Bean + @JobScope + fun healthCheckNotificationStep( + jobRepository: JobRepository, + transactionManager: PlatformTransactionManager, + healthCheckNotificationItemReader: ItemReader, + waterNotificationItemProcessor: ItemProcessor, + waterNotificationItemWriter: ItemWriter, + ): Step { + return StepBuilder("healthCheckNotificationStep", jobRepository) + .chunk(CHUNK_SIZE, transactionManager) + .reader(healthCheckNotificationItemReader) + .processor(waterNotificationItemProcessor) + .writer(waterNotificationItemWriter) + .build() + } } diff --git a/batch/src/main/kotlin/dnd11th/blooming/batch/PlantNotificationProcessor.kt b/batch/src/main/kotlin/dnd11th/blooming/batch/notification/PlantNotificationProcessor.kt similarity index 78% rename from batch/src/main/kotlin/dnd11th/blooming/batch/PlantNotificationProcessor.kt rename to batch/src/main/kotlin/dnd11th/blooming/batch/notification/PlantNotificationProcessor.kt index 262bc039..2a74bb0d 100644 --- a/batch/src/main/kotlin/dnd11th/blooming/batch/PlantNotificationProcessor.kt +++ b/batch/src/main/kotlin/dnd11th/blooming/batch/notification/PlantNotificationProcessor.kt @@ -1,4 +1,4 @@ -package dnd11th.blooming.batch +package dnd11th.blooming.batch.notification import dnd11th.blooming.client.fcm.PushNotification import dnd11th.blooming.core.entity.myplant.UserPlantDto @@ -13,7 +13,7 @@ class PlantNotificationProcessor { @StepScope fun waterNotificationItemProcessor(): ItemProcessor { return ItemProcessor { userPlantDto -> - PushNotification.create(userPlantDto.myPlantId, "token", userPlantDto.plantNickname) + PushNotification.create(userPlantDto.myPlantId, userPlantDto.deviceToken, userPlantDto.plantNickname) } } } diff --git a/batch/src/main/kotlin/dnd11th/blooming/batch/notification/PlantNotificationReader.kt b/batch/src/main/kotlin/dnd11th/blooming/batch/notification/PlantNotificationReader.kt new file mode 100644 index 00000000..7c17afc9 --- /dev/null +++ b/batch/src/main/kotlin/dnd11th/blooming/batch/notification/PlantNotificationReader.kt @@ -0,0 +1,47 @@ +package dnd11th.blooming.batch.notification + +import dnd11th.blooming.core.entity.myplant.UserPlantDto +import dnd11th.blooming.core.entity.user.AlarmTime +import dnd11th.blooming.core.repository.myplant.MyPlantRepository +import org.springframework.batch.core.configuration.annotation.StepScope +import org.springframework.batch.item.support.ListItemReader +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import java.time.LocalTime + +@Configuration +class PlantNotificationReader( + private val myPlantRepository: MyPlantRepository, +) { + @Bean + @StepScope + fun waterNotificationItemReader(): ListItemReader { + val now: LocalTime = LocalTime.now() + // val alarmTime = AlarmTime.fromHour(now) + val alarmTime = AlarmTime.TIME_10_11 + + val userPlantByAlarmTime: List = + myPlantRepository.findNeedWaterPlantsByAlarmTimeInBatch(alarmTime) + return ListItemReader(userPlantByAlarmTime) + } + + @Bean + @StepScope + fun fertilizerNotificationItemReader(): ListItemReader { + val now: LocalTime = LocalTime.now() + val alarmTime = AlarmTime.fromHour(now) + val userPlantByAlarmTime: List = + myPlantRepository.findNeedFertilizerPlantsByAlarmTimeInBatch(alarmTime) + return ListItemReader(userPlantByAlarmTime) + } + + @Bean + @StepScope + fun healthCheckNotificationItemReader(): ListItemReader { + val now: LocalTime = LocalTime.now() + val alarmTime = AlarmTime.fromHour(now) + val userPlantByAlarmTime: List = + myPlantRepository.findNeedHealthCheckPlantsByAlarmTimeInBatch(alarmTime) + return ListItemReader(userPlantByAlarmTime) + } +} diff --git a/batch/src/main/kotlin/dnd11th/blooming/batch/PlantNotificationWriter.kt b/batch/src/main/kotlin/dnd11th/blooming/batch/notification/PlantNotificationWriter.kt similarity index 53% rename from batch/src/main/kotlin/dnd11th/blooming/batch/PlantNotificationWriter.kt rename to batch/src/main/kotlin/dnd11th/blooming/batch/notification/PlantNotificationWriter.kt index 7986f6f8..f834cd91 100644 --- a/batch/src/main/kotlin/dnd11th/blooming/batch/PlantNotificationWriter.kt +++ b/batch/src/main/kotlin/dnd11th/blooming/batch/notification/PlantNotificationWriter.kt @@ -1,26 +1,36 @@ -package dnd11th.blooming.batch +package dnd11th.blooming.batch.notification import dnd11th.blooming.client.fcm.FcmService import dnd11th.blooming.client.fcm.PushNotification +import dnd11th.blooming.common.util.Logger.Companion.log +import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.springframework.batch.core.configuration.annotation.StepScope import org.springframework.batch.item.ItemWriter import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicInteger @Configuration class PlantNotificationWriter( private val fcmService: FcmService, ) { + private var counter = AtomicInteger(0) + private val customDispatcher = Executors.newFixedThreadPool(3).asCoroutineDispatcher() + @Bean @StepScope fun waterNotificationItemWriter(): ItemWriter { return ItemWriter { pushNotifications -> runBlocking { pushNotifications.forEach { pushNotification -> - launch { - fcmService.send(pushNotification) // 비동기 처리 + launch(customDispatcher) { + val currentCount = counter.incrementAndGet() + val threadName = Thread.currentThread().name + fcmService.mock(pushNotification) + log.info { "Thread: $threadName, Count: $currentCount" } } } } diff --git a/batch/src/main/kotlin/dnd11th/blooming/batch/weather/WeatherCareMessageWriter.kt b/batch/src/main/kotlin/dnd11th/blooming/batch/weather/WeatherCareMessageWriter.kt index af1998b4..22fa19f8 100644 --- a/batch/src/main/kotlin/dnd11th/blooming/batch/weather/WeatherCareMessageWriter.kt +++ b/batch/src/main/kotlin/dnd11th/blooming/batch/weather/WeatherCareMessageWriter.kt @@ -8,7 +8,7 @@ import dnd11th.blooming.core.entity.region.Region import dnd11th.blooming.redis.entity.weather.WeatherCareMessage import dnd11th.blooming.redis.entity.weather.WeatherCareMessageType import dnd11th.blooming.redis.repository.weather.WeatherCareMessageRepository -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.runBlocking @@ -20,7 +20,9 @@ import org.springframework.util.StopWatch import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.Collections +import java.util.concurrent.Executors import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger @Configuration class WeatherCareMessageWriter( @@ -41,6 +43,9 @@ class WeatherCareMessageWriter( private const val HIGH_TEMPERATURE_THRESHOLD = 30 } + private var counter = AtomicInteger(0) + private val customDispatcher = Executors.newFixedThreadPool(3).asCoroutineDispatcher() + @Bean @StepScope fun weatherCareMessageItemWriter(): ItemWriter { @@ -53,7 +58,9 @@ class WeatherCareMessageWriter( runBlocking { val jobs = regions.map { region -> - async(Dispatchers.IO) { + async(customDispatcher) { + val currentCount = counter.incrementAndGet() + val threadName = Thread.currentThread().name try { val weatherItems: List = weatherInfoClient.getWeatherInfo( @@ -68,18 +75,18 @@ class WeatherCareMessageWriter( } catch (e: Exception) { log.error(e) { "날씨 데이터를 불러오는데 실패했습니다: ${region.id}" } } + log.info { "Thread: $threadName, Count: $currentCount" } } } jobs.awaitAll() runBlockingStopWatch.stop() log.info { runBlockingStopWatch.getTotalTime(TimeUnit.MILLISECONDS) } - - val saveStopWatch = StopWatch() - saveStopWatch.start() - weatherCareMessageRepository.saveAll(weatherCareMessages) - saveStopWatch.stop() - log.info { saveStopWatch.getTotalTime(TimeUnit.MILLISECONDS) } } + val saveStopWatch = StopWatch() + saveStopWatch.start() + weatherCareMessageRepository.saveAll(weatherCareMessages) + saveStopWatch.stop() + log.info { saveStopWatch.getTotalTime(TimeUnit.MILLISECONDS) } } } diff --git a/batch/src/main/resources/application.yml b/batch/src/main/resources/application.yml deleted file mode 100644 index 88f61e95..00000000 --- a/batch/src/main/resources/application.yml +++ /dev/null @@ -1,38 +0,0 @@ -spring: - application: - name: batch application - profiles: - active: local ---- -spring: - config: - activate: - on-profile: local - import: - - client.yml - - redis-local.yml - - core-local.yml - batch: - jdbc: - initialize-schema: always - job: - enabled: false - -server: - port: 8081 ---- -spring: - config: - activate: - on-profile: prod - import: - - client.yml - - redis-local.yml - - core-local.yml - batch: - jdbc: - initialize-schema: never - job: - enabled: false -server: - port: 8080 diff --git a/batch/src/main/resources/batch-local.yml b/batch/src/main/resources/batch-local.yml new file mode 100644 index 00000000..5669e029 --- /dev/null +++ b/batch/src/main/resources/batch-local.yml @@ -0,0 +1,6 @@ +spring: + batch: + jdbc: + initialize-schema: always + job: + enabled: false diff --git a/batch/src/main/resources/batch-prod.yml b/batch/src/main/resources/batch-prod.yml new file mode 100644 index 00000000..4cde540b --- /dev/null +++ b/batch/src/main/resources/batch-prod.yml @@ -0,0 +1,6 @@ +spring: + batch: + jdbc: + initialize-schema: never + job: + enabled: false diff --git a/client/src/main/kotlin/dnd11th/blooming/client/fcm/FcmService.kt b/client/src/main/kotlin/dnd11th/blooming/client/fcm/FcmService.kt index 6064942b..f1d8be07 100644 --- a/client/src/main/kotlin/dnd11th/blooming/client/fcm/FcmService.kt +++ b/client/src/main/kotlin/dnd11th/blooming/client/fcm/FcmService.kt @@ -1,7 +1,7 @@ package dnd11th.blooming.client.fcm interface FcmService { - suspend fun send(pushNotification: PushNotification) + fun send(pushNotification: PushNotification) - suspend fun mock(pushNotification: PushNotification) + fun mock(pushNotification: PushNotification) } diff --git a/client/src/main/kotlin/dnd11th/blooming/client/fcm/FcmServiceImpl.kt b/client/src/main/kotlin/dnd11th/blooming/client/fcm/FcmServiceImpl.kt index dae1be7b..3ae4031c 100644 --- a/client/src/main/kotlin/dnd11th/blooming/client/fcm/FcmServiceImpl.kt +++ b/client/src/main/kotlin/dnd11th/blooming/client/fcm/FcmServiceImpl.kt @@ -2,37 +2,41 @@ package dnd11th.blooming.client.fcm import com.google.firebase.FirebaseApp import com.google.firebase.messaging.FirebaseMessaging +import com.google.firebase.messaging.FirebaseMessagingException import com.google.firebase.messaging.Message import com.google.firebase.messaging.Notification -import kotlinx.coroutines.delay +import dnd11th.blooming.common.exception.ErrorType +import dnd11th.blooming.common.exception.ExternalServerException import org.springframework.stereotype.Service @Service class FcmServiceImpl : FcmService { - override suspend fun send(pushNotification: PushNotification) { - val notification: Notification = - Notification.builder() - .setTitle(pushNotification.title) - .setBody(pushNotification.content) - .build() - - val message: Message = - Message - .builder() - .setToken(pushNotification.token) - .setNotification(notification) - .putData("myPlantId", pushNotification.myPlantId.toString()) - .build() + override fun send(pushNotification: PushNotification) { + try { + val notification: Notification = + Notification.builder() + .setTitle(pushNotification.title) + .setBody(pushNotification.content) + .build() - if (FirebaseApp.getApps().isEmpty()) { - FirebaseMessaging.getInstance().send(message) + val message: Message = + Message + .builder() + .setToken(pushNotification.token) + .setNotification(notification) + .putData("myPlantId", pushNotification.myPlantId.toString()) + .build() + if (FirebaseApp.getApps().isNotEmpty()) { + FirebaseMessaging.getInstance().sendAsync(message) + } + } catch (e: FirebaseMessagingException) { + throw ExternalServerException(ErrorType.FIREBASE_API_CALL_EXCEPTION) } } - override suspend fun mock(pushNotification: PushNotification) { + override fun mock(pushNotification: PushNotification) { try { - // 100ms 동안 스레드를 대기 - delay(100) + Thread.sleep(50) } catch (e: InterruptedException) { e.printStackTrace() } diff --git a/common/src/main/kotlin/dnd11th/blooming/common/exception/ErrorType.kt b/common/src/main/kotlin/dnd11th/blooming/common/exception/ErrorType.kt index 37c3946a..136ccfd9 100644 --- a/common/src/main/kotlin/dnd11th/blooming/common/exception/ErrorType.kt +++ b/common/src/main/kotlin/dnd11th/blooming/common/exception/ErrorType.kt @@ -11,7 +11,7 @@ enum class ErrorType(val status: HttpStatus, var message: String, val logLevel: HTTP_METHOD_NOT_SUPPORTED(HttpStatus.METHOD_NOT_ALLOWED, "지원하지 않는 HTTP METHOD 입니다.", LogLevel.DEBUG), PATH_VARIABLE_MISSING(HttpStatus.BAD_REQUEST, "경로 변수가 누락되었습니다.", LogLevel.DEBUG), REQUEST_PARAMETER_MISSING(HttpStatus.BAD_REQUEST, "요청 파라미터가 누락되었습니다.", LogLevel.DEBUG), - ARUGMENT_TYPE_MISMATCH(HttpStatus.BAD_REQUEST, "경로 변수 혹은 요청 파라미터 타입이 올바르지 않습니다.", LogLevel.DEBUG), + ARGUMENT_TYPE_MISMATCH(HttpStatus.BAD_REQUEST, "경로 변수 혹은 요청 파라미터 타입이 올바르지 않습니다.", LogLevel.DEBUG), // MyPlant NOT_FOUND_MYPLANT(HttpStatus.NOT_FOUND, "존재하지 않는 내 식물입니다.", LogLevel.DEBUG), @@ -40,8 +40,9 @@ enum class ErrorType(val status: HttpStatus, var message: String, val logLevel: // User USER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않은 사용자입니다.", LogLevel.DEBUG), - // OpenAPI + // CLIENT OPEN_API_CALL_EXCEPTION(HttpStatus.BAD_GATEWAY, "OpenAPI 호출에 실패했습니다", LogLevel.WARN), + FIREBASE_API_CALL_EXCEPTION(HttpStatus.BAD_GATEWAY, "FCM 호출에 실패했습니다", LogLevel.WARN), // REGION NOT_FOUND_REGION(HttpStatus.NOT_FOUND, "존재하지 않는 지역번호입니다.", LogLevel.DEBUG), diff --git a/domain/core/src/main/kotlin/dnd11th/blooming/core/entity/myplant/UserPlantDto.kt b/domain/core/src/main/kotlin/dnd11th/blooming/core/entity/myplant/UserPlantDto.kt index bad17181..835abdca 100644 --- a/domain/core/src/main/kotlin/dnd11th/blooming/core/entity/myplant/UserPlantDto.kt +++ b/domain/core/src/main/kotlin/dnd11th/blooming/core/entity/myplant/UserPlantDto.kt @@ -5,6 +5,7 @@ import java.time.LocalDate data class UserPlantDto( val userId: Long, val userEmail: String, + val deviceToken: String, val myPlantId: Long, val plantNickname: String, val lastWateredDate: LocalDate, diff --git a/domain/core/src/main/kotlin/dnd11th/blooming/core/repository/myplant/MyPlantQueryDslRepository.kt b/domain/core/src/main/kotlin/dnd11th/blooming/core/repository/myplant/MyPlantQueryDslRepository.kt index 4dcd97c5..8607a02f 100644 --- a/domain/core/src/main/kotlin/dnd11th/blooming/core/repository/myplant/MyPlantQueryDslRepository.kt +++ b/domain/core/src/main/kotlin/dnd11th/blooming/core/repository/myplant/MyPlantQueryDslRepository.kt @@ -4,5 +4,9 @@ import dnd11th.blooming.core.entity.myplant.UserPlantDto import dnd11th.blooming.core.entity.user.AlarmTime interface MyPlantQueryDslRepository { - fun findPlantsByAlarmTimeInBatch(alarmTime: AlarmTime): List + fun findNeedWaterPlantsByAlarmTimeInBatch(alarmTime: AlarmTime): List + + fun findNeedFertilizerPlantsByAlarmTimeInBatch(alarmTime: AlarmTime): List + + fun findNeedHealthCheckPlantsByAlarmTimeInBatch(alarmTime: AlarmTime): List } diff --git a/domain/core/src/main/kotlin/dnd11th/blooming/core/repository/myplant/MyPlantQueryDslRepositoryImpl.kt b/domain/core/src/main/kotlin/dnd11th/blooming/core/repository/myplant/MyPlantQueryDslRepositoryImpl.kt index bdbfa985..86b99ac5 100644 --- a/domain/core/src/main/kotlin/dnd11th/blooming/core/repository/myplant/MyPlantQueryDslRepositoryImpl.kt +++ b/domain/core/src/main/kotlin/dnd11th/blooming/core/repository/myplant/MyPlantQueryDslRepositoryImpl.kt @@ -13,15 +13,21 @@ import org.springframework.stereotype.Repository class MyPlantQueryDslRepositoryImpl( private val queryFactory: JPAQueryFactory, ) : MyPlantQueryDslRepository { - override fun findPlantsByAlarmTimeInBatch(alarmTime: AlarmTime): List { - val myPlant = QMyPlant.myPlant - val user = QUser.user + companion object { + const val HEALTH_CHECK_PERIOD_DAYS = 14 + } + + private val myPlant = QMyPlant.myPlant + private val user = QUser.user + + override fun findNeedWaterPlantsByAlarmTimeInBatch(alarmTime: AlarmTime): List { return queryFactory .select( Projections.constructor( UserPlantDto::class.java, user.id, user.email, + user.deviceToken, myPlant.id, myPlant.nickname, myPlant.lastWateredDate, @@ -33,6 +39,7 @@ class MyPlantQueryDslRepositoryImpl( .where( user.alarmStatus.isTrue, user.alarmTime.eq(alarmTime), + user.deviceToken.isNotNull, myPlant.alarm.waterAlarm.isTrue, Expressions.numberTemplate( Int::class.java, @@ -43,4 +50,66 @@ class MyPlantQueryDslRepositoryImpl( ) .fetch() } + + override fun findNeedFertilizerPlantsByAlarmTimeInBatch(alarmTime: AlarmTime): List { + return queryFactory + .select( + Projections.constructor( + UserPlantDto::class.java, + user.id, + user.email, + user.deviceToken, + myPlant.id, + myPlant.nickname, + myPlant.lastWateredDate, + myPlant.alarm.waterPeriod, + ), + ) + .from(myPlant) + .join(myPlant.user, user) + .where( + user.alarmStatus.isTrue, + user.alarmTime.eq(alarmTime), + user.deviceToken.isNotNull, + myPlant.alarm.fertilizerAlarm.isTrue, + Expressions.numberTemplate( + Int::class.java, + "DATEDIFF(CURRENT_DATE, {0})", + myPlant.lastFertilizerDate, + ) + .eq(myPlant.alarm.fertilizerPeriod), + ) + .fetch() + } + + override fun findNeedHealthCheckPlantsByAlarmTimeInBatch(alarmTime: AlarmTime): List { + return queryFactory + .select( + Projections.constructor( + UserPlantDto::class.java, + user.id, + user.email, + user.deviceToken, + myPlant.id, + myPlant.nickname, + myPlant.lastWateredDate, + myPlant.alarm.waterPeriod, + ), + ) + .from(myPlant) + .join(myPlant.user, user) + .where( + user.alarmStatus.isTrue, + user.alarmTime.eq(alarmTime), + user.deviceToken.isNotNull, + myPlant.alarm.healthCheckAlarm.isTrue, + Expressions.numberTemplate( + Int::class.java, + "DATEDIFF(CURRENT_DATE, {0})", + myPlant.lastHealthCheckDate, + ) + .eq(HEALTH_CHECK_PERIOD_DAYS), + ) + .fetch() + } } diff --git a/domain/redis/src/main/kotlin/dnd11th/blooming/redis/entity/weather/WeatherCareMessageType.kt b/domain/redis/src/main/kotlin/dnd11th/blooming/redis/entity/weather/WeatherCareMessageType.kt index 9e30cc39..0adb1ecf 100644 --- a/domain/redis/src/main/kotlin/dnd11th/blooming/redis/entity/weather/WeatherCareMessageType.kt +++ b/domain/redis/src/main/kotlin/dnd11th/blooming/redis/entity/weather/WeatherCareMessageType.kt @@ -3,7 +3,7 @@ package dnd11th.blooming.redis.entity.weather enum class WeatherCareMessageType(val title: String, val message: String) { HUMIDITY( "과유불습", - "비가 계속 내려서 과습 위험이 있어요!\n" + + "습도가 높아서 과습 위험이 있어요!\n" + "물 주기 전에 흙이 촉촉한지 먼저 확인하고,\n" + "식물 친구들이 물에 잠기지 않도록 조심해주세요", ),