API Gateway Swagger 관련 코드 완전 제거

변경사항:
- SwaggerConfig.java 클래스 완전 삭제
- application.yml에서 springdoc 설정 모두 제거
- build.gradle에서 springdoc-openapi-starter-webflux-ui 의존성 제거

API Gateway는 단순 라우팅 역할만 담당하므로 Swagger 불필요
각 마이크로서비스별로 개별 Swagger UI 사용하는 구조로 변경:
- User Service: http://localhost:8081/swagger-ui.html
- Bill Service: http://localhost:8082/swagger-ui.html
- Product Service: http://localhost:8083/swagger-ui.html
- KOS Mock: http://localhost:8084/swagger-ui.html

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
hiondal 2025-09-10 11:00:56 +09:00
parent 2df9b7d14f
commit d5791aa159
3 changed files with 0 additions and 478 deletions

View File

@ -1,442 +0,0 @@
package com.unicorn.phonebill.gateway.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springdoc.core.models.GroupedOpenApi;
import org.springdoc.core.properties.SwaggerUiConfigParameters;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpStatus;
import java.net.URI;
/**
* Swagger 통합 문서화 설정
*
* API Gateway를 통해 모든 마이크로서비스의 OpenAPI 문서를 통합하여 제공합니다.
* 개발 환경에서만 활성화되며, 서비스별 API 문서를 중앙집중식으로 관리합니다.
*
* 주요 기능:
* - 마이크로서비스별 OpenAPI 문서 통합
* - Swagger UI 커스터마이징
* - JWT 인증 정보 포함
* - 환경별 설정 (개발환경에서만 활성화)
*
* @author 이개발(백엔더)
* @version 1.0.0
* @since 2025-01-08
*/
@Configuration
@Profile("!prod") // 운영환경에서는 비활성화
public class SwaggerConfig {
@Value("${services.user-service.url:http://localhost:8081}")
private String userServiceUrl;
@Value("${services.bill-service.url:http://localhost:8082}")
private String billServiceUrl;
@Value("${services.product-service.url:http://localhost:8083}")
private String productServiceUrl;
@Value("${services.kos-mock.url:http://localhost:8084}")
private String kosMockUrl;
private final WebClient webClient;
public SwaggerConfig() {
this.webClient = WebClient.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024))
.build();
}
/**
* Swagger UI 설정 파라미터
*
* SpringDoc WebFlux에서는 기본 설정을 사용하고 필요시 커스터마이징합니다.
*
* @return SwaggerUiConfigParameters
*/
@Bean
public SwaggerUiConfigParameters swaggerUiConfigParameters() {
return new SwaggerUiConfigParameters(
new org.springdoc.core.properties.SwaggerUiConfigProperties()
);
}
/**
* API Gateway OpenAPI 그룹 정의
*
* @return GroupedOpenApi
*/
@Bean
public GroupedOpenApi gatewayApi() {
return GroupedOpenApi.builder()
.group("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의 헬스체크 및 관리 기능을 설명합니다.\n\n" +
"**주요 기능:**\n" +
"- 마이크로서비스 라우팅\n" +
"- JWT 인증/인가\n" +
"- Circuit Breaker\n" +
"- CORS 처리")
);
// JWT 보안 스키마 추가
openApi.addSecurityItem(
new io.swagger.v3.oas.models.security.SecurityRequirement()
.addList("bearerAuth")
);
openApi.getComponents()
.addSecuritySchemes("bearerAuth",
new io.swagger.v3.oas.models.security.SecurityScheme()
.type(io.swagger.v3.oas.models.security.SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("JWT 토큰을 Authorization 헤더에 포함시켜 주세요.\n" +
"형식: Authorization: Bearer {token}")
);
})
.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 리다이렉트 라우터
*
* @return RouterFunction
*/
@Bean
public RouterFunction<ServerResponse> swaggerRouterFunction() {
return RouterFunctions.route()
// 루트 경로에서 Swagger UI로 리다이렉트
.GET("/", request ->
ServerResponse.temporaryRedirect(URI.create("/swagger-ui.html")).build())
// docs 경로에서 Swagger UI로 리다이렉트
.GET("/docs", request ->
ServerResponse.temporaryRedirect(URI.create("/swagger-ui.html")).build())
// api-docs 경로에서 Swagger UI로 리다이렉트
.GET("/api-docs", request ->
ServerResponse.temporaryRedirect(URI.create("/swagger-ui.html")).build())
// Gateway API 문서 직접 제공
.GET("/v3/api-docs/gateway", request ->
ServerResponse.ok()
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.bodyValue(getGatewayApiDoc()))
// 서비스별 API 문서 프록시
.GET("/v3/api-docs/user", request ->
proxyApiDocs(userServiceUrl + "/v3/api-docs"))
.GET("/v3/api-docs/bill", request ->
proxyApiDocs(billServiceUrl + "/v3/api-docs"))
.GET("/v3/api-docs/product", request ->
proxyApiDocs(productServiceUrl + "/v3/api-docs"))
.GET("/v3/api-docs/kos", request ->
proxyApiDocs(kosMockUrl + "/v3/api-docs"))
.build();
}
/**
* API 문서 프록시
*
* 마이크로서비스의 OpenAPI 문서를 프록시하여 제공합니다.
* Gateway 경로로 서버 정보를 수정하여 반환합니다.
*
* @param apiDocsUrl API 문서 URL
* @return ServerResponse
*/
private Mono<ServerResponse> proxyApiDocs(String apiDocsUrl) {
return webClient.get()
.uri(apiDocsUrl)
.retrieve()
.onStatus(status -> status.isError(), clientResponse ->
Mono.error(new RuntimeException("Service unavailable")))
.bodyToMono(String.class)
.map(this::modifyOpenApiServers)
.onErrorReturn(getDefaultApiDoc(apiDocsUrl))
.flatMap(body ->
ServerResponse.ok()
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.bodyValue(body)
);
}
/**
* 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 문서 생성
*
* Gateway 자체의 OpenAPI 문서를 생성합니다.
*
* @return Gateway API 문서 JSON
*/
private String getGatewayApiDoc() {
return "{\n" +
" \"openapi\": \"3.0.1\",\n" +
" \"info\": {\n" +
" \"title\": \"PhoneBill API Gateway\",\n" +
" \"version\": \"1.0.0\",\n" +
" \"description\": \"통신요금 관리 서비스 API Gateway\\n\\n" +
"이 문서는 API Gateway의 헬스체크 및 관리 기능을 설명합니다.\"\n" +
" },\n" +
" \"paths\": {\n" +
" \"/health\": {\n" +
" \"get\": {\n" +
" \"summary\": \"헬스 체크\",\n" +
" \"description\": \"API Gateway 서비스 상태를 확인합니다.\",\n" +
" \"responses\": {\n" +
" \"200\": {\n" +
" \"description\": \"서비스 정상\",\n" +
" \"content\": {\n" +
" \"application/json\": {\n" +
" \"schema\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"status\": { \"type\": \"string\" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"/actuator/health\": {\n" +
" \"get\": {\n" +
" \"summary\": \"Actuator 헬스 체크\",\n" +
" \"description\": \"Spring Boot Actuator 헬스 체크 엔드포인트\",\n" +
" \"responses\": {\n" +
" \"200\": {\n" +
" \"description\": \"헬스 체크 결과\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"components\": {\n" +
" \"securitySchemes\": {\n" +
" \"bearerAuth\": {\n" +
" \"type\": \"http\",\n" +
" \"scheme\": \"bearer\",\n" +
" \"bearerFormat\": \"JWT\",\n" +
" \"description\": \"JWT 토큰을 Authorization 헤더에 포함시켜 주세요.\\nFormat: Authorization: Bearer {token}\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
}
/**
* 기본 API 문서 생성
*
* 서비스에 접근할 없을 반환할 기본 문서를 생성합니다.
*
* @param apiDocsUrl API 문서 URL
* @return 기본 API 문서 JSON
*/
private String getDefaultApiDoc(String apiDocsUrl) {
String serviceName = extractServiceName(apiDocsUrl);
return "{\n" +
" \"openapi\": \"3.0.1\",\n" +
" \"info\": {\n" +
" \"title\": \"" + serviceName + " API\",\n" +
" \"version\": \"1.0.0\",\n" +
" \"description\": \"" + serviceName + " 마이크로서비스 API 문서\\n\\n" +
"서비스가 시작되지 않았거나 연결할 수 없습니다.\"\n" +
" },\n" +
" \"paths\": {},\n" +
" \"components\": {}\n" +
"}";
}
/**
* URL에서 서비스명 추출
*
* @param apiDocsUrl API 문서 URL
* @return 서비스명
*/
private String extractServiceName(String apiDocsUrl) {
if (apiDocsUrl.contains("8081")) return "User Service";
if (apiDocsUrl.contains("8082")) return "Bill Service";
if (apiDocsUrl.contains("8083")) return "Product Service";
if (apiDocsUrl.contains("8084")) return "KOS Mock Service";
return "Unknown Service";
}
}

