From 78d0aeec7a195bea4fc181d15c3a51f8ec088efe Mon Sep 17 00:00:00 2001 From: JIWONKIMS Date: Tue, 14 Oct 2025 12:31:42 +0900 Subject: [PATCH 1/2] feat(be): Add dummy guide data initializer for dev environment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DevConfig에 더미 가이드 2명 자동 생성 기능 추가 - 서울 가이드 1명, 부산 사하구 가이드 1명 - dev 프로필에서만 실행되며 중복 방지 로직 포함 - 생성/기존 통계 콘솔 출력 기능 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- build.gradle.kts | 1 + .../common/config/DevConfig.kt | 67 +++++++++++++++++++ .../common/config/JacksonConfig.kt | 21 ++++++ .../common/config/RedisConfig.kt | 8 --- 4 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/com/back/koreaTravelGuide/common/config/JacksonConfig.kt diff --git a/build.gradle.kts b/build.gradle.kts index b7bb36d..f7af5c6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml") implementation("org.jetbrains.kotlin:kotlin-reflect") diff --git a/src/main/kotlin/com/back/koreaTravelGuide/common/config/DevConfig.kt b/src/main/kotlin/com/back/koreaTravelGuide/common/config/DevConfig.kt index b962c68..a5da7ec 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/common/config/DevConfig.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/common/config/DevConfig.kt @@ -1,5 +1,9 @@ package com.back.koreaTravelGuide.common.config +import com.back.koreaTravelGuide.domain.user.entity.User +import com.back.koreaTravelGuide.domain.user.enums.Region +import com.back.koreaTravelGuide.domain.user.enums.UserRole +import com.back.koreaTravelGuide.domain.user.repository.UserRepository import org.springframework.boot.CommandLineRunner import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -93,4 +97,67 @@ class DevConfig { println("=".repeat(80) + "\n") } } + + /** + * dev 모드에서 더미 가이드 데이터 생성 + * 서버 시작시 자동으로 가이드 2명을 데이터베이스에 추가 + */ + @Bean + fun dummyGuideDataInitializer(userRepository: UserRepository): CommandLineRunner { + return CommandLineRunner { + // 생성할 더미 가이드 리스트 + val dummyGuides = + listOf( + User( + oauthProvider = "dev", + oauthId = "dev-guide-001", + email = "dummy.guide@example.com", + nickname = "서울 투어 김가이드", + profileImageUrl = "https://i.pravatar.cc/300?img=12", + role = UserRole.GUIDE, + location = Region.SEOUL, + description = + "안녕하세요! 서울에서 10년간 가이드 경험이 있는 김가이드입니다. " + + "서울의 숨은 명소부터 핫플레이스까지 모두 안내해드릴 수 있습니다. " + + "특히 한식 맛집과 전통시장 투어에 자신 있습니다!", + ), + User( + oauthProvider = "dev", + oauthId = "dev-guide-002", + email = "busan.guide@example.com", + nickname = "부산 사하구 박가이드", + profileImageUrl = "https://i.pravatar.cc/300?img=33", + role = UserRole.GUIDE, + location = Region.BUSAN, + description = + "부산 사하구 토박이 박가이드입니다! 다대포 해수욕장, 을숙도, 감천문화마을 등 " + + "부산의 숨은 보석 같은 명소들을 소개해드립니다. " + + "특히 사하구 맛집과 바다 뷰 카페 투어는 제가 제일 자신 있어요. 부산 사투리로 정감있게 안내해드립니다!", + ), + ) + + println("\n📝 더미 가이드 데이터 초기화 시작...") + var createdCount = 0 + var existingCount = 0 + + dummyGuides.forEach { guide -> + val existing = userRepository.findByEmail(guide.email) + if (existing == null) { + userRepository.save(guide) + createdCount++ + println("\n✅ 가이드 생성 완료:") + println(" 📧 Email: ${guide.email}") + println(" 👤 Nickname: ${guide.nickname}") + println(" 📍 Location: ${guide.location?.displayName}") + } else { + existingCount++ + println("\n✅ 이미 존재하는 가이드: ${existing.nickname} (ID: ${existing.id})") + } + } + + println("\n📊 더미 가이드 데이터 초기화 완료!") + println(" ✨ 새로 생성: ${createdCount}명") + println(" ♻️ 기존 유지: ${existingCount}명") + } + } } diff --git a/src/main/kotlin/com/back/koreaTravelGuide/common/config/JacksonConfig.kt b/src/main/kotlin/com/back/koreaTravelGuide/common/config/JacksonConfig.kt new file mode 100644 index 0000000..e9c07be --- /dev/null +++ b/src/main/kotlin/com/back/koreaTravelGuide/common/config/JacksonConfig.kt @@ -0,0 +1,21 @@ +package com.back.koreaTravelGuide.common.config + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder + +@Configuration +class JacksonConfig { + @Bean + fun objectMapper(): ObjectMapper { + return Jackson2ObjectMapperBuilder() + .modulesToInstall(JavaTimeModule()) + .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build() + .registerKotlinModule() + } +} diff --git a/src/main/kotlin/com/back/koreaTravelGuide/common/config/RedisConfig.kt b/src/main/kotlin/com/back/koreaTravelGuide/common/config/RedisConfig.kt index d6c25c6..d2926ad 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/common/config/RedisConfig.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/common/config/RedisConfig.kt @@ -5,7 +5,6 @@ import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse import com.back.koreaTravelGuide.domain.ai.weather.dto.MidForecastDto import com.back.koreaTravelGuide.domain.ai.weather.dto.TemperatureAndLandForecastDto import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.KotlinModule import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.cache.CacheManager @@ -45,13 +44,6 @@ class RedisConfig { return LettuceConnectionFactory(redisConfiguration) } - @Bean - fun objectMapper(): ObjectMapper = - ObjectMapper().apply { - // Kotlin 모듈 등록 (data class 생성자 인식) - registerModule(KotlinModule.Builder().build()) - } - @Bean fun redisTemplate(connectionFactory: RedisConnectionFactory): RedisTemplate { val template = RedisTemplate() From f7a8f1c07ba82a15895e2b135b6b8f8be3899181 Mon Sep 17 00:00:00 2001 From: JIWONKIMS Date: Tue, 14 Oct 2025 14:34:24 +0900 Subject: [PATCH 2/2] feat(be): Add RabbitMQ infrastructure and configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add docker-compose.yml for local RabbitMQ development - Configure RabbitMQ in application.yml and application-prod.yml - Add RabbitMQ container setup in Terraform (infra/main.tf) - Add RabbitMQ environment variables to deploy.yml - Remove docker-compose.yml from .gitignore for team sharing - Connection timeout and heartbeat settings for production stability 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/deploy.yml | 5 +++++ .gitignore | 3 +-- docker-compose.yml | 23 ++++++++++++++++++++ infra/main.tf | 28 +++++++++++++++++++++++++ src/main/resources/application-prod.yml | 10 +++++++++ src/main/resources/application.yml | 8 +++++++ 6 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 docker-compose.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index df3550a..8772d1f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -234,6 +234,11 @@ jobs: -e REDIS_HOST=redis_1 \ -e REDIS_PORT=6379 \ -e REDIS_PASSWORD="${PASSWORD_1}" \ + -e RABBITMQ_HOST=rabbitmq_1 \ + -e RABBITMQ_PORT=5672 \ + -e RABBITMQ_USERNAME=admin \ + -e RABBITMQ_PASSWORD="${PASSWORD_1}" \ + -e RABBITMQ_STOMP_PORT=61613 \ "${IMAGE}" # --------------------------------------------------------- diff --git a/.gitignore b/.gitignore index 7c3616a..c179ae7 100644 --- a/.gitignore +++ b/.gitignore @@ -75,5 +75,4 @@ build/reports/ coverage/ # Test file -index.html -docker-compose.yml \ No newline at end of file +index.html \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3722ff1 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: "3.8" + +services: + rabbit: + image: rabbitmq:3-management # 버전 명시 권장 (예: 3.13-management) + container_name: rabbitmq-1 + ports: + - "5672:5672" # AMQP (Spring ↔ RabbitMQ) + - "61613:61613" # STOMP (Relay가 여기에 붙음) + - "15672:15672" # Management UI (로컬에서만) + environment: + RABBITMQ_DEFAULT_USER: admin + RABBITMQ_DEFAULT_PASS: admin + volumes: # 영속화가 필요할 때만 유지 + - ./dockerProjects/rabbitmq-1/volumes/etc/rabbitmq:/etc/rabbitmq + - ./dockerProjects/rabbitmq-1/volumes/var/lib/rabbitmq:/var/lib/rabbitmq + - ./dockerProjects/rabbitmq-1/volumes/var/log/rabbitmq:/var/log/rabbitmq + command: > + sh -c " + rabbitmq-plugins enable rabbitmq_management && + rabbitmq-plugins enable rabbitmq_stomp && + rabbitmq-server + " diff --git a/infra/main.tf b/infra/main.tf index 0b68455..ba9c8be 100644 --- a/infra/main.tf +++ b/infra/main.tf @@ -263,6 +263,34 @@ CREATE DATABASE \"${var.app_1_db_name}\" OWNER team11; GRANT ALL PRIVILEGES ON DATABASE \"${var.app_1_db_name}\" TO team11; " +# rabbitmq 설치 +docker run -d \ + --name rabbitmq_1 \ + --restart unless-stopped \ + --network common \ + -p 5672:5672 \ + -p 61613:61613 \ + -p 15672:15672 \ + -e RABBITMQ_DEFAULT_USER=admin \ + -e RABBITMQ_DEFAULT_PASS=${var.password_1} \ + -e TZ=Asia/Seoul \ + -v /dockerProjects/rabbitmq_1/volumes/data:/var/lib/rabbitmq \ + rabbitmq:3-management + +# RabbitMQ가 준비될 때까지 대기 +echo "RabbitMQ가 기동될 때까지 대기 중..." +until docker exec rabbitmq_1 rabbitmqctl status &> /dev/null; do + echo "RabbitMQ가 아직 준비되지 않음. 5초 후 재시도..." + sleep 5 +done +echo "RabbitMQ가 준비됨. STOMP 플러그인 활성화 중..." + +# RabbitMQ STOMP 플러그인 활성화 +docker exec rabbitmq_1 rabbitmq-plugins enable rabbitmq_stomp +docker exec rabbitmq_1 rabbitmq-plugins enable rabbitmq_management + +echo "RabbitMQ 설치 및 설정 완료!" + echo "${var.github_access_token_1}" | docker login ghcr.io -u ${var.github_access_token_1_owner} --password-stdin END_OF_FILE diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 68cb403..e18a96a 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -32,6 +32,16 @@ spring: password: ${REDIS_PASSWORD:} database: 0 + # RabbitMQ 설정 (WebSocket STOMP Relay용) + rabbitmq: + host: ${RABBITMQ_HOST} + port: ${RABBITMQ_PORT} + username: ${RABBITMQ_USERNAME} + password: ${RABBITMQ_PASSWORD} + stomp-port: ${RABBITMQ_STOMP_PORT} + connection-timeout: 5000 + requested-heartbeat: 30 + session: store-type: redis timeout: 30m diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9fc1000..4786d61 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -81,6 +81,14 @@ spring: max-idle: 8 min-idle: 0 + # RabbitMQ 설정 (WebSocket STOMP Relay용) + rabbitmq: + host: ${RABBITMQ_HOST:localhost} + port: ${RABBITMQ_PORT:5672} + username: ${RABBITMQ_USERNAME:admin} + password: ${RABBITMQ_PASSWORD:admin} + stomp-port: ${RABBITMQ_STOMP_PORT:61613} + # 세션 관리 (개발용: none, 운영용: redis) session: store-type: none # Redis 없어도 실행 가능하도록 변경