API Gateway Swagger 통합 기능 구현 완료

주요 변경사항:
- Gateway 라우팅 경로 통일화 (/api/{service}/**)
  * user-service: /api/auth/**
  * bill-service: /api/bills/** (내부적으로 /api/v1/bills/**로 변환)
  * product-service: /api/products/** (내부적으로 /products/**로 변환)
  * kos-mock: /api/kos/** 추가

- OpenAPI 서버 정보 동적 수정
  * 각 서비스의 OpenAPI JSON에 Gateway 경로 정보 주입
  * "Try it out" 기능이 Gateway를 통해 정상 동작하도록 개선

- Swagger UI 설정 개선
  * 서비스별 이모지와 한글 설명 추가
  * 표시 순서 최적화 (User → Bill → Product → KOS → Gateway)

- 서비스별 GroupedOpenApi 빈 추가
  * 각 서비스별 상세 정보와 기능 설명 포함
  * 일관된 API 문서 구조 제공

이제 API Gateway의 Swagger UI에서 모든 마이크로서비스 API가 통합되어 표시되며,
실제 테스트도 가능합니다.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
hiondal 2025-09-10 10:48:59 +09:00
parent 2a719048f8
commit 2df9b7d14f
3 changed files with 204 additions and 15 deletions

View File

@ -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/(?<segment>.*)", "/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/(?<segment>.*)", "/products/${segment}")
.filter(jwtAuthFilter.apply(new JwtAuthenticationGatewayFilterFactory.Config()))
.circuitBreaker(cb -> cb
.setName("product-service-cb")
@ -101,6 +103,20 @@ public class GatewayConfig {
)
.uri("lb://product-service"))
// KOS Mock Service 라우팅 (인증 불필요 - 목업용)
.route("kos-mock-service", r -> r
.path("/api/kos/**")
.filters(f -> f
.rewritePath("/api/kos/(?<segment>.*)", "/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에서 직접 제공

View File

@ -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 보안 스키마 추가
@ -107,6 +112,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 문서 생성
*

View File

@ -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