View File

@ -166,40 +166,6 @@ logging:
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{36} - %msg%n"
console: "%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan([%X{traceId:-},%X{spanId:-}]) %logger{36} - %msg%n"
# OpenAPI 설정
springdoc:
api-docs:
enabled: true
path: /v3/api-docs
swagger-ui:
enabled: true
path: /swagger-ui.html
disable-swagger-default-url: true
# HTTP 431 오류 방지를 위한 설정
config-url: /v3/api-docs/swagger-config
use-root-path: true
# 큰 헤더 처리를 위한 설정
csrf:
enabled: false
# 서비스별 URL 등록 (Gateway 통합 순서)
urls:
- name: "📱 User Service (인증)"
url: "/v3/api-docs/user"
display-name: "User Service API"
- name: "💰 Bill Service (요금조회)"
url: "/v3/api-docs/bill"
display-name: "Bill Inquiry API"
- name: "📦 Product Service (상품변경)"
url: "/v3/api-docs/product"
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
# 애플리케이션 정보
info:

View File

@ -116,8 +116,6 @@ configure(subprojects.findAll { it.name == 'api-gateway' }) {
implementation "io.jsonwebtoken:jjwt-impl:${jjwtVersion}"
implementation "io.jsonwebtoken:jjwt-jackson:${jjwtVersion}"
// API Documentation for WebFlux
implementation "org.springdoc:springdoc-openapi-starter-webflux-ui:${springdocVersion}"
// Testing (WebFlux specific)
testImplementation 'org.springframework.boot:spring-boot-starter-test'