recommend 수정

This commit is contained in:
youbeen 2025-06-12 13:41:18 +09:00
parent 544fa1624e
commit b822917e4e
11 changed files with 521 additions and 149 deletions

View File

@ -3,4 +3,8 @@ dependencies {
// AI and Location Services
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer'
}

View File

@ -2,19 +2,19 @@ package com.ktds.hi.recommend;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
/**
* 추천 서비스 메인 애플리케이션 클래스
* 가게 추천, 취향 분석 기능을 제공
*
* @author 하이오더 개발팀
* @version 1.0.0
* 추천 서비스 메인 애플리케이션
*/
@SpringBootApplication(scanBasePackages = {"com.ktds.hi.recommend", "com.ktds.hi.common"})
@SpringBootApplication(scanBasePackages = {
"com.ktds.hi.recommend",
"com.ktds.hi.common"
})
@EnableFeignClients
@EnableJpaAuditing
public class RecommendServiceApplication {
public static void main(String[] args) {
SpringApplication.run(RecommendServiceApplication.class, args);
}

View File

@ -1,12 +1,18 @@
// recommend/src/main/java/com/ktds/hi/recommend/infra/controller/StoreRecommendController.java
package com.ktds.hi.recommend.infra.controller;
import com.ktds.hi.recommend.biz.usecase.in.StoreRecommendUseCase;
import com.ktds.hi.recommend.infra.dto.request.RecommendStoreRequest;
import com.ktds.hi.recommend.infra.dto.response.RecommendStoreResponse;
import com.ktds.hi.recommend.infra.dto.response.StoreDetailResponse;
import com.ktds.hi.common.dto.response.ApiResponse;
import com.ktds.hi.common.dto.response.PageResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
@ -14,11 +20,10 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 매장 추천 컨트롤러 클래스
* 매장 추천 관련 API를 제공
* 매장 추천 컨트롤러
*/
@RestController
@RequestMapping("/api/recommend")
@RequestMapping("/api/recommend/stores")
@RequiredArgsConstructor
@Tag(name = "매장 추천 API", description = "사용자 취향 기반 매장 추천 관련 API")
public class StoreRecommendController {
@ -26,43 +31,82 @@ public class StoreRecommendController {
private final StoreRecommendUseCase storeRecommendUseCase;
/**
* 사용자 취향 기반 매장 추천 API
* 개인화 매장 추천
*/
@PostMapping("/stores")
@Operation(summary = "매장 추천", description = "사용자 취향과 위치를 기반으로 매장을 추천합니다.")
public ResponseEntity<List<RecommendStoreResponse>> recommendStores(Authentication authentication,
@PostMapping
@Operation(summary = "개인화 매장 추천", description = "사용자 취향과 위치를 기반으로 매장을 추천합니다.")
public ResponseEntity<ApiResponse<List<RecommendStoreResponse>>> recommendStores(
Authentication authentication,
@Valid @RequestBody RecommendStoreRequest request) {
Long memberId = Long.valueOf(authentication.getName());
List<RecommendStoreResponse> recommendations = storeRecommendUseCase.recommendStores(memberId, request);
return ResponseEntity.ok(recommendations);
List<RecommendStoreResponse> recommendations =
storeRecommendUseCase.recommendPersonalizedStores(memberId, request);
return ResponseEntity.ok(ApiResponse.success(recommendations));
}
/**
* 위치 기반 매장 추천 API
* 위치 기반 매장 추천
*/
@GetMapping("/stores/nearby")
@GetMapping("/nearby")
@Operation(summary = "주변 매장 추천", description = "현재 위치 기반으로 주변 매장을 추천합니다.")
public ResponseEntity<List<RecommendStoreResponse>> recommendNearbyStores(
public ResponseEntity<ApiResponse<PageResponse<RecommendStoreResponse>>> recommendNearbyStores(
@RequestParam Double latitude,
@RequestParam Double longitude,
@RequestParam(defaultValue = "5000") Integer radius) {
@RequestParam(defaultValue = "5000") Integer radius,
@RequestParam(required = false) String category,
@PageableDefault(size = 20) Pageable pageable) {
List<RecommendStoreResponse> recommendations = storeRecommendUseCase
.recommendStoresByLocation(latitude, longitude, radius);
return ResponseEntity.ok(recommendations);
PageResponse<RecommendStoreResponse> recommendations =
storeRecommendUseCase.recommendStoresByLocation(latitude, longitude, radius, category, pageable);
return ResponseEntity.ok(ApiResponse.success(recommendations));
}
/**
* 인기 매장 추천 API
* 인기 매장 추천
*/
@GetMapping("/stores/popular")
@GetMapping("/popular")
@Operation(summary = "인기 매장 추천", description = "카테고리별 인기 매장을 추천합니다.")
public ResponseEntity<List<RecommendStoreResponse>> recommendPopularStores(
public ResponseEntity<ApiResponse<List<RecommendStoreResponse>>> recommendPopularStores(
@RequestParam(required = false) String category,
@RequestParam(defaultValue = "10") Integer limit) {
List<RecommendStoreResponse> recommendations = storeRecommendUseCase
.recommendPopularStores(category, limit);
return ResponseEntity.ok(recommendations);
List<RecommendStoreResponse> recommendations =
storeRecommendUseCase.recommendPopularStores(category, limit);
return ResponseEntity.ok(ApiResponse.success(recommendations));
}
/**
* 추천 매장 상세 조회
*/
@GetMapping("/{storeId}")
@Operation(summary = "추천 매장 상세 조회", description = "추천된 매장의 상세 정보를 조회합니다.")
public ResponseEntity<ApiResponse<StoreDetailResponse>> getRecommendedStoreDetail(
@PathVariable Long storeId,
Authentication authentication) {
Long memberId = authentication != null ? Long.valueOf(authentication.getName()) : null;
StoreDetailResponse storeDetail =
storeRecommendUseCase.getRecommendedStoreDetail(storeId, memberId);
return ResponseEntity.ok(ApiResponse.success(storeDetail));
}
/**
* 추천 클릭 로깅
*/
@PostMapping("/{storeId}/click")
@Operation(summary = "추천 클릭 로깅", description = "추천된 매장 클릭을 로깅합니다.")
public ResponseEntity<ApiResponse<Void>> logRecommendClick(
@PathVariable Long storeId,
Authentication authentication) {
Long memberId = Long.valueOf(authentication.getName());
storeRecommendUseCase.logRecommendClick(memberId, storeId);
return ResponseEntity.ok(ApiResponse.success());
}
}

View File

@ -1,18 +1,22 @@
package com.ktds.hi.recommend.infra.controller;
import com.ktds.hi.recommend.biz.usecase.in.TasteAnalysisUseCase;
import com.ktds.hi.recommend.infra.dto.request.TasteUpdateRequest;
import com.ktds.hi.recommend.infra.dto.response.TasteAnalysisResponse;
import com.ktds.hi.common.dto.SuccessResponse;
import com.ktds.hi.recommend.infra.dto.response.PreferenceTagResponse;
import com.ktds.hi.common.dto.response.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 취향 분석 컨트롤러 클래스
* 사용자 취향 분석 관련 API를 제공
* 취향 분석 컨트롤러
*/
@RestController
@RequestMapping("/api/recommend/taste")
@ -23,24 +27,43 @@ public class TasteAnalysisController {
private final TasteAnalysisUseCase tasteAnalysisUseCase;
/**
* 사용자 취향 분석 조회 API
* 사용자 취향 분석 조회
*/
@GetMapping("/analysis")
@Operation(summary = "취향 분석 조회", description = "현재 로그인한 사용자의 취향 분석 결과를 조회합니다.")
public ResponseEntity<TasteAnalysisResponse> getMemberTasteAnalysis(Authentication authentication) {
public ResponseEntity<ApiResponse<TasteAnalysisResponse>> getMemberTasteAnalysis(
Authentication authentication) {
Long memberId = Long.valueOf(authentication.getName());
TasteAnalysisResponse analysis = tasteAnalysisUseCase.analyzeMemberTaste(memberId);
return ResponseEntity.ok(analysis);
return ResponseEntity.ok(ApiResponse.success(analysis));
}
/**
* 취향 프로필 업데이트 API
* 취향 프로필 업데이트
*/
@PostMapping("/update")
@Operation(summary = "취향 프로필 업데이트", description = "사용자의 리뷰 데이터를 기반으로 취향 프로필을 업데이트합니다.")
public ResponseEntity<SuccessResponse> updateTasteProfile(Authentication authentication) {
public ResponseEntity<ApiResponse<Void>> updateTasteProfile(
Authentication authentication,
@Valid @RequestBody TasteUpdateRequest request) {
Long memberId = Long.valueOf(authentication.getName());
tasteAnalysisUseCase.updateTasteProfile(memberId);
return ResponseEntity.ok(SuccessResponse.of("취향 프로필이 업데이트되었습니다"));
tasteAnalysisUseCase.updateTasteProfile(memberId, request);
return ResponseEntity.ok(ApiResponse.success("취향 프로필이 업데이트되었습니다"));
}
/**
* 가용한 취향 태그 조회
*/
@GetMapping("/tags")
@Operation(summary = "취향 태그 목록 조회", description = "선택 가능한 취향 태그 목록을 조회합니다.")
public ResponseEntity<ApiResponse<List<PreferenceTagResponse>>> getAvailablePreferenceTags() {
List<PreferenceTagResponse> tags = tasteAnalysisUseCase.getAvailablePreferenceTags();
return ResponseEntity.ok(ApiResponse.success(tags));
}
}

View File

@ -1,10 +1,10 @@
package com.ktds.hi.recommend.infra.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import jakarta.validation.constraints.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
@ -12,46 +12,36 @@ import java.util.List;
* 매장 추천 요청 DTO
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "매장 추천 요청")
public class RecommendStoreRequest {
@NotNull(message = "위도는 필수입니다")
@DecimalMin(value = "-90.0", message = "위도는 -90도 이상이어야 합니다")
@DecimalMax(value = "90.0", message = "위도는 90도 이하여야 합니다")
@Schema(description = "위도", example = "37.5665")
private Double latitude;
@NotNull(message = "경도는 필수입니다")
@DecimalMin(value = "-180.0", message = "경도는 -180도 이상이어야 합니다")
@DecimalMax(value = "180.0", message = "경도는 180도 이하여야 합니다")
@Schema(description = "경도", example = "126.9780")
private Double longitude;
@Schema(description = "검색 반경(미터)", example = "5000", defaultValue = "5000")
@Min(value = 100, message = "반경은 최소 100m 이상이어야 합니다")
@Max(value = 50000, message = "반경은 최대 50km 이하여야 합니다")
@Schema(description = "검색 반경 (미터)", example = "5000")
private Integer radius = 5000;
@Schema(description = "선호 카테고리", example = "[\"한식\", \"일식\"]")
private List<String> preferredCategories;
@Schema(description = "카테고리 필터", example = "한식")
private String category;
@Schema(description = "가격 범위", example = "MEDIUM")
private String priceRange;
@Schema(description = "취향 태그 목록", example = "[\"매운맛\", \"혼밥\"]")
private List<String> tags;
@Schema(description = "추천 개수", example = "10", defaultValue = "10")
@Min(value = 1, message = "최소 1개 이상의 결과가 필요합니다")
@Max(value = 50, message = "최대 50개까지 조회 가능합니다")
@Schema(description = "결과 개수", example = "10")
private Integer limit = 10;
/**
* 유효성 검증
*/
public void validate() {
if (latitude == null || longitude == null) {
throw new IllegalArgumentException("위도와 경도는 필수입니다");
}
if (latitude < -90 || latitude > 90) {
throw new IllegalArgumentException("위도는 -90과 90 사이여야 합니다");
}
if (longitude < -180 || longitude > 180) {
throw new IllegalArgumentException("경도는 -180과 180 사이여야 합니다");
}
if (radius != null && radius <= 0) {
throw new IllegalArgumentException("검색 반경은 0보다 커야 합니다");
}
}
}

View File

@ -0,0 +1,26 @@
package com.ktds.hi.recommend.infra.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
/**
* 취향 업데이트 요청 DTO
*/
@Getter
@Setter
@NoArgsConstructor
@Schema(description = "취향 프로필 업데이트 요청")
public class TasteUpdateRequest {
@NotEmpty(message = "선호 카테고리는 최소 1개 이상 선택해야 합니다")
@Schema(description = "선호 카테고리 목록", example = "[\"한식\", \"일식\"]")
private List<String> preferredCategories;
@Schema(description = "선호 태그 목록", example = "[\"매운맛\", \"혼밥\", \"가성비\"]")
private List<String> preferredTags;
}

View File

@ -0,0 +1,34 @@
package com.ktds.hi.recommend.infra.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;
/**
* 취향 태그 응답 DTO
*/
@Getter
@Builder
@Schema(description = "취향 태그 응답")
public class PreferenceTagResponse {
@Schema(description = "태그명", example = "매운맛")
private String tagName;
@Schema(description = "태그 아이콘", example = "🌶️")
private String icon;
@Schema(description = "태그 설명", example = "매운 음식을 선호")
private String description;
/**
* 정적 생성 메서드
*/
public static PreferenceTagResponse of(String tagName, String icon, String description) {
return PreferenceTagResponse.builder()
.tagName(tagName)
.icon(icon)
.description(description)
.build();
}
}

View File

@ -1,50 +1,55 @@
package com.ktds.hi.recommend.infra.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 매장 추천 응답 DTO
* 추천 매장 응답 DTO
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "매장 추천 응답")
@Schema(description = "추천 매장 응답")
public class RecommendStoreResponse {
@Schema(description = "매장 ID")
@Schema(description = "매장 ID", example = "1")
private Long storeId;
@Schema(description = "매장명")
@Schema(description = "매장명", example = "맛있는 김치찌개")
private String storeName;
@Schema(description = "주소")
private String address;
@Schema(description = "카테고리")
@Schema(description = "카테고리", example = "한식")
private String category;
@Schema(description = "태그 목록")
private List<String> tags;
@Schema(description = "주소", example = "서울시 강남구 테헤란로 123")
private String address;
@Schema(description = "평점")
private Double rating;
@Schema(description = "위도", example = "37.5665")
private Double latitude;
@Schema(description = "리뷰 수")
@Schema(description = "경도", example = "126.9780")
private Double longitude;
@Schema(description = "평균 평점", example = "4.5")
private Double averageRating;
@Schema(description = "리뷰 수", example = "127")
private Integer reviewCount;
@Schema(description = "거리(미터)")
private Double distance;
@Schema(description = "추천 점수")
@Schema(description = "추천 점수", example = "0.85")
private Double recommendScore;
@Schema(description = "추천 이유")
@Schema(description = "추천 이유", example = "좋아하는 '매운맛' 태그에 맞는 매장입니다")
private String recommendReason;
@Schema(description = "거리 (미터)", example = "1200")
private Double distance;
@Schema(description = "매장 이미지 URL 목록")
private List<String> imageUrls;
@Schema(description = "태그 목록", example = "[\"매운맛\", \"혼밥\"]")
private List<String> tags;
}

View File

@ -0,0 +1,58 @@
package com.ktds.hi.recommend.infra.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
/**
* 매장 상세 정보 응답 DTO
*/
@Getter
@Builder
@Schema(description = "매장 상세 정보 응답")
public class StoreDetailResponse {
@Schema(description = "매장 ID", example = "1")
private Long storeId;
@Schema(description = "매장명", example = "맛있는 김치찌개")
private String storeName;
@Schema(description = "카테고리", example = "한식")
private String category;
@Schema(description = "주소", example = "서울시 강남구 테헤란로 123")
private String address;
@Schema(description = "전화번호", example = "02-1234-5678")
private String phoneNumber;
@Schema(description = "위도", example = "37.5665")
private Double latitude;
@Schema(description = "경도", example = "126.9780")
private Double longitude;
@Schema(description = "평균 평점", example = "4.5")
private Double averageRating;
@Schema(description = "리뷰 수", example = "127")
private Integer reviewCount;
@Schema(description = "매장 이미지 URL 목록")
private List<String> imageUrls;
@Schema(description = "운영시간", example = "10:00-22:00")
private String operatingHours;
@Schema(description = "AI 요약", example = "친절한 서비스와 맛있는 김치찌개로 유명한 곳입니다")
private String aiSummary;
@Schema(description = "긍정 키워드", example = "[\"맛있다\", \"친절하다\", \"깔끔하다\"]")
private List<String> topPositiveKeywords;
@Schema(description = "부정 키워드", example = "[\"시끄럽다\", \"대기시간\"]")
private List<String> topNegativeKeywords;
}

View File

@ -1,46 +1,42 @@
package com.ktds.hi.recommend.infra.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* 취향 분석 응답 DTO
* 취향 분석 결과 응답 DTO
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "취향 분석 응답")
@Schema(description = "취향 분석 결과 응답")
public class TasteAnalysisResponse {
@Schema(description = "회원 ID")
@Schema(description = "회원 ID", example = "1")
private Long memberId;
@Schema(description = "선호 카테고리")
@Schema(description = "선호 카테고리 목록", example = "[\"한식\", \"일식\"]")
private List<String> preferredCategories;
@Schema(description = "최고 선호 카테고리")
@Schema(description = "최고 선호 카테고리", example = "한식")
private String topCategory;
@Schema(description = "카테고리별 점수")
private Map<String, Double> categoryScores;
@Schema(description = "선호 태그")
@Schema(description = "선호 태그 목록", example = "[\"매운맛\", \"혼밥\"]")
private List<String> preferredTags;
@Schema(description = "가격 선호도")
private Double pricePreference;
@Schema(description = "거리 선호도")
private Double distancePreference;
@Schema(description = "분석 일시")
private LocalDateTime analysisDate;
@Schema(description = "신뢰도", example = "0.75")
private Double confidence;
@Schema(description = "추천사항", example = "[\"한식 카테고리의 새로운 매장을 시도해보세요\"]")
private List<String> recommendations;
}

View File

@ -1,3 +1,4 @@
# recommend/src/main/resources/application.yml
server:
port: ${RECOMMEND_SERVICE_PORT:8085}
@ -5,12 +6,24 @@ spring:
application:
name: recommend-service
# 프로필 설정
profiles:
active: ${SPRING_PROFILES_ACTIVE:local}
# 데이터베이스 설정
datasource:
url: ${RECOMMEND_DB_URL:jdbc:postgresql://localhost:5432/hiorder_recommend}
username: ${RECOMMEND_DB_USERNAME:hiorder_user}
password: ${RECOMMEND_DB_PASSWORD:hiorder_pass}
driver-class-name: org.postgresql.Driver
hikari:
connection-timeout: 20000
maximum-pool-size: 10
minimum-idle: 5
idle-timeout: 300000
pool-name: RecommendHikariCP
# JPA 설정
jpa:
hibernate:
ddl-auto: ${JPA_DDL_AUTO:validate}
@ -19,27 +32,206 @@ spring:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
jdbc:
batch_size: 20
order_inserts: true
order_updates: true
open-in-view: false
# Redis 설정 (올바른 구조)
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
timeout: 2000ms
database: ${REDIS_DATABASE:0}
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 2
max-wait: -1ms
shutdown-timeout: 100ms
recommendation:
cache-ttl: 3600 # 1시간
max-recommendations: 20
# 외부 서비스 URL 설정
services:
store:
url: ${STORE_SERVICE_URL:http://store-service:8082}
review:
url: ${REVIEW_SERVICE_URL:http://review-service:8083}
member:
url: ${MEMBER_SERVICE_URL:http://member-service:8081}
# Feign 설정
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 10000
loggerLevel: basic
store-service:
connectTimeout: 3000
readTimeout: 8000
review-service:
connectTimeout: 3000
readTimeout: 8000
circuitbreaker:
enabled: true
compression:
request:
enabled: true
response:
enabled: true
# Circuit Breaker 설정
resilience4j:
circuitbreaker:
instances:
store-service:
failure-rate-threshold: 50
wait-duration-in-open-state: 30000
sliding-window-size: 10
minimum-number-of-calls: 5
review-service:
failure-rate-threshold: 50
wait-duration-in-open-state: 30000
sliding-window-size: 10
minimum-number-of-calls: 5
retry:
instances:
store-service:
max-attempts: 3
wait-duration: 1000
review-service:
max-attempts: 3
wait-duration: 1000
# 추천 알고리즘 설정
recommend:
algorithm:
distance-weight: 0.3
rating-weight: 0.3
taste-weight: 0.4
max-distance: 50000 # 50km
default-radius: 5000 # 5km
cache:
ttl:
recommendation: 30m
store-detail: 1h
taste-analysis: 6h
popular-stores: 2h
batch:
size: 100
max-concurrent: 5
location:
google-maps-api-key: ${GOOGLE_MAPS_API_KEY:}
hiorder-api:
base-url: ${HIORDER_API_BASE_URL:https://api.hiorder.com}
api-key: ${HIORDER_API_KEY:}
# Actuator 설정
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,configprops
base-path: /actuator
endpoint:
health:
show-details: when-authorized
show-components: always
metrics:
enabled: true
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
# Swagger/OpenAPI 설정
springdoc:
api-docs:
path: /api-docs
enabled: true
swagger-ui:
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
display-request-duration: true
display-operation-id: true
show-actuator: false
# 로깅 설정
logging:
level:
root: ${LOG_LEVEL_ROOT:INFO}
com.ktds.hi.recommend: ${LOG_LEVEL:INFO}
org.springframework.cloud.openfeign: ${LOG_LEVEL_FEIGN:DEBUG}
org.springframework.web: ${LOG_LEVEL_WEB:INFO}
org.springframework.data.redis: ${LOG_LEVEL_REDIS:INFO}
org.hibernate.SQL: ${LOG_LEVEL_SQL:INFO}
org.hibernate.type.descriptor.sql.BasicBinder: ${LOG_LEVEL_SQL_PARAM:INFO}
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId},%X{spanId}] %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId},%X{spanId}] %logger{36} - %msg%n"
file:
name: ${LOG_FILE_PATH:./logs/recommend-service.log}
max-size: 100MB
max-history: 30
# Security 설정
security:
jwt:
secret: ${JWT_SECRET:hiorder-recommend-secret-key-2024}
expiration: ${JWT_EXPIRATION:86400000} # 24시간
cors:
allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost:3000,http://localhost:8080}
allowed-methods: GET,POST,PUT,DELETE,OPTIONS
allowed-headers: "*"
allow-credentials: true
---
# Local 환경 설정
spring:
config:
activate:
on-profile: local
jpa:
show-sql: true
hibernate:
ddl-auto: create-drop
logging:
level:
com.ktds.hi.recommend: DEBUG
org.springframework.web: DEBUG
---
# Development 환경 설정
spring:
config:
activate:
on-profile: dev
jpa:
show-sql: true
hibernate:
ddl-auto: update
logging:
level:
com.ktds.hi.recommend: DEBUG
---
# Production 환경 설정
spring:
config:
activate:
on-profile: prod
jpa:
show-sql: false
hibernate:
ddl-auto: validate
logging:
level:
root: WARN
com.ktds.hi.recommend: INFO
org.springframework.cloud.openfeign: INFO