diff --git a/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/GatewayConfig.java b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/GatewayConfig.java index 8b5b554..6d1f5b7 100644 --- a/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/GatewayConfig.java +++ b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/GatewayConfig.java @@ -18,10 +18,10 @@ import java.util.Arrays; * 마이크로서비스별 라우팅 규칙과 CORS 정책을 정의합니다. * * 라우팅 구성: - * - /auth/** -> auth-service (인증 서비스) - * - /bills/** -> bill-service (요금조회 서비스) - * - /products/** -> product-service (상품변경 서비스) - * - /kos/** -> kos-mock (KOS 목업 서비스) + * - /api/auth/** -> user-service (인증 서비스) + * - /api/bills/** -> bill-service (요금조회 서비스) + * - /api/products/** -> product-service (상품변경 서비스) + * - /api/kos/** -> kos-mock (KOS 목업 서비스) * * @author 이개발(백엔더) * @version 1.0.0 @@ -75,8 +75,9 @@ public class GatewayConfig { // Bill-Inquiry Service 라우팅 (인증 필요) .route("bill-service", r -> r - .path("/api/v1/bills/**") + .path("/api/bills/**") .filters(f -> f + .rewritePath("/api/bills/(?.*)", "/api/v1/bills/${segment}") .filter(jwtAuthFilter.apply(new JwtAuthenticationGatewayFilterFactory.Config())) .circuitBreaker(cb -> cb .setName("bill-service-cb") @@ -89,8 +90,9 @@ public class GatewayConfig { // Product-Change Service 라우팅 (인증 필요) .route("product-service", r -> r - .path("/products/**") + .path("/api/products/**") .filters(f -> f + .rewritePath("/api/products/(?.*)", "/products/${segment}") .filter(jwtAuthFilter.apply(new JwtAuthenticationGatewayFilterFactory.Config())) .circuitBreaker(cb -> cb .setName("product-service-cb") @@ -100,6 +102,20 @@ public class GatewayConfig { .setBackoff(java.time.Duration.ofSeconds(2), java.time.Duration.ofSeconds(10), 2, true)) ) .uri("lb://product-service")) + + // KOS Mock Service 라우팅 (인증 불필요 - 목업용) + .route("kos-mock-service", r -> r + .path("/api/kos/**") + .filters(f -> f + .rewritePath("/api/kos/(?.*)", "/kos/${segment}") + .circuitBreaker(cb -> cb + .setName("kos-mock-cb") + .setFallbackUri("forward:/fallback/kos")) + .retry(retry -> retry + .setRetries(2) + .setBackoff(java.time.Duration.ofSeconds(1), java.time.Duration.ofSeconds(5), 2, true)) + ) + .uri("lb://kos-mock")) // 주의: Gateway 자체 엔드포인트는 라우팅하지 않음 // Health Check와 Swagger UI는 Spring Boot에서 직접 제공 diff --git a/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/SwaggerConfig.java b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/SwaggerConfig.java index 3c142f2..2fd1ba5 100644 --- a/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/SwaggerConfig.java +++ b/api-gateway/src/main/java/com/unicorn/phonebill/gateway/config/SwaggerConfig.java @@ -78,14 +78,19 @@ public class SwaggerConfig { public GroupedOpenApi gatewayApi() { return GroupedOpenApi.builder() .group("gateway") - .displayName("API Gateway") + .displayName("🌐 API Gateway") .pathsToMatch("/health/**", "/actuator/**") .addOpenApiCustomizer(openApi -> { openApi.info(new io.swagger.v3.oas.models.info.Info() .title("PhoneBill API Gateway") .version("1.0.0") .description("통신요금 관리 서비스 API Gateway\n\n" + - "이 문서는 API Gateway의 헬스체크 및 관리 기능을 설명합니다.") + "이 문서는 API Gateway의 헬스체크 및 관리 기능을 설명합니다.\n\n" + + "**주요 기능:**\n" + + "- 마이크로서비스 라우팅\n" + + "- JWT 인증/인가\n" + + "- Circuit Breaker\n" + + "- CORS 처리") ); // JWT 보안 스키마 추가 @@ -106,6 +111,106 @@ public class SwaggerConfig { }) .build(); } + + /** + * User Service OpenAPI 그룹 정의 + * + * @return GroupedOpenApi + */ + @Bean + public GroupedOpenApi userServiceApi() { + return GroupedOpenApi.builder() + .group("user-service") + .displayName("📱 User Service") + .pathsToMatch("/api/auth/**") + .addOpenApiCustomizer(openApi -> { + openApi.info(new io.swagger.v3.oas.models.info.Info() + .title("User Service API") + .version("1.0.0") + .description("사용자 인증 및 관리 서비스\n\n" + + "**주요 기능:**\n" + + "- 사용자 로그인/로그아웃\n" + + "- JWT 토큰 발급/갱신\n" + + "- 사용자 정보 관리") + ); + }) + .build(); + } + + /** + * Bill Service OpenAPI 그룹 정의 + * + * @return GroupedOpenApi + */ + @Bean + public GroupedOpenApi billServiceApi() { + return GroupedOpenApi.builder() + .group("bill-service") + .displayName("💰 Bill Service") + .pathsToMatch("/api/bills/**") + .addOpenApiCustomizer(openApi -> { + openApi.info(new io.swagger.v3.oas.models.info.Info() + .title("Bill Inquiry Service API") + .version("1.0.0") + .description("통신요금 조회 서비스\n\n" + + "**주요 기능:**\n" + + "- 월별 요금 조회\n" + + "- 요금 상세 내역\n" + + "- 조회 이력 관리") + ); + }) + .build(); + } + + /** + * Product Service OpenAPI 그룹 정의 + * + * @return GroupedOpenApi + */ + @Bean + public GroupedOpenApi productServiceApi() { + return GroupedOpenApi.builder() + .group("product-service") + .displayName("📦 Product Service") + .pathsToMatch("/api/products/**") + .addOpenApiCustomizer(openApi -> { + openApi.info(new io.swagger.v3.oas.models.info.Info() + .title("Product Change Service API") + .version("1.0.0") + .description("통신상품 변경 서비스\n\n" + + "**주요 기능:**\n" + + "- 상품 목록 조회\n" + + "- 상품 변경 신청\n" + + "- 변경 이력 관리") + ); + }) + .build(); + } + + /** + * KOS Mock Service OpenAPI 그룹 정의 + * + * @return GroupedOpenApi + */ + @Bean + public GroupedOpenApi kosMockServiceApi() { + return GroupedOpenApi.builder() + .group("kos-mock") + .displayName("🔧 KOS Mock Service") + .pathsToMatch("/api/kos/**") + .addOpenApiCustomizer(openApi -> { + openApi.info(new io.swagger.v3.oas.models.info.Info() + .title("KOS Mock Service API") + .version("1.0.0") + .description("KOS 외부 연동 목업 서비스\n\n" + + "**주요 기능:**\n" + + "- 요금 조회 목업\n" + + "- 상품 변경 목업\n" + + "- 테스트 데이터 제공") + ); + }) + .build(); + } /** * Swagger UI 리다이렉트 라우터 @@ -153,6 +258,7 @@ public class SwaggerConfig { * API 문서 프록시 * * 각 마이크로서비스의 OpenAPI 문서를 프록시하여 제공합니다. + * Gateway 경로로 서버 정보를 수정하여 반환합니다. * * @param apiDocsUrl API 문서 URL * @return ServerResponse @@ -164,6 +270,7 @@ public class SwaggerConfig { .onStatus(status -> status.isError(), clientResponse -> Mono.error(new RuntimeException("Service unavailable"))) .bodyToMono(String.class) + .map(this::modifyOpenApiServers) .onErrorReturn(getDefaultApiDoc(apiDocsUrl)) .flatMap(body -> ServerResponse.ok() @@ -172,6 +279,67 @@ public class SwaggerConfig { ); } + /** + * OpenAPI 문서의 서버 정보를 Gateway 경로로 수정 + * + * @param openApiJson 원본 OpenAPI JSON + * @return 수정된 OpenAPI JSON + */ + private String modifyOpenApiServers(String openApiJson) { + try { + // JSON 파싱을 위한 간단한 문자열 치환 + // 실제 프로덕션에서는 Jackson ObjectMapper 사용 권장 + String modified = openApiJson; + + // 서버 정보를 Gateway 기반으로 수정 + if (openApiJson.contains("user-service") || openApiJson.contains("8081")) { + modified = addGatewayServerInfo(modified, "/api/auth", "User Service"); + } else if (openApiJson.contains("bill-service") || openApiJson.contains("8082")) { + modified = addGatewayServerInfo(modified, "/api/bills", "Bill Service"); + } else if (openApiJson.contains("product-service") || openApiJson.contains("8083")) { + modified = addGatewayServerInfo(modified, "/api/products", "Product Service"); + } else if (openApiJson.contains("kos-mock") || openApiJson.contains("8084")) { + modified = addGatewayServerInfo(modified, "/api/kos", "KOS Mock Service"); + } + + return modified; + } catch (Exception e) { + // JSON 수정 실패 시 원본 반환 + return openApiJson; + } + } + + /** + * OpenAPI JSON에 Gateway 서버 정보 추가 + * + * @param openApiJson 원본 OpenAPI JSON + * @param basePath Gateway 기반 경로 + * @param serviceName 서비스명 + * @return 수정된 OpenAPI JSON + */ + private String addGatewayServerInfo(String openApiJson, String basePath, String serviceName) { + // servers 섹션을 Gateway 정보로 교체 + String serverInfo = "\"servers\": [" + + " {" + + " \"url\": \"" + basePath + "\"," + + " \"description\": \"" + serviceName + " via API Gateway\"" + + " }" + + " ],"; + + // 기존 servers 정보가 있으면 교체, 없으면 info 다음에 추가 + if (openApiJson.contains("\"servers\"")) { + return openApiJson.replaceFirst( + "\"servers\":\\s*\\[[^\\]]*\\],?", + serverInfo + ); + } else { + return openApiJson.replaceFirst( + "(\"info\":\\s*\\{[^}]*\\},?)", + "$1\n " + serverInfo + ); + } + } + /** * Gateway API 문서 생성 * diff --git a/api-gateway/src/main/resources/application.yml b/api-gateway/src/main/resources/application.yml index fffff21..4175ae7 100644 --- a/api-gateway/src/main/resources/application.yml +++ b/api-gateway/src/main/resources/application.yml @@ -181,18 +181,23 @@ springdoc: # 큰 헤더 처리를 위한 설정 csrf: enabled: false - # 서비스별 URL 등록 + # 서비스별 URL 등록 (Gateway 통합 순서) urls: - - name: "Gateway API" - url: "/v3/api-docs/gateway" - - name: "User Service" + - name: "📱 User Service (인증)" url: "/v3/api-docs/user" - - name: "Bill Service" + display-name: "User Service API" + - name: "💰 Bill Service (요금조회)" url: "/v3/api-docs/bill" - - name: "Product Service" + display-name: "Bill Inquiry API" + - name: "📦 Product Service (상품변경)" url: "/v3/api-docs/product" - - name: "KOS Mock Service" + display-name: "Product Change API" + - name: "🔧 KOS Mock Service (외부연동)" url: "/v3/api-docs/kos" + display-name: "KOS Mock API" + - name: "🌐 Gateway API (게이트웨이)" + url: "/v3/api-docs/gateway" + display-name: "API Gateway" use-management-port: false show-actuator: false