kos-mock 상품변경 실제 DB 업데이트 기능 추가

- MockDataService에 updateCustomerProduct 메서드 추가
- KosMockService에 실제 고객 데이터 업데이트 로직 추가
- 상품변경 시 고객의 current_product_code를 실제로 업데이트하도록 수정
- 트랜잭션 처리로 데이터 일관성 보장
- product-service Hibernate dialect 설정 추가

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
hiondal
2025-09-10 02:06:24 +09:00
parent 6ca4daed8d
commit 02bcfa5434
122 changed files with 6116 additions and 3983 deletions
+7 -10
View File
@@ -3,18 +3,15 @@
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="SPRING_PROFILES_ACTIVE" value="dev" />
<entry key="SERVER_PORT" value="8080" />
<entry key="JWT_SECRET" value="phonebill-jwt-secret-key-2025-dev" />
<entry key="AUTH_SERVICE_URL" value="http://localhost:8081" />
<entry key="BILL_SERVICE_URL" value="http://localhost:8082" />
<entry key="CORS_ALLOWED_ORIGINS" value="http://localhost:3000" />
<entry key="JWT_ACCESS_TOKEN_VALIDITY" value="18000000" />
<entry key="JWT_REFRESH_TOKEN_VALIDITY" value="86400000" />
<entry key="JWT_SECRET" value="nwe5Yo9qaJ6FBD/Thl2/j6/SFAfNwUorAY1ZcWO2KI7uA4bmVLOCPxE9hYuUpRCOkgV2UF2DdHXtqHi3+BU/ecbz2zpHyf/720h48UbA3XOMYOX1sdM+dQ==" />
<entry key="PRODUCT_SERVICE_URL" value="http://localhost:8083" />
<entry key="KOS_MOCK_SERVICE_URL" value="http://localhost:8084" />
<entry key="LOG_FILE" value="logs/api-gateway.log" />
<entry key="LOG_LEVEL_GATEWAY" value="DEBUG" />
<entry key="LOG_LEVEL_SPRING_CLOUD_GATEWAY" value="DEBUG" />
<entry key="LOG_LEVEL_REACTOR_NETTY" value="INFO" />
<entry key="LOG_LEVEL_ROOT" value="INFO" />
<entry key="SERVER_PORT" value="8080" />
<entry key="SPRING_PROFILES_ACTIVE" value="dev" />
</map>
</option>
<option name="executionName" />
@@ -37,4 +34,4 @@
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
</component>
@@ -1,6 +1,7 @@
package com.unicorn.phonebill.gateway.config;
import com.unicorn.phonebill.gateway.filter.JwtAuthenticationGatewayFilterFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
@@ -31,6 +32,9 @@ public class GatewayConfig {
private final JwtAuthenticationGatewayFilterFactory jwtAuthFilter;
@Value("${cors.allowed-origins")
private String allowedOrigins;
public GatewayConfig(JwtAuthenticationGatewayFilterFactory jwtAuthFilter) {
this.jwtAuthFilter = jwtAuthFilter;
}
@@ -48,28 +52,30 @@ public class GatewayConfig {
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// Auth Service 라우팅 (인증 불필요)
.route("auth-service", r -> r
.path("/auth/login", "/auth/refresh")
.route("user-service-login", r -> r
.path("/api/auth/login", "/api/auth/refresh")
.and()
.method("POST")
.uri("lb://auth-service"))
.filters(f -> f.rewritePath("/api/auth/(?<segment>.*)", "/auth/${segment}"))
.uri("lb://user-service"))
// Auth Service 라우팅 (인증 필요)
.route("auth-service-authenticated", r -> r
.path("/auth/**")
.route("user-service-authenticated", r -> r
.path("/api/auth/**")
.filters(f -> f
.rewritePath("/api/auth/(?<segment>.*)", "/auth/${segment}")
.filter(jwtAuthFilter.apply(new JwtAuthenticationGatewayFilterFactory.Config()))
.circuitBreaker(cb -> cb
.setName("auth-service-cb")
.setName("user-service-cb")
.setFallbackUri("forward:/fallback/auth"))
.retry(retry -> retry
.setRetries(3)
.setBackoff(java.time.Duration.ofSeconds(2), java.time.Duration.ofSeconds(10), 2, true)))
.uri("lb://auth-service"))
.uri("lb://user-service"))
// Bill-Inquiry Service 라우팅 (인증 필요)
.route("bill-service", r -> r
.path("/bills/**")
.path("/api/v1/bills/**")
.filters(f -> f
.filter(jwtAuthFilter.apply(new JwtAuthenticationGatewayFilterFactory.Config()))
.circuitBreaker(cb -> cb
@@ -131,15 +137,11 @@ public class GatewayConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration corsConfig = new CorsConfiguration();
// 허용할 Origin 설정
corsConfig.setAllowedOriginPatterns(Arrays.asList(
"http://localhost:3000", // React 개발 서버
"http://localhost:3001", // Next.js 개발 서버
"https://*.unicorn.com", // 운영 도메인
"https://*.phonebill.com" // 운영 도메인
));
// 환경변수에서 허용할 Origin 패턴 설정
String[] origins = allowedOrigins.split(",");
corsConfig.setAllowedOriginPatterns(Arrays.asList(origins));
// 허용할 HTTP 메서드
corsConfig.setAllowedMethods(Arrays.asList(
"GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"
@@ -44,9 +44,9 @@ public class JwtTokenService {
private final long refreshTokenValidityInSeconds;
public JwtTokenService(
@Value("${app.jwt.secret}") String jwtSecret,
@Value("${app.jwt.access-token-validity-in-seconds:1800}") long accessTokenValidityInSeconds,
@Value("${app.jwt.refresh-token-validity-in-seconds:86400}") long refreshTokenValidityInSeconds) {
@Value("${jwt.secret}") String jwtSecret,
@Value("${jwt.access-token-validity:1800}") long accessTokenValidityInSeconds,
@Value("${jwt.refresh-token-validity:86400}") long refreshTokenValidityInSeconds) {
this.secretKey = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
this.accessTokenValidityInSeconds = accessTokenValidityInSeconds;
@@ -1,128 +1,10 @@
# API Gateway 개발 환경 설정
server:
port: 8080
spring:
# Cloud Gateway 개발환경 설정
cloud:
gateway:
default-filters: []
globalcors:
cors-configurations:
'[/**]':
allowed-origin-patterns:
- "http://localhost:*"
- "http://127.0.0.1:*"
- "https://localhost:*"
allowed-methods: "*"
allowed-headers: "*"
allow-credentials: true
max-age: 86400 # 24시간
# 개발도구 설정
devtools:
restart:
enabled: true
additional-paths: src/main/java,src/main/resources
livereload:
enabled: true
# JWT 설정 (개발용 - 더 긴 유효시간)
app:
jwt:
secret: ${JWT_SECRET:dev-phonebill-api-gateway-jwt-secret-key-256-bit-minimum-length-for-development}
access-token-validity-in-seconds: 3600 # 1시간 (개발편의성)
refresh-token-validity-in-seconds: 172800 # 48시간 (개발편의성)
# Circuit Breaker 설정 (개발환경 - 더 관대한 설정)
resilience4j:
circuitbreaker:
instances:
auth-service-cb:
failure-rate-threshold: 80 # 개발환경은 더 관대한 임계값
wait-duration-in-open-state: 10s
sliding-window-size: 5
minimum-number-of-calls: 3
bill-service-cb:
failure-rate-threshold: 80
wait-duration-in-open-state: 10s
sliding-window-size: 5
minimum-number-of-calls: 3
product-service-cb:
failure-rate-threshold: 80
wait-duration-in-open-state: 10s
sliding-window-size: 5
minimum-number-of-calls: 3
kos-mock-cb:
failure-rate-threshold: 90
wait-duration-in-open-state: 5s
sliding-window-size: 5
minimum-number-of-calls: 2
# Actuator 설정 (개발환경 - 모든 엔드포인트 노출)
management:
endpoints:
web:
exposure:
include: "*" # 개발환경에서는 모든 엔드포인트 노출
base-path: /actuator
endpoint:
health:
show-details: always # 개발환경에서는 상세 정보 항상 표시
shutdown:
enabled: true # 개발환경에서만 활성화
beans:
enabled: true
env:
enabled: true
configprops:
enabled: true
# 로깅 설정 (개발환경 - 더 상세한 로그)
logging:
level:
com.unicorn.phonebill.gateway: ${LOG_LEVEL_GATEWAY:DEBUG}
org.springframework.cloud.gateway: ${LOG_LEVEL_SPRING_CLOUD_GATEWAY:DEBUG}
org.springframework.data.redis: ${LOG_LEVEL_SPRING_DATA_REDIS:DEBUG}
org.springframework.web.reactive: ${LOG_LEVEL_SPRING_WEB_REACTIVE:DEBUG}
reactor.netty.http.client: ${LOG_LEVEL_REACTOR_NETTY_HTTP_CLIENT:DEBUG}
io.netty.handler.ssl: ${LOG_LEVEL_IO_NETTY_HANDLER_SSL:WARN}
root: ${LOG_LEVEL_ROOT:INFO}
file:
name: ${LOG_FILE:logs/api-gateway.log}
max-size: ${LOG_FILE_MAX_SIZE:100MB}
max-history: ${LOG_FILE_MAX_HISTORY:7}
# OpenAPI 설정 (개발환경)
springdoc:
api-docs:
enabled: true
path: /v3/api-docs
swagger-ui:
enabled: true
path: /swagger-ui.html
try-it-out-enabled: true # 개발환경에서 Try it out 활성화
urls:
- name: Auth Service (Dev)
url: http://localhost:8081/v3/api-docs
- name: Bill Service (Dev)
url: http://localhost:8082/v3/api-docs
- name: Product Service (Dev)
url: http://localhost:8083/v3/api-docs
- name: KOS Mock Service (Dev)
url: http://localhost:8084/v3/api-docs
# CORS 설정 (개발환경 - 더 관대한 설정) - 이미 위에서 설정됨
# 개발환경 특성 설정
debug: false
trace: false
# 애플리케이션 정보 (개발환경)
info:
app:
environment: development
debug-mode: enabled
hot-reload: enabled
com.unicorn.phonebill.gateway: DEBUG
org.springframework.cloud.gateway: DEBUG
org.springframework.data.redis: DEBUG
org.springframework.web.reactive: DEBUG
reactor.netty.http.client: DEBUG
io.netty.handler.ssl: DEBUG
root: DEBUG
@@ -1,219 +1,9 @@
# API Gateway 운영 환경 설정
server:
port: 8080
netty:
connection-timeout: 20s
idle-timeout: 30s
compression:
enabled: true
mime-types: application/json,application/xml,text/html,text/xml,text/plain
http2:
enabled: true
spring:
profiles:
active: prod
# Redis 설정 (운영용) - 현재 사용하지 않음
# data:
# redis:
# host: ${REDIS_HOST:redis-cluster.unicorn.com}
# port: ${REDIS_PORT:6379}
# database: ${REDIS_DATABASE:0}
# password: ${REDIS_PASSWORD}
# timeout: 2000ms
# ssl: true # 운영환경에서는 SSL 사용
# lettuce:
# pool:
# max-active: 20
# max-wait: 2000ms
# max-idle: 10
# min-idle: 5
# cluster:
# refresh:
# adaptive: true
# period: 30s
# Cloud Gateway 운영환경 설정
cloud:
gateway:
default-filters:
# - name: RequestRateLimiter # Redis 사용하지 않으므로 주석처리
# args:
# redis-rate-limiter.replenishRate: 500 # 운영환경 적정 한도
# redis-rate-limiter.burstCapacity: 1000
# key-resolver: "#{@userKeyResolver}"
- name: RequestSize
args:
maxSize: 5MB # 요청 크기 제한
globalcors:
cors-configurations:
'[/**]':
allowed-origins:
- "https://app.phonebill.com"
- "https://admin.phonebill.com"
- "https://*.unicorn.com"
allowed-methods:
- GET
- POST
- PUT
- DELETE
allowed-headers:
- Authorization
- Content-Type
- X-Requested-With
allow-credentials: true
max-age: 3600
# JWT 설정 (운영용 - 보안 강화)
app:
jwt:
secret: ${JWT_SECRET} # 환경변수에서 주입 (필수)
access-token-validity-in-seconds: 1800 # 30분 (보안 강화)
refresh-token-validity-in-seconds: 86400 # 24시간
# Circuit Breaker 설정 (운영환경 - 엄격한 설정)
resilience4j:
circuitbreaker:
instances:
auth-service-cb:
failure-rate-threshold: 50
slow-call-rate-threshold: 60
slow-call-duration-threshold: 3s
wait-duration-in-open-state: 30s
sliding-window-size: 100
minimum-number-of-calls: 20
permitted-number-of-calls-in-half-open-state: 10
bill-service-cb:
failure-rate-threshold: 50
slow-call-rate-threshold: 60
slow-call-duration-threshold: 5s
wait-duration-in-open-state: 30s
sliding-window-size: 100
minimum-number-of-calls: 20
product-service-cb:
failure-rate-threshold: 50
slow-call-rate-threshold: 60
slow-call-duration-threshold: 5s
wait-duration-in-open-state: 30s
sliding-window-size: 100
minimum-number-of-calls: 20
kos-mock-cb:
failure-rate-threshold: 60
slow-call-rate-threshold: 70
slow-call-duration-threshold: 10s
wait-duration-in-open-state: 60s
sliding-window-size: 50
minimum-number-of-calls: 10
retry:
instances:
default:
max-attempts: 3
wait-duration: 1s
exponential-backoff-multiplier: 2
retry-exceptions:
- java.net.ConnectException
- java.net.SocketTimeoutException
- org.springframework.web.client.ResourceAccessException
# Actuator 설정 (운영환경 - 보안 강화)
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,gateway # 필요한 것만 노출
base-path: /actuator
endpoint:
health:
show-details: never # 운영환경에서는 상세 정보 숨김
show-components: never
gateway:
enabled: true
shutdown:
enabled: false # 운영환경에서는 비활성화
health:
redis:
enabled: true
circuitbreakers:
enabled: true
info:
env:
enabled: false # 환경 정보 숨김
java:
enabled: true
build:
enabled: true
metrics:
export:
prometheus:
enabled: true
# 로깅 설정 (운영환경 - 성능 고려)
logging:
level:
com.unicorn.phonebill.gateway: INFO
org.springframework.cloud.gateway: WARN
reactor.netty: WARN
io.netty: WARN
org.springframework.cloud.gateway: INFO
org.springframework.data.redis: INFO
org.springframework.web.reactive: WARN
reactor.netty.http.client: WARN
io.netty.handler.ssl: WARN
root: WARN
file:
name: /var/log/api-gateway/api-gateway.log
max-size: 500MB
max-history: 30
pattern:
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{36} - %msg%n"
loggers:
org.springframework.security: WARN
org.springframework.web: WARN
# OpenAPI 설정 (운영환경 - 비활성화)
springdoc:
api-docs:
enabled: false # 운영환경에서는 비활성화
swagger-ui:
enabled: false # 운영환경에서는 비활성화
# CORS 설정은 위의 spring.cloud.gateway 섹션에서 설정됨
# 보안 설정
security:
headers:
frame:
deny: true
content-type:
nosniff: true
xss:
protection: true
# JVM 튜닝 (운영환경)
jvm:
heap:
initial: 512m
maximum: 1024m
gc:
algorithm: G1GC
options:
- "-server"
- "-XX:+UseG1GC"
- "-XX:G1HeapRegionSize=16m"
- "-XX:+UseStringDeduplication"
- "-XX:+OptimizeStringConcat"
- "-XX:+UnlockExperimentalVMOptions"
- "-XX:+UseJVMCICompiler"
# 모니터링 및 트레이싱
tracing:
enabled: true
sampling:
probability: 0.1 # 10% 샘플링
zipkin:
base-url: ${ZIPKIN_BASE_URL:http://zipkin.monitoring.unicorn.com:9411}
# 애플리케이션 정보 (운영환경)
info:
app:
environment: production
security-level: high
monitoring: enabled
+13 -16
View File
@@ -7,15 +7,15 @@ server:
connection-timeout: ${SERVER_NETTY_CONNECTION_TIMEOUT:30s}
idle-timeout: ${SERVER_NETTY_IDLE_TIMEOUT:60s}
http2:
enabled: ${SERVER_HTTP2_ENABLED:true}
enabled: true
spring:
application:
name: api-gateway
profiles:
active: dev
active: ${SPRING_PROFILES_ACTIVE:dev}
# Spring Cloud Gateway 설정
cloud:
gateway:
@@ -59,12 +59,15 @@ spring:
deserialization:
fail-on-unknown-properties: false
# JWT 설정
app:
jwt:
secret: ${JWT_SECRET:phonebill-api-gateway-jwt-secret-key-256-bit-minimum-length-required}
access-token-validity-in-seconds: 1800 # 30분
refresh-token-validity-in-seconds: 86400 # 24시간
# CORS
cors:
allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost:3000}
# JWT 토큰 설정
jwt:
secret: ${JWT_SECRET:}
access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY:1800}
refresh-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY:86400}
# 서비스 URL 설정
services:
@@ -142,14 +145,8 @@ management:
# 로깅 설정
logging:
level:
com.unicorn.phonebill.gateway: ${LOG_LEVEL_GATEWAY:INFO}
org.springframework.cloud.gateway: ${LOG_LEVEL_SPRING_CLOUD_GATEWAY:DEBUG}
reactor.netty: ${LOG_LEVEL_REACTOR_NETTY:INFO}
io.netty: ${LOG_LEVEL_IO_NETTY:WARN}
root: ${LOG_LEVEL_ROOT:INFO}
file:
name: ${LOG_FILE:logs/api-gateway.log}
name: logs/api-gateway.log
logback:
rollingpolicy:
max-file-size: 10MB