Merge pull request 'feat(swagger): Swagger 응답 스키마 자동 생성 및 API 문서 개선 (#14)' (#15) from feature/ISSUE-14-swagger-response-example into develop
This commit is contained in:
커밋
45d9561cb1
@ -4,6 +4,17 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### 추가
|
||||
- swagger.json 기반 Swagger 응답 스키마 DTO 자동 생성 (#14)
|
||||
- POST 요청 시 requestBody 스키마 자동 생성
|
||||
- POST BODY 파라미터 미등록 시 JsonNode body 자동 추가
|
||||
|
||||
### 변경
|
||||
- Swagger API 그룹을 WebClient 종류별 3개로 분리 (Ships/AIS/Web Services)
|
||||
- Swagger/코드 생성기에서 bypass 용어 제거
|
||||
- Screening Guide API를 운영환경에서도 노출
|
||||
- API 카탈로그 Swagger 링크를 WebClient 종류별 그룹으로 연결
|
||||
|
||||
## [2026-04-08.2]
|
||||
|
||||
### 추가
|
||||
|
||||
@ -39,6 +39,7 @@ export interface BypassConfigResponse {
|
||||
description: string;
|
||||
generated: boolean;
|
||||
generatedAt: string | null;
|
||||
responseSchemaClass: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
params: BypassParamDto[];
|
||||
|
||||
@ -14,6 +14,7 @@ interface BypassConfig {
|
||||
domainName: string;
|
||||
endpointName: string;
|
||||
displayName: string;
|
||||
webclientBean: string;
|
||||
httpMethod: string;
|
||||
externalPath: string;
|
||||
description: string;
|
||||
@ -36,15 +37,18 @@ const METHOD_COLORS: Record<string, string> = {
|
||||
DELETE: 'bg-red-100 text-red-700',
|
||||
};
|
||||
|
||||
const SWAGGER_BASE = '/snp-global/swagger-ui/index.html?urls.primaryName=3.%20Bypass%20API';
|
||||
const SWAGGER_GROUP_MAP: Record<string, string> = {
|
||||
maritimeApiWebClient: '3-1.%20Ships%20API',
|
||||
maritimeAisApiWebClient: '3-2.%20AIS%20API',
|
||||
maritimeServiceApiWebClient: '3-3.%20Web%20Services%20API',
|
||||
};
|
||||
|
||||
function buildSwaggerDeepLink(config: BypassConfig): string {
|
||||
// Swagger UI deep link: #/{Tag}/{operationId}
|
||||
// Tag = domainName 첫글자 대문자 (예: compliance → Compliance)
|
||||
// operationId = get{EndpointName}Data (SpringDoc 기본 패턴)
|
||||
const group = SWAGGER_GROUP_MAP[config.webclientBean] ?? '3-1.%20Ships%20API';
|
||||
const base = `/snp-global/swagger-ui/index.html?urls.primaryName=${group}`;
|
||||
const tag = config.domainName.charAt(0).toUpperCase() + config.domainName.slice(1);
|
||||
const operationId = `get${config.endpointName}Data`;
|
||||
return `${SWAGGER_BASE}#/${tag}/${operationId}`;
|
||||
return `${base}#/${tag}/${operationId}`;
|
||||
}
|
||||
|
||||
export default function BypassCatalog() {
|
||||
@ -98,7 +102,7 @@ export default function BypassCatalog() {
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href={SWAGGER_BASE}
|
||||
href="/snp-global/swagger-ui/index.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-wing-accent hover:bg-wing-accent/80 rounded-lg transition-colors no-underline"
|
||||
|
||||
@ -5,6 +5,7 @@ import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import io.swagger.v3.oas.models.tags.Tag;
|
||||
import org.springdoc.core.models.GroupedOpenApi;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@ -12,6 +13,8 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Swagger/OpenAPI 3.0 설정
|
||||
@ -41,17 +44,16 @@ public class SwaggerConfig {
|
||||
@ConditionalOnProperty(name = "app.environment", havingValue = "dev", matchIfMissing = true)
|
||||
public GroupedOpenApi bypassConfigApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("2. Bypass Config")
|
||||
.group("2. API Config")
|
||||
.pathsToMatch("/api/bypass-config/**")
|
||||
.addOpenApiCustomizer(openApi -> openApi.info(new Info()
|
||||
.title("Bypass Config API")
|
||||
.description("Bypass API 설정 및 코드 생성 관리 API")
|
||||
.title("API Config")
|
||||
.description("API 설정 및 코드 생성 관리")
|
||||
.version("v1.0.0")))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "app.environment", havingValue = "dev", matchIfMissing = true)
|
||||
public GroupedOpenApi screeningGuideApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("4. Screening Guide")
|
||||
@ -64,16 +66,59 @@ public class SwaggerConfig {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi bypassApi() {
|
||||
public GroupedOpenApi shipsBypassApi() {
|
||||
return createBypassApiGroup("3-1. Ships API", "[Ship API]",
|
||||
"S&P Global Ships/Events/PSC 데이터를 제공합니다.");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi aisBypassApi() {
|
||||
return createBypassApiGroup("3-2. AIS API", "[AIS API]",
|
||||
"S&P Global AIS(선박위치추적) 데이터를 제공합니다.");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi servicesBypassApi() {
|
||||
return createBypassApiGroup("3-3. Web Services API", "[Service API]",
|
||||
"S&P Global Maritime Web Services 데이터를 제공합니다.");
|
||||
}
|
||||
|
||||
private GroupedOpenApi createBypassApiGroup(String group, String tagPrefix, String description) {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("3. Bypass API")
|
||||
.group(group)
|
||||
.pathsToMatch("/api/**")
|
||||
.pathsToExclude("/api/bypass-config/**", "/api/screening-guide/**", "/api/bypass-account/**")
|
||||
.addOpenApiCustomizer(openApi -> {
|
||||
openApi.info(new Info()
|
||||
.title("Bypass API")
|
||||
.description("S&P Global 선박/해운 데이터를 제공합니다.")
|
||||
.title(group)
|
||||
.description(description)
|
||||
.version("v1.0.0"));
|
||||
|
||||
// Tag description에 prefix가 포함된 것만 필터링
|
||||
if (openApi.getTags() != null) {
|
||||
Set<String> matchingTags = openApi.getTags().stream()
|
||||
.filter(tag -> tag.getDescription() != null
|
||||
&& tag.getDescription().startsWith(tagPrefix))
|
||||
.map(Tag::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (openApi.getPaths() != null) {
|
||||
if (matchingTags.isEmpty()) {
|
||||
// 매칭 태그가 없으면 모든 경로 제거
|
||||
openApi.getPaths().clear();
|
||||
} else {
|
||||
openApi.getPaths().entrySet().removeIf(entry -> {
|
||||
var pathItem = entry.getValue();
|
||||
return pathItem.readOperations().stream()
|
||||
.noneMatch(op -> op.getTags() != null
|
||||
&& op.getTags().stream().anyMatch(matchingTags::contains));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 매칭되지 않는 태그 제거
|
||||
openApi.getTags().removeIf(tag -> !matchingTags.contains(tag.getName()));
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
@ -82,11 +127,11 @@ public class SwaggerConfig {
|
||||
@ConditionalOnProperty(name = "app.environment", havingValue = "dev", matchIfMissing = true)
|
||||
public GroupedOpenApi bypassAccountApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("5. Bypass Account")
|
||||
.group("5. Account Management")
|
||||
.pathsToMatch("/api/bypass-account/**")
|
||||
.addOpenApiCustomizer(openApi -> openApi.info(new Info()
|
||||
.title("Bypass Account Management API")
|
||||
.description("Bypass API 계정 및 신청 관리 API")
|
||||
.title("Account Management API")
|
||||
.description("API 계정 및 신청 관리")
|
||||
.version("v1.0.0")))
|
||||
.build();
|
||||
}
|
||||
@ -123,10 +168,10 @@ public class SwaggerConfig {
|
||||
S&P Global Maritime 데이터 서비스 REST API 문서입니다.
|
||||
|
||||
### 제공 API
|
||||
- **Bypass API**: S&P Global 선박/해운 데이터 조회
|
||||
- **Bypass Config API**: Bypass API 설정 관리
|
||||
- **Ships / AIS / Web Services API**: S&P Global 선박/해운 데이터 조회
|
||||
- **API Config**: API 설정 관리
|
||||
- **Screening Guide API**: Risk & Compliance 스크리닝 가이드
|
||||
- **Bypass Account API**: API 계정 관리
|
||||
- **Account Management API**: API 계정 관리
|
||||
|
||||
### 버전 정보
|
||||
- API Version: v1.0.0
|
||||
|
||||
@ -34,6 +34,9 @@ public class BypassConfigRequest {
|
||||
/** 설명 */
|
||||
private String description;
|
||||
|
||||
/** Swagger 응답 스키마 DTO 클래스 (FQCN) */
|
||||
private String responseSchemaClass;
|
||||
|
||||
/** 파라미터 목록 */
|
||||
private List<BypassParamDto> params;
|
||||
}
|
||||
|
||||
@ -40,6 +40,9 @@ public class BypassConfigResponse {
|
||||
/** 설명 */
|
||||
private String description;
|
||||
|
||||
/** Swagger 응답 스키마 DTO 클래스 (FQCN) */
|
||||
private String responseSchemaClass;
|
||||
|
||||
/** 코드 생성 완료 여부 */
|
||||
private Boolean generated;
|
||||
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
package com.snp.batch.global.dto.bypass;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* S&P Global API 응답 스키마 (Swagger 문서용)
|
||||
* 이 클래스는 자동 생성되었습니다. 직접 수정하지 마세요.
|
||||
*/
|
||||
@Getter
|
||||
@Schema(description = "APSShipResult")
|
||||
public class APSShipResult {
|
||||
@Schema(description = "apsStatus", example = "")
|
||||
private String apsStatus;
|
||||
@Schema(description = "shipCount", example = "0")
|
||||
private Integer shipCount;
|
||||
@Schema(description = "apsShipDetail", example = "")
|
||||
private String apsShipDetail;
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
package com.snp.batch.global.dto.bypass;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* S&P Global API 응답 스키마 (Swagger 문서용)
|
||||
* 이 클래스는 자동 생성되었습니다. 직접 수정하지 마세요.
|
||||
*/
|
||||
@Getter
|
||||
@Schema(description = "ComplianceDetails")
|
||||
public class ComplianceDetails {
|
||||
@Schema(description = "lrimoShipNo", example = "")
|
||||
private String lrimoShipNo;
|
||||
@Schema(description = "dateAmended", example = "")
|
||||
private String dateAmended;
|
||||
@Schema(description = "legalOverall", example = "0")
|
||||
private Integer legalOverall;
|
||||
@Schema(description = "shipBESSanctionList", example = "0")
|
||||
private Integer shipBESSanctionList;
|
||||
@Schema(description = "shipDarkActivityIndicator", example = "0")
|
||||
private Integer shipDarkActivityIndicator;
|
||||
@Schema(description = "shipDetailsNoLongerMaintained", example = "0")
|
||||
private Integer shipDetailsNoLongerMaintained;
|
||||
@Schema(description = "shipEUSanctionList", example = "0")
|
||||
private Integer shipEUSanctionList;
|
||||
@Schema(description = "shipFlagDisputed", example = "0")
|
||||
private Integer shipFlagDisputed;
|
||||
@Schema(description = "shipFlagSanctionedCountry", example = "0")
|
||||
private Integer shipFlagSanctionedCountry;
|
||||
@Schema(description = "shipHistoricalFlagSanctionedCountry", example = "0")
|
||||
private Integer shipHistoricalFlagSanctionedCountry;
|
||||
@Schema(description = "shipOFACNonSDNSanctionList", example = "0")
|
||||
private Integer shipOFACNonSDNSanctionList;
|
||||
@Schema(description = "shipOFACSanctionList", example = "0")
|
||||
private Integer shipOFACSanctionList;
|
||||
@Schema(description = "shipOFACAdvisoryList", example = "0")
|
||||
private Integer shipOFACAdvisoryList;
|
||||
@Schema(description = "shipOwnerOFACSSIList", example = "0")
|
||||
private Integer shipOwnerOFACSSIList;
|
||||
@Schema(description = "shipOwnerAustralianSanctionList", example = "0")
|
||||
private Integer shipOwnerAustralianSanctionList;
|
||||
@Schema(description = "shipOwnerBESSanctionList", example = "0")
|
||||
private Integer shipOwnerBESSanctionList;
|
||||
@Schema(description = "shipOwnerCanadianSanctionList", example = "0")
|
||||
private Integer shipOwnerCanadianSanctionList;
|
||||
@Schema(description = "shipOwnerEUSanctionList", example = "0")
|
||||
private Integer shipOwnerEUSanctionList;
|
||||
@Schema(description = "shipOwnerFATFJurisdiction", example = "0")
|
||||
private Integer shipOwnerFATFJurisdiction;
|
||||
@Schema(description = "shipOwnerHistoricalOFACSanctionedCountry", example = "0")
|
||||
private Integer shipOwnerHistoricalOFACSanctionedCountry;
|
||||
@Schema(description = "shipOwnerOFACSanctionList", example = "0")
|
||||
private Integer shipOwnerOFACSanctionList;
|
||||
@Schema(description = "shipOwnerOFACSanctionedCountry", example = "0")
|
||||
private Integer shipOwnerOFACSanctionedCountry;
|
||||
@Schema(description = "shipOwnerParentCompanyNonCompliance", example = "0")
|
||||
private Integer shipOwnerParentCompanyNonCompliance;
|
||||
@Schema(description = "shipOwnerParentFATFJurisdiction", example = "0")
|
||||
private Integer shipOwnerParentFATFJurisdiction;
|
||||
@Schema(description = "shipOwnerParentOFACSanctionedCountry", example = "0")
|
||||
private Integer shipOwnerParentOFACSanctionedCountry;
|
||||
@Schema(description = "shipOwnerSwissSanctionList", example = "0")
|
||||
private Integer shipOwnerSwissSanctionList;
|
||||
@Schema(description = "shipOwnerUAESanctionList", example = "0")
|
||||
private Integer shipOwnerUAESanctionList;
|
||||
@Schema(description = "shipOwnerUNSanctionList", example = "0")
|
||||
private Integer shipOwnerUNSanctionList;
|
||||
@Schema(description = "shipSanctionedCountryPortCallLast12m", example = "0")
|
||||
private Integer shipSanctionedCountryPortCallLast12m;
|
||||
@Schema(description = "shipSanctionedCountryPortCallLast3m", example = "0")
|
||||
private Integer shipSanctionedCountryPortCallLast3m;
|
||||
@Schema(description = "shipSanctionedCountryPortCallLast6m", example = "0")
|
||||
private Integer shipSanctionedCountryPortCallLast6m;
|
||||
@Schema(description = "shipSecurityLegalDisputeEvent", example = "0")
|
||||
private Integer shipSecurityLegalDisputeEvent;
|
||||
@Schema(description = "shipSTSPartnerNonComplianceLast12m", example = "0")
|
||||
private Integer shipSTSPartnerNonComplianceLast12m;
|
||||
@Schema(description = "shipSwissSanctionList", example = "0")
|
||||
private Integer shipSwissSanctionList;
|
||||
@Schema(description = "shipUNSanctionList", example = "0")
|
||||
private Integer shipUNSanctionList;
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
package com.snp.batch.global.dto.bypass;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* S&P Global API 응답 스키마 (Swagger 문서용)
|
||||
* 이 클래스는 자동 생성되었습니다. 직접 수정하지 마세요.
|
||||
*/
|
||||
@Getter
|
||||
@Schema(description = "PortFacility")
|
||||
public class PortFacility {
|
||||
@Schema(description = "port_ID", example = "0")
|
||||
private Integer port_ID;
|
||||
@Schema(description = "old_ID", example = "")
|
||||
private String old_ID;
|
||||
@Schema(description = "status", example = "")
|
||||
private String status;
|
||||
@Schema(description = "port_Name", example = "")
|
||||
private String port_Name;
|
||||
@Schema(description = "unlocode", example = "")
|
||||
private String unlocode;
|
||||
@Schema(description = "countryCode", example = "")
|
||||
private String countryCode;
|
||||
@Schema(description = "country_Name", example = "")
|
||||
private String country_Name;
|
||||
@Schema(description = "dec_Lat", example = "0.0")
|
||||
private Double dec_Lat;
|
||||
@Schema(description = "dec_Long", example = "0.0")
|
||||
private Double dec_Long;
|
||||
@Schema(description = "position", example = "")
|
||||
private String position;
|
||||
@Schema(description = "time_Zone", example = "")
|
||||
private String time_Zone;
|
||||
@Schema(description = "dayLight_Saving_Time", example = "false")
|
||||
private Boolean dayLight_Saving_Time;
|
||||
@Schema(description = "maximum_Draft", example = "0.0")
|
||||
private Double maximum_Draft;
|
||||
@Schema(description = "breakbulk_Facilities", example = "false")
|
||||
private Boolean breakbulk_Facilities;
|
||||
@Schema(description = "container_Facilities", example = "false")
|
||||
private Boolean container_Facilities;
|
||||
@Schema(description = "dry_Bulk_Facilities", example = "false")
|
||||
private Boolean dry_Bulk_Facilities;
|
||||
@Schema(description = "liquid_Facilities", example = "false")
|
||||
private Boolean liquid_Facilities;
|
||||
@Schema(description = "roRo_Facilities", example = "false")
|
||||
private Boolean roRo_Facilities;
|
||||
@Schema(description = "passenger_Facilities", example = "false")
|
||||
private Boolean passenger_Facilities;
|
||||
@Schema(description = "dry_Dock_Facilities", example = "false")
|
||||
private Boolean dry_Dock_Facilities;
|
||||
@Schema(description = "lpG_Facilities", example = "0")
|
||||
private Integer lpG_Facilities;
|
||||
@Schema(description = "lnG_Facilities", example = "0")
|
||||
private Integer lnG_Facilities;
|
||||
@Schema(description = "ispS_Compliant", example = "false")
|
||||
private Boolean ispS_Compliant;
|
||||
@Schema(description = "csI_Compliant", example = "false")
|
||||
private Boolean csI_Compliant;
|
||||
@Schema(description = "last_Update", example = "")
|
||||
private String last_Update;
|
||||
@Schema(description = "entry_Date", example = "")
|
||||
private String entry_Date;
|
||||
@Schema(description = "region_Name", example = "")
|
||||
private String region_Name;
|
||||
@Schema(description = "continent_Name", example = "")
|
||||
private String continent_Name;
|
||||
@Schema(description = "master_POID", example = "")
|
||||
private String master_POID;
|
||||
@Schema(description = "wS_Port", example = "0")
|
||||
private Integer wS_Port;
|
||||
@Schema(description = "max_LOA", example = "0.0")
|
||||
private Double max_LOA;
|
||||
@Schema(description = "max_Beam", example = "0.0")
|
||||
private Double max_Beam;
|
||||
@Schema(description = "max_DWT", example = "0")
|
||||
private Integer max_DWT;
|
||||
@Schema(description = "max_Offshore_Draught", example = "0.0")
|
||||
private Double max_Offshore_Draught;
|
||||
@Schema(description = "max_Offshore_LOA", example = "0.0")
|
||||
private Double max_Offshore_LOA;
|
||||
@Schema(description = "max_Offshore_BCM", example = "0.0")
|
||||
private Double max_Offshore_BCM;
|
||||
@Schema(description = "max_Offshore_DWT", example = "0.0")
|
||||
private Double max_Offshore_DWT;
|
||||
@Schema(description = "lnG_Bunker", example = "false")
|
||||
private Boolean lnG_Bunker;
|
||||
@Schema(description = "dO_Bunker", example = "false")
|
||||
private Boolean dO_Bunker;
|
||||
@Schema(description = "fO_Bunker", example = "false")
|
||||
private Boolean fO_Bunker;
|
||||
@Schema(description = "free_Trade_Zone", example = "false")
|
||||
private Boolean free_Trade_Zone;
|
||||
@Schema(description = "ecO_Port", example = "false")
|
||||
private Boolean ecO_Port;
|
||||
@Schema(description = "emission_Control_Area", example = "false")
|
||||
private Boolean emission_Control_Area;
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
package com.snp.batch.global.dto.bypass;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* S&P Global API 응답 스키마 (Swagger 문서용)
|
||||
* 이 클래스는 자동 생성되었습니다. 직접 수정하지 마세요.
|
||||
*/
|
||||
@Getter
|
||||
@Schema(description = "RiskDetails")
|
||||
public class RiskDetails {
|
||||
@Schema(description = "lrno", example = "")
|
||||
private String lrno;
|
||||
@Schema(description = "lastUpdated", example = "")
|
||||
private String lastUpdated;
|
||||
@Schema(description = "riskDataMaintained", example = "0")
|
||||
private Integer riskDataMaintained;
|
||||
@Schema(description = "daysSinceLastSeenOnAIS", example = "0")
|
||||
private Integer daysSinceLastSeenOnAIS;
|
||||
@Schema(description = "daysUnderAIS", example = "0")
|
||||
private Integer daysUnderAIS;
|
||||
@Schema(description = "imoCorrectOnAIS", example = "0")
|
||||
private Integer imoCorrectOnAIS;
|
||||
@Schema(description = "sailingUnderName", example = "0")
|
||||
private Integer sailingUnderName;
|
||||
@Schema(description = "anomalousMessagesFromMMSI", example = "0")
|
||||
private Integer anomalousMessagesFromMMSI;
|
||||
@Schema(description = "mostRecentDarkActivity", example = "0")
|
||||
private Integer mostRecentDarkActivity;
|
||||
@Schema(description = "portCalls", example = "0")
|
||||
private Integer portCalls;
|
||||
@Schema(description = "portRisk", example = "0")
|
||||
private Integer portRisk;
|
||||
@Schema(description = "stsOperations", example = "0")
|
||||
private Integer stsOperations;
|
||||
@Schema(description = "driftingHighSeas", example = "0")
|
||||
private Integer driftingHighSeas;
|
||||
@Schema(description = "riskEvents", example = "0")
|
||||
private Integer riskEvents;
|
||||
@Schema(description = "flagChanges", example = "0")
|
||||
private Integer flagChanges;
|
||||
@Schema(description = "flagParisMOUPerformance", example = "0")
|
||||
private Integer flagParisMOUPerformance;
|
||||
@Schema(description = "flagTokyoMOUPeformance", example = "0")
|
||||
private Integer flagTokyoMOUPeformance;
|
||||
@Schema(description = "flagUSCGMOUPerformance", example = "0")
|
||||
private Integer flagUSCGMOUPerformance;
|
||||
@Schema(description = "uscgQualship21", example = "0")
|
||||
private Integer uscgQualship21;
|
||||
@Schema(description = "timeSincePSCInspection", example = "0")
|
||||
private Integer timeSincePSCInspection;
|
||||
@Schema(description = "pscInspections", example = "0")
|
||||
private Integer pscInspections;
|
||||
@Schema(description = "pscDefects", example = "0")
|
||||
private Integer pscDefects;
|
||||
@Schema(description = "pscDetentions", example = "0")
|
||||
private Integer pscDetentions;
|
||||
@Schema(description = "currentSMCCertificate", example = "0")
|
||||
private Integer currentSMCCertificate;
|
||||
@Schema(description = "docChanges", example = "0")
|
||||
private Integer docChanges;
|
||||
@Schema(description = "currentClass", example = "0")
|
||||
private Integer currentClass;
|
||||
@Schema(description = "classStatusChanges", example = "0")
|
||||
private Integer classStatusChanges;
|
||||
@Schema(description = "pandICoverage", example = "0")
|
||||
private Integer pandICoverage;
|
||||
@Schema(description = "nameChanges", example = "0")
|
||||
private Integer nameChanges;
|
||||
@Schema(description = "gboChanges", example = "0")
|
||||
private Integer gboChanges;
|
||||
@Schema(description = "ageOfShip", example = "0")
|
||||
private Integer ageOfShip;
|
||||
@Schema(description = "iuuFishingViolation", example = "0")
|
||||
private Integer iuuFishingViolation;
|
||||
@Schema(description = "draughtChanges", example = "0")
|
||||
private Integer draughtChanges;
|
||||
@Schema(description = "mostRecentSanctionedPortCall", example = "0")
|
||||
private Integer mostRecentSanctionedPortCall;
|
||||
@Schema(description = "singleShipOperation", example = "0")
|
||||
private Integer singleShipOperation;
|
||||
@Schema(description = "fleetSafety", example = "0")
|
||||
private Integer fleetSafety;
|
||||
@Schema(description = "fleetPSC", example = "0")
|
||||
private Integer fleetPSC;
|
||||
@Schema(description = "specialSurveyOverdue", example = "0")
|
||||
private Integer specialSurveyOverdue;
|
||||
@Schema(description = "ownerUnknown", example = "0")
|
||||
private Integer ownerUnknown;
|
||||
@Schema(description = "russianPortCall", example = "0")
|
||||
private Integer russianPortCall;
|
||||
@Schema(description = "russianOwnerRegistration", example = "0")
|
||||
private Integer russianOwnerRegistration;
|
||||
@Schema(description = "russianSTS", example = "0")
|
||||
private Integer russianSTS;
|
||||
}
|
||||
@ -0,0 +1,181 @@
|
||||
package com.snp.batch.global.dto.bypass;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* S&P Global API 응답 스키마 (Swagger 문서용)
|
||||
* 이 클래스는 자동 생성되었습니다. 직접 수정하지 마세요.
|
||||
*/
|
||||
@Getter
|
||||
@Schema(description = "RiskWithNarrativesDetails")
|
||||
public class RiskWithNarrativesDetails {
|
||||
@Schema(description = "lrno", example = "")
|
||||
private String lrno;
|
||||
@Schema(description = "lastUpdated", example = "")
|
||||
private String lastUpdated;
|
||||
@Schema(description = "riskDataMaintained", example = "0")
|
||||
private Integer riskDataMaintained;
|
||||
@Schema(description = "daysSinceLastSeenOnAIS", example = "0")
|
||||
private Integer daysSinceLastSeenOnAIS;
|
||||
@Schema(description = "daysSinceLastSeenOnAISNarrative", example = "")
|
||||
private String daysSinceLastSeenOnAISNarrative;
|
||||
@Schema(description = "daysUnderAIS", example = "0")
|
||||
private Integer daysUnderAIS;
|
||||
@Schema(description = "daysUnderAISNarrative", example = "")
|
||||
private String daysUnderAISNarrative;
|
||||
@Schema(description = "imoCorrectOnAIS", example = "0")
|
||||
private Integer imoCorrectOnAIS;
|
||||
@Schema(description = "imoCorrectOnAISNarrative", example = "")
|
||||
private String imoCorrectOnAISNarrative;
|
||||
@Schema(description = "sailingUnderName", example = "0")
|
||||
private Integer sailingUnderName;
|
||||
@Schema(description = "sailingUnderNameNarrative", example = "")
|
||||
private String sailingUnderNameNarrative;
|
||||
@Schema(description = "anomalousMessagesFromMMSI", example = "0")
|
||||
private Integer anomalousMessagesFromMMSI;
|
||||
@Schema(description = "anomalousMessagesFromMMSINarrative", example = "")
|
||||
private String anomalousMessagesFromMMSINarrative;
|
||||
@Schema(description = "mostRecentDarkActivity", example = "0")
|
||||
private Integer mostRecentDarkActivity;
|
||||
@Schema(description = "mostRecentDarkActivityNarrative", example = "")
|
||||
private String mostRecentDarkActivityNarrative;
|
||||
@Schema(description = "portCalls", example = "0")
|
||||
private Integer portCalls;
|
||||
@Schema(description = "portCallsNarrative", example = "")
|
||||
private String portCallsNarrative;
|
||||
@Schema(description = "portRisk", example = "0")
|
||||
private Integer portRisk;
|
||||
@Schema(description = "portRiskNarrative", example = "")
|
||||
private String portRiskNarrative;
|
||||
@Schema(description = "stsOperations", example = "0")
|
||||
private Integer stsOperations;
|
||||
@Schema(description = "stsOperationsNarrative", example = "")
|
||||
private String stsOperationsNarrative;
|
||||
@Schema(description = "driftingHighSeas", example = "0")
|
||||
private Integer driftingHighSeas;
|
||||
@Schema(description = "driftingHighSeasNarrative", example = "")
|
||||
private String driftingHighSeasNarrative;
|
||||
@Schema(description = "riskEvents", example = "0")
|
||||
private Integer riskEvents;
|
||||
@Schema(description = "riskEventNarrative", example = "")
|
||||
private String riskEventNarrative;
|
||||
@Schema(description = "riskEventNarrativeExtended", example = "")
|
||||
private String riskEventNarrativeExtended;
|
||||
@Schema(description = "flagChanges", example = "0")
|
||||
private Integer flagChanges;
|
||||
@Schema(description = "flagChangeNarrative", example = "")
|
||||
private String flagChangeNarrative;
|
||||
@Schema(description = "flagParisMOUPerformance", example = "0")
|
||||
private Integer flagParisMOUPerformance;
|
||||
@Schema(description = "flagParisMOUPerformanceNarrative", example = "")
|
||||
private String flagParisMOUPerformanceNarrative;
|
||||
@Schema(description = "flagTokyoMOUPeformance", example = "0")
|
||||
private Integer flagTokyoMOUPeformance;
|
||||
@Schema(description = "flagTokyoMOUPeformanceNarrative", example = "")
|
||||
private String flagTokyoMOUPeformanceNarrative;
|
||||
@Schema(description = "flagUSCGMOUPerformance", example = "0")
|
||||
private Integer flagUSCGMOUPerformance;
|
||||
@Schema(description = "flagUSCGMOUPerformanceNarrative", example = "")
|
||||
private String flagUSCGMOUPerformanceNarrative;
|
||||
@Schema(description = "uscgQualship21", example = "0")
|
||||
private Integer uscgQualship21;
|
||||
@Schema(description = "uscgQualship21Narrative", example = "")
|
||||
private String uscgQualship21Narrative;
|
||||
@Schema(description = "timeSincePSCInspection", example = "0")
|
||||
private Integer timeSincePSCInspection;
|
||||
@Schema(description = "timeSincePSCInspectionNarrative", example = "")
|
||||
private String timeSincePSCInspectionNarrative;
|
||||
@Schema(description = "pscInspections", example = "0")
|
||||
private Integer pscInspections;
|
||||
@Schema(description = "pscInspectionNarrative", example = "")
|
||||
private String pscInspectionNarrative;
|
||||
@Schema(description = "pscDefects", example = "0")
|
||||
private Integer pscDefects;
|
||||
@Schema(description = "pscDefectsNarrative", example = "")
|
||||
private String pscDefectsNarrative;
|
||||
@Schema(description = "pscDetentions", example = "0")
|
||||
private Integer pscDetentions;
|
||||
@Schema(description = "pscDetentionsNarrative", example = "")
|
||||
private String pscDetentionsNarrative;
|
||||
@Schema(description = "currentSMCCertificate", example = "0")
|
||||
private Integer currentSMCCertificate;
|
||||
@Schema(description = "currentSMCCertificateNarrative", example = "")
|
||||
private String currentSMCCertificateNarrative;
|
||||
@Schema(description = "docChanges", example = "0")
|
||||
private Integer docChanges;
|
||||
@Schema(description = "docChangesNarrative", example = "")
|
||||
private String docChangesNarrative;
|
||||
@Schema(description = "currentClass", example = "0")
|
||||
private Integer currentClass;
|
||||
@Schema(description = "currentClassNarrative", example = "")
|
||||
private String currentClassNarrative;
|
||||
@Schema(description = "currentClassNarrativeExtended", example = "")
|
||||
private String currentClassNarrativeExtended;
|
||||
@Schema(description = "classStatusChanges", example = "0")
|
||||
private Integer classStatusChanges;
|
||||
@Schema(description = "classStatusChangesNarrative", example = "")
|
||||
private String classStatusChangesNarrative;
|
||||
@Schema(description = "pandICoverage", example = "0")
|
||||
private Integer pandICoverage;
|
||||
@Schema(description = "pandICoverageNarrative", example = "")
|
||||
private String pandICoverageNarrative;
|
||||
@Schema(description = "pandICoverageNarrativeExtended", example = "")
|
||||
private String pandICoverageNarrativeExtended;
|
||||
@Schema(description = "nameChanges", example = "0")
|
||||
private Integer nameChanges;
|
||||
@Schema(description = "nameChangesNarrative", example = "")
|
||||
private String nameChangesNarrative;
|
||||
@Schema(description = "gboChanges", example = "0")
|
||||
private Integer gboChanges;
|
||||
@Schema(description = "gboChangesNarrative", example = "")
|
||||
private String gboChangesNarrative;
|
||||
@Schema(description = "ageOfShip", example = "0")
|
||||
private Integer ageOfShip;
|
||||
@Schema(description = "ageofShipNarrative", example = "")
|
||||
private String ageofShipNarrative;
|
||||
@Schema(description = "iuuFishingViolation", example = "0")
|
||||
private Integer iuuFishingViolation;
|
||||
@Schema(description = "iuuFishingNarrative", example = "")
|
||||
private String iuuFishingNarrative;
|
||||
@Schema(description = "draughtChanges", example = "0")
|
||||
private Integer draughtChanges;
|
||||
@Schema(description = "draughtChangesNarrative", example = "")
|
||||
private String draughtChangesNarrative;
|
||||
@Schema(description = "mostRecentSanctionedPortCall", example = "0")
|
||||
private Integer mostRecentSanctionedPortCall;
|
||||
@Schema(description = "mostRecentSanctionedPortCallNarrative", example = "")
|
||||
private String mostRecentSanctionedPortCallNarrative;
|
||||
@Schema(description = "singleShipOperation", example = "0")
|
||||
private Integer singleShipOperation;
|
||||
@Schema(description = "singleShipOperationNarrative", example = "")
|
||||
private String singleShipOperationNarrative;
|
||||
@Schema(description = "fleetSafety", example = "0")
|
||||
private Integer fleetSafety;
|
||||
@Schema(description = "fleetSafetyNarrative", example = "")
|
||||
private String fleetSafetyNarrative;
|
||||
@Schema(description = "fleetPSC", example = "0")
|
||||
private Integer fleetPSC;
|
||||
@Schema(description = "fleetPSCNarrative", example = "")
|
||||
private String fleetPSCNarrative;
|
||||
@Schema(description = "specialSurveyOverdue", example = "0")
|
||||
private Integer specialSurveyOverdue;
|
||||
@Schema(description = "specialSurveyOverdueNarrative", example = "")
|
||||
private String specialSurveyOverdueNarrative;
|
||||
@Schema(description = "ownerUnknown", example = "0")
|
||||
private Integer ownerUnknown;
|
||||
@Schema(description = "ownerUnknownNarrative", example = "")
|
||||
private String ownerUnknownNarrative;
|
||||
@Schema(description = "russianPortCall", example = "0")
|
||||
private Integer russianPortCall;
|
||||
@Schema(description = "russianPortCallNarrative", example = "")
|
||||
private String russianPortCallNarrative;
|
||||
@Schema(description = "russianOwnerRegistration", example = "0")
|
||||
private Integer russianOwnerRegistration;
|
||||
@Schema(description = "russianOwnerRegistrationNarrative", example = "")
|
||||
private String russianOwnerRegistrationNarrative;
|
||||
@Schema(description = "russianSTS", example = "0")
|
||||
private Integer russianSTS;
|
||||
@Schema(description = "russianSTSNarrative", example = "")
|
||||
private String russianSTSNarrative;
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package com.snp.batch.global.dto.bypass;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* S&P Global API 응답 스키마 (Swagger 문서용)
|
||||
* 이 클래스는 자동 생성되었습니다. 직접 수정하지 마세요.
|
||||
*/
|
||||
@Getter
|
||||
@Schema(description = "TargetsParameters")
|
||||
public class TargetsParameters {
|
||||
@Schema(description = "sinceSeconds", example = "0")
|
||||
private Integer sinceSeconds;
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package com.snp.batch.global.dto.bypass;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* S&P Global API 응답 스키마 (Swagger 문서용)
|
||||
* 이 클래스는 자동 생성되었습니다. 직접 수정하지 마세요.
|
||||
*/
|
||||
@Getter
|
||||
@Schema(description = "sTargetCount")
|
||||
public class sTargetCount {
|
||||
@Schema(description = "apsStatus", example = "")
|
||||
private String apsStatus;
|
||||
@Schema(description = "targetCount", example = "0")
|
||||
private Integer targetCount;
|
||||
}
|
||||
@ -95,6 +95,12 @@ public class BypassApiConfig {
|
||||
@Column(name = "generated_at")
|
||||
private LocalDateTime generatedAt;
|
||||
|
||||
/**
|
||||
* Swagger 응답 스키마 DTO 클래스 (FQCN, 코드 생성 시 자동 설정)
|
||||
*/
|
||||
@Column(name = "response_schema_class", length = 300)
|
||||
private String responseSchemaClass;
|
||||
|
||||
/**
|
||||
* 생성 일시 (감사 필드)
|
||||
*/
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
package com.snp.batch.jobs.web.ais.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.snp.batch.common.web.controller.BaseBypassController;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import com.snp.batch.jobs.web.ais.service.GetTargetCountService;
|
||||
|
||||
/**
|
||||
* Ais API
|
||||
* S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/ais")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Ais", description = "[AIS API] Ais API")
|
||||
public class AisController extends BaseBypassController {
|
||||
|
||||
private final GetTargetCountService getTargetCountService;
|
||||
|
||||
@Operation(
|
||||
summary = "선박 AIS 대상 수 조회",
|
||||
description = "선박 AIS 대상 수 조회"
|
||||
)
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "선박 AIS 대상 수 조회",
|
||||
content = @io.swagger.v3.oas.annotations.media.Content(
|
||||
mediaType = "application/json",
|
||||
array = @io.swagger.v3.oas.annotations.media.ArraySchema(
|
||||
schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = com.snp.batch.global.dto.bypass.sTargetCount.class)
|
||||
)
|
||||
)
|
||||
)
|
||||
@PostMapping("/GetTargetCount")
|
||||
public ResponseEntity<JsonNode> getGetTargetCountData(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "",
|
||||
content = @io.swagger.v3.oas.annotations.media.Content(
|
||||
mediaType = "application/json",
|
||||
schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = com.snp.batch.global.dto.bypass.TargetsParameters.class)))
|
||||
@RequestBody JsonNode sinceSeconds) {
|
||||
return executeRaw(() -> getTargetCountService.getGetTargetCountData(sinceSeconds));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.snp.batch.jobs.web.ais.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.snp.batch.common.web.service.BaseBypassService;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* 선박 AIS 대상 수 조회 서비스
|
||||
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||
*/
|
||||
@Service
|
||||
public class GetTargetCountService extends BaseBypassService<JsonNode> {
|
||||
|
||||
public GetTargetCountService(
|
||||
@Qualifier("maritimeAisApiWebClient") WebClient webClient) {
|
||||
super(webClient, "/AisSvc.svc/AIS/GetTargetCount", "선박 AIS 대상 수 조회",
|
||||
new ParameterizedTypeReference<>() {},
|
||||
new ParameterizedTypeReference<>() {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 선박 AIS 대상 수 조회 데이터를 조회합니다.
|
||||
*/
|
||||
public JsonNode getGetTargetCountData(JsonNode sinceSeconds) {
|
||||
return fetchRawPost(sinceSeconds, uri -> uri.path(getApiPath())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@ -14,13 +14,13 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import com.snp.batch.jobs.web.compliance.service.CompliancesByImosService;
|
||||
|
||||
/**
|
||||
* Compliance bypass API
|
||||
* Compliance API
|
||||
* S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/compliance")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Compliance", description = "[Service API] Compliance bypass API")
|
||||
@Tag(name = "Compliance", description = "[Service API] Compliance API")
|
||||
public class ComplianceController extends BaseBypassController {
|
||||
|
||||
private final CompliancesByImosService compliancesByImosService;
|
||||
@ -29,6 +29,16 @@ public class ComplianceController extends BaseBypassController {
|
||||
summary = "IMO 기반 선박 규정준수 조회",
|
||||
description = "Gets details of the IMOs of ships with full compliance details that match given IMOs"
|
||||
)
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "IMO 기반 선박 규정준수 조회",
|
||||
content = @io.swagger.v3.oas.annotations.media.Content(
|
||||
mediaType = "application/json",
|
||||
array = @io.swagger.v3.oas.annotations.media.ArraySchema(
|
||||
schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = com.snp.batch.global.dto.bypass.ComplianceDetails.class)
|
||||
)
|
||||
)
|
||||
)
|
||||
@GetMapping("/CompliancesByImos")
|
||||
public ResponseEntity<JsonNode> getCompliancesByImosData(@Parameter(description = "Comma separated IMOs up to a total of 100", example = "9876543")
|
||||
@RequestParam(required = true) String imos) {
|
||||
|
||||
@ -8,7 +8,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* IMO 기반 선박 규정준수 조회 bypass 서비스
|
||||
* IMO 기반 선박 규정준수 조회 서비스
|
||||
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||
*/
|
||||
@Service
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
package com.snp.batch.jobs.web.facility.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.snp.batch.common.web.controller.BaseBypassController;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import com.snp.batch.jobs.web.facility.service.PortsService;
|
||||
|
||||
/**
|
||||
* Facility API
|
||||
* S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/facility")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Facility", description = "[Service API] Facility API")
|
||||
public class FacilityController extends BaseBypassController {
|
||||
|
||||
private final PortsService portsService;
|
||||
|
||||
@Operation(
|
||||
summary = "항구 시설 조회",
|
||||
description = "항구 시설 조회"
|
||||
)
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "항구 시설 조회",
|
||||
content = @io.swagger.v3.oas.annotations.media.Content(
|
||||
mediaType = "application/json",
|
||||
array = @io.swagger.v3.oas.annotations.media.ArraySchema(
|
||||
schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = com.snp.batch.global.dto.bypass.PortFacility.class)
|
||||
)
|
||||
)
|
||||
)
|
||||
@GetMapping("/Ports")
|
||||
public ResponseEntity<JsonNode> getPortsData() {
|
||||
return executeRaw(() -> portsService.getPortsData());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.snp.batch.jobs.web.facility.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.snp.batch.common.web.service.BaseBypassService;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* 항구 시설 조회 서비스
|
||||
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||
*/
|
||||
@Service
|
||||
public class PortsService extends BaseBypassService<JsonNode> {
|
||||
|
||||
public PortsService(
|
||||
@Qualifier("maritimeServiceApiWebClient") WebClient webClient) {
|
||||
super(webClient, "/Facilities/Ports", "항구 시설 조회",
|
||||
new ParameterizedTypeReference<>() {},
|
||||
new ParameterizedTypeReference<>() {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 항구 시설 조회 데이터를 조회합니다.
|
||||
*/
|
||||
public JsonNode getPortsData() {
|
||||
return fetchRawGet(uri -> uri.path(getApiPath())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@ -15,13 +15,13 @@ import com.snp.batch.jobs.web.risk.service.RisksByImosService;
|
||||
import com.snp.batch.jobs.web.risk.service.UpdatedComplianceListService;
|
||||
|
||||
/**
|
||||
* Risk bypass API
|
||||
* Risk API
|
||||
* S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/risk")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Risk", description = "[Service API] Risk bypass API")
|
||||
@Tag(name = "Risk", description = "[Service API] Risk API")
|
||||
public class RiskController extends BaseBypassController {
|
||||
|
||||
private final RisksByImosService risksByImosService;
|
||||
@ -31,6 +31,16 @@ public class RiskController extends BaseBypassController {
|
||||
summary = "IMO 기반 선박 위험지표 조회",
|
||||
description = "Gets details of the IMOs of all ships with risk updates as a collection"
|
||||
)
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "IMO 기반 선박 위험지표 조회",
|
||||
content = @io.swagger.v3.oas.annotations.media.Content(
|
||||
mediaType = "application/json",
|
||||
array = @io.swagger.v3.oas.annotations.media.ArraySchema(
|
||||
schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = com.snp.batch.global.dto.bypass.RiskWithNarrativesDetails.class)
|
||||
)
|
||||
)
|
||||
)
|
||||
@GetMapping("/RisksByImos")
|
||||
public ResponseEntity<JsonNode> getRisksByImosData(@Parameter(description = "Comma separated IMOs up to a total of 100", example = "9876543")
|
||||
@RequestParam(required = true) String imos) {
|
||||
@ -41,6 +51,16 @@ public class RiskController extends BaseBypassController {
|
||||
summary = "기간 내 변경된 위험지표 조회",
|
||||
description = "Gets details of the IMOs of all ships with risk updates"
|
||||
)
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "기간 내 변경된 위험지표 조회",
|
||||
content = @io.swagger.v3.oas.annotations.media.Content(
|
||||
mediaType = "application/json",
|
||||
array = @io.swagger.v3.oas.annotations.media.ArraySchema(
|
||||
schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = com.snp.batch.global.dto.bypass.RiskDetails.class)
|
||||
)
|
||||
)
|
||||
)
|
||||
@GetMapping("/UpdatedRiskList")
|
||||
public ResponseEntity<JsonNode> getUpdatedComplianceListData(@Parameter(description = "Time/seconds are optional", example = "2026-03-30T07:01:27.000Z")
|
||||
@RequestParam(required = true) String fromDate,
|
||||
|
||||
@ -8,7 +8,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* IMO 기반 선박 위험지표 조회 bypass 서비스
|
||||
* IMO 기반 선박 위험지표 조회 서비스
|
||||
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||
*/
|
||||
@Service
|
||||
|
||||
@ -8,7 +8,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* 기간 내 변경된 위험지표 조회 bypass 서비스
|
||||
* 기간 내 변경된 위험지표 조회 서비스
|
||||
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||
*/
|
||||
@Service
|
||||
|
||||
@ -14,13 +14,13 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import com.snp.batch.jobs.web.ship.service.GetShipDataByIHSLRorIMOService;
|
||||
|
||||
/**
|
||||
* Ship bypass API
|
||||
* Ship API
|
||||
* S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/ship")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Ship", description = "[Ship API] Ship bypass API")
|
||||
@Tag(name = "Ship", description = "[Ship API] Ship API")
|
||||
public class ShipController extends BaseBypassController {
|
||||
|
||||
private final GetShipDataByIHSLRorIMOService getShipDataByIHSLRorIMOService;
|
||||
@ -29,6 +29,16 @@ public class ShipController extends BaseBypassController {
|
||||
summary = "IMO 기반 선박제원정보 조회",
|
||||
description = "IMO 기반 선박제원정보 조회"
|
||||
)
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "IMO 기반 선박제원정보 조회",
|
||||
content = @io.swagger.v3.oas.annotations.media.Content(
|
||||
mediaType = "application/json",
|
||||
array = @io.swagger.v3.oas.annotations.media.ArraySchema(
|
||||
schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = com.snp.batch.global.dto.bypass.APSShipResult.class)
|
||||
)
|
||||
)
|
||||
)
|
||||
@GetMapping("/GetShipDataByIHSLRorIMO")
|
||||
public ResponseEntity<JsonNode> getGetShipDataByIHSLRorIMOData(@Parameter(description = "", example = "9876543")
|
||||
@RequestParam(required = true) String ihslrOrImo) {
|
||||
|
||||
@ -8,7 +8,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* IMO 기반 선박제원정보 조회 bypass 서비스
|
||||
* IMO 기반 선박제원정보 조회 서비스
|
||||
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||
*/
|
||||
@Service
|
||||
|
||||
@ -3,6 +3,7 @@ package com.snp.batch.service;
|
||||
import com.snp.batch.global.dto.CodeGenerationResult;
|
||||
import com.snp.batch.global.model.BypassApiConfig;
|
||||
import com.snp.batch.global.model.BypassApiParam;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@ -11,8 +12,10 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -23,8 +26,11 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class BypassCodeGenerator {
|
||||
|
||||
private final SwaggerSchemaGenerator swaggerSchemaGenerator;
|
||||
|
||||
private static final String BASE_PACKAGE = "com.snp.batch.jobs.web";
|
||||
|
||||
/**
|
||||
@ -43,6 +49,26 @@ public class BypassCodeGenerator {
|
||||
|
||||
List<String> servicePaths = new ArrayList<>();
|
||||
|
||||
// Generate response/request DTOs from swagger.json
|
||||
Map<Long, String> responseSchemaMap = new HashMap<>();
|
||||
Map<Long, String> requestBodySchemaMap = new HashMap<>();
|
||||
for (BypassApiConfig config : configs) {
|
||||
String fqcn = swaggerSchemaGenerator.generateResponseDto(
|
||||
config.getWebclientBean(), config.getExternalPath(), force);
|
||||
if (fqcn != null) {
|
||||
responseSchemaMap.put(config.getId(), fqcn);
|
||||
config.setResponseSchemaClass(fqcn);
|
||||
}
|
||||
// POST 엔드포인트의 requestBody DTO 생성
|
||||
if ("POST".equalsIgnoreCase(config.getHttpMethod())) {
|
||||
String reqFqcn = swaggerSchemaGenerator.generateRequestBodyDto(
|
||||
config.getWebclientBean(), config.getExternalPath(), force);
|
||||
if (reqFqcn != null) {
|
||||
requestBodySchemaMap.put(config.getId(), reqFqcn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (BypassApiConfig config : configs) {
|
||||
String endpointName = config.getEndpointName();
|
||||
String servicePath = basePath + "/service/" + endpointName + "Service.java";
|
||||
@ -58,7 +84,7 @@ public class BypassCodeGenerator {
|
||||
}
|
||||
|
||||
// Controller: 모든 엔드포인트를 합치므로 항상 재생성
|
||||
String controllerCode = generateControllerCode(domain, configs);
|
||||
String controllerCode = generateControllerCode(domain, configs, responseSchemaMap, requestBodySchemaMap);
|
||||
String domainCapitalized = capitalize(domain);
|
||||
Path controllerFilePath = writeFile(
|
||||
basePath + "/controller/" + domainCapitalized + "Controller.java", controllerCode, true);
|
||||
@ -83,9 +109,23 @@ public class BypassCodeGenerator {
|
||||
String serviceClass = endpointName + "Service";
|
||||
boolean isPost = "POST".equalsIgnoreCase(config.getHttpMethod());
|
||||
|
||||
// POST인데 BODY 파라미터가 없으면 자동 추가
|
||||
List<BypassApiParam> effectiveParams = new ArrayList<>(params);
|
||||
if (isPost && effectiveParams.stream().noneMatch(p -> "BODY".equalsIgnoreCase(p.getParamIn()))) {
|
||||
BypassApiParam autoBody = new BypassApiParam();
|
||||
autoBody.setParamName("body");
|
||||
autoBody.setParamType("STRING");
|
||||
autoBody.setParamIn("BODY");
|
||||
autoBody.setRequired(false);
|
||||
autoBody.setDescription("요청 본문 (JSON)");
|
||||
autoBody.setExample("{}");
|
||||
autoBody.setSortOrder(999);
|
||||
effectiveParams.add(autoBody);
|
||||
}
|
||||
|
||||
String methodName = "get" + endpointName + "Data";
|
||||
String fetchMethod = buildFetchMethodCall(config, params, isPost);
|
||||
String methodParams = buildMethodParams(params);
|
||||
String fetchMethod = buildFetchMethodCall(config, effectiveParams, isPost);
|
||||
String methodParams = buildMethodParams(effectiveParams);
|
||||
|
||||
return """
|
||||
package {{PACKAGE}};
|
||||
@ -98,7 +138,7 @@ public class BypassCodeGenerator {
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* {{DISPLAY_NAME}} bypass 서비스
|
||||
* {{DISPLAY_NAME}} 서비스
|
||||
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||
*/
|
||||
@Service
|
||||
@ -133,7 +173,7 @@ public class BypassCodeGenerator {
|
||||
* Controller 코드 생성 (RAW 모드).
|
||||
* 모든 엔드포인트가 ResponseEntity<JsonNode>를 반환합니다 (외부 API 원본 JSON 그대로).
|
||||
*/
|
||||
private String generateControllerCode(String domain, List<BypassApiConfig> configs) {
|
||||
private String generateControllerCode(String domain, List<BypassApiConfig> configs, Map<Long, String> responseSchemaMap, Map<Long, String> requestBodySchemaMap) {
|
||||
String packageName = BASE_PACKAGE + "." + domain + ".controller";
|
||||
String servicePackage = BASE_PACKAGE + "." + domain + ".service";
|
||||
String domainCap = capitalize(domain);
|
||||
@ -157,7 +197,7 @@ public class BypassCodeGenerator {
|
||||
.anyMatch(c -> c.getParams().stream().anyMatch(p -> "PATH".equalsIgnoreCase(p.getParamIn())));
|
||||
boolean anyQuery = configs.stream()
|
||||
.anyMatch(c -> c.getParams().stream().anyMatch(p -> "QUERY".equalsIgnoreCase(p.getParamIn())));
|
||||
boolean anyBody = configs.stream()
|
||||
boolean anyBody = anyPost || configs.stream()
|
||||
.anyMatch(c -> c.getParams().stream().anyMatch(p -> "BODY".equalsIgnoreCase(p.getParamIn())));
|
||||
|
||||
if (anyPost) importSet.add("import org.springframework.web.bind.annotation.PostMapping;");
|
||||
@ -181,7 +221,7 @@ public class BypassCodeGenerator {
|
||||
}
|
||||
|
||||
String tagPrefix = getTagPrefix(configs.get(0).getWebclientBean());
|
||||
String tagDescription = tagPrefix + " " + domainCap + " bypass API";
|
||||
String tagDescription = tagPrefix + " " + domainCap + " API";
|
||||
|
||||
// 엔드포인트 메서드 목록
|
||||
StringBuilder methods = new StringBuilder();
|
||||
@ -191,10 +231,25 @@ public class BypassCodeGenerator {
|
||||
String serviceField = Character.toLowerCase(serviceClass.charAt(0)) + serviceClass.substring(1);
|
||||
boolean isPost = "POST".equalsIgnoreCase(config.getHttpMethod());
|
||||
|
||||
// POST인데 BODY 파라미터가 없으면 자동 추가
|
||||
List<BypassApiParam> ctrlParams = new ArrayList<>(config.getParams());
|
||||
if (isPost && ctrlParams.stream().noneMatch(p -> "BODY".equalsIgnoreCase(p.getParamIn()))) {
|
||||
BypassApiParam autoBody = new BypassApiParam();
|
||||
autoBody.setParamName("body");
|
||||
autoBody.setParamType("STRING");
|
||||
autoBody.setParamIn("BODY");
|
||||
autoBody.setRequired(false);
|
||||
autoBody.setDescription("요청 본문 (JSON)");
|
||||
autoBody.setExample("{}");
|
||||
autoBody.setSortOrder(999);
|
||||
ctrlParams.add(autoBody);
|
||||
}
|
||||
|
||||
String mappingAnnotation = isPost ? "@PostMapping" : "@GetMapping";
|
||||
String mappingPath = buildMappingPath(config.getParams(), config.getExternalPath());
|
||||
String paramAnnotations = buildControllerParamAnnotations(config.getParams());
|
||||
String serviceCallArgs = buildServiceCallArgs(config.getParams());
|
||||
String mappingPath = buildMappingPath(ctrlParams, config.getExternalPath());
|
||||
String reqBodySchema = requestBodySchemaMap.get(config.getId());
|
||||
String paramAnnotations = buildControllerParamAnnotations(ctrlParams, reqBodySchema);
|
||||
String serviceCallArgs = buildServiceCallArgs(ctrlParams);
|
||||
String methodName = "get" + endpointName + "Data";
|
||||
|
||||
methods.append("\n");
|
||||
@ -205,6 +260,20 @@ public class BypassCodeGenerator {
|
||||
: config.getDisplayName() + " 데이터를 요청하고 응답을 그대로 반환합니다.";
|
||||
methods.append(" description = \"").append(opDescription).append("\"\n");
|
||||
methods.append(" )\n");
|
||||
// @ApiResponse with schema (if available)
|
||||
String schemaClass = responseSchemaMap.get(config.getId());
|
||||
if (schemaClass != null && !schemaClass.isEmpty()) {
|
||||
methods.append(" @io.swagger.v3.oas.annotations.responses.ApiResponse(\n");
|
||||
methods.append(" responseCode = \"200\",\n");
|
||||
methods.append(" description = \"").append(config.getDisplayName()).append("\",\n");
|
||||
methods.append(" content = @io.swagger.v3.oas.annotations.media.Content(\n");
|
||||
methods.append(" mediaType = \"application/json\",\n");
|
||||
methods.append(" array = @io.swagger.v3.oas.annotations.media.ArraySchema(\n");
|
||||
methods.append(" schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = ").append(schemaClass).append(".class)\n");
|
||||
methods.append(" )\n");
|
||||
methods.append(" )\n");
|
||||
methods.append(" )\n");
|
||||
}
|
||||
methods.append(" ").append(mappingAnnotation).append(mappingPath).append("\n");
|
||||
methods.append(" public ResponseEntity<JsonNode> ").append(methodName).append("(");
|
||||
if (!paramAnnotations.isEmpty()) {
|
||||
@ -219,7 +288,7 @@ public class BypassCodeGenerator {
|
||||
return "package " + packageName + ";\n\n"
|
||||
+ importsStr + "\n\n"
|
||||
+ "/**\n"
|
||||
+ " * " + domainCap + " bypass API\n"
|
||||
+ " * " + domainCap + " API\n"
|
||||
+ " * S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환\n"
|
||||
+ " */\n"
|
||||
+ "@RestController\n"
|
||||
@ -263,7 +332,10 @@ public class BypassCodeGenerator {
|
||||
private String buildMethodParams(List<BypassApiParam> params) {
|
||||
return params.stream()
|
||||
.sorted((a, b) -> Integer.compare(a.getSortOrder(), b.getSortOrder()))
|
||||
.map(p -> toJavaType(p.getParamType()) + " " + p.getParamName())
|
||||
.map(p -> {
|
||||
String type = "BODY".equalsIgnoreCase(p.getParamIn()) ? "JsonNode" : toJavaType(p.getParamType());
|
||||
return type + " " + p.getParamName();
|
||||
})
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
@ -274,7 +346,7 @@ public class BypassCodeGenerator {
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
private String buildControllerParamAnnotations(List<BypassApiParam> params) {
|
||||
private String buildControllerParamAnnotations(List<BypassApiParam> params, String requestBodySchemaClass) {
|
||||
if (params.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
@ -290,8 +362,17 @@ public class BypassCodeGenerator {
|
||||
return switch (p.getParamIn().toUpperCase()) {
|
||||
case "PATH" -> "@Parameter(description = \"" + description + "\", example = \"" + example + "\")\n"
|
||||
+ " @PathVariable " + javaType + " " + paramName;
|
||||
case "BODY" -> "@Parameter(description = \"" + description + "\", example = \"" + example + "\")\n"
|
||||
+ " @RequestBody " + javaType + " " + paramName;
|
||||
case "BODY" -> {
|
||||
StringBuilder bodyAnno = new StringBuilder();
|
||||
bodyAnno.append("@io.swagger.v3.oas.annotations.parameters.RequestBody(description = \"").append(description).append("\"");
|
||||
if (requestBodySchemaClass != null && !requestBodySchemaClass.isEmpty()) {
|
||||
bodyAnno.append(",\n content = @io.swagger.v3.oas.annotations.media.Content(\n");
|
||||
bodyAnno.append(" mediaType = \"application/json\",\n");
|
||||
bodyAnno.append(" schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = ").append(requestBodySchemaClass).append(".class))");
|
||||
}
|
||||
bodyAnno.append(")\n @RequestBody JsonNode ").append(paramName);
|
||||
yield bodyAnno.toString();
|
||||
}
|
||||
default -> {
|
||||
String required = Boolean.TRUE.equals(p.getRequired()) ? "true" : "false";
|
||||
yield "@Parameter(description = \"" + description + "\", example = \"" + example + "\")\n"
|
||||
|
||||
@ -80,6 +80,7 @@ public class BypassConfigService {
|
||||
config.setExternalPath(request.getExternalPath());
|
||||
config.setHttpMethod(request.getHttpMethod());
|
||||
config.setDescription(request.getDescription());
|
||||
config.setResponseSchemaClass(request.getResponseSchemaClass());
|
||||
|
||||
// params 교체: clear → flush(DELETE 실행) → 새로 추가
|
||||
config.getParams().clear();
|
||||
@ -131,6 +132,7 @@ public class BypassConfigService {
|
||||
.externalPath(config.getExternalPath())
|
||||
.httpMethod(config.getHttpMethod())
|
||||
.description(config.getDescription())
|
||||
.responseSchemaClass(config.getResponseSchemaClass())
|
||||
.generated(config.getGenerated())
|
||||
.generatedAt(config.getGeneratedAt())
|
||||
.createdAt(config.getCreatedAt())
|
||||
@ -148,6 +150,7 @@ public class BypassConfigService {
|
||||
.externalPath(request.getExternalPath())
|
||||
.httpMethod(request.getHttpMethod() != null ? request.getHttpMethod() : "GET")
|
||||
.description(request.getDescription())
|
||||
.responseSchemaClass(request.getResponseSchemaClass())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
252
src/main/java/com/snp/batch/service/SwaggerSchemaGenerator.java
Normal file
252
src/main/java/com/snp/batch/service/SwaggerSchemaGenerator.java
Normal file
@ -0,0 +1,252 @@
|
||||
package com.snp.batch.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SwaggerSchemaGenerator {
|
||||
|
||||
private static final String DTO_PACKAGE = "com.snp.batch.global.dto.bypass";
|
||||
private static final String DTO_BASE_PATH = "src/main/java/com/snp/batch/global/dto/bypass";
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private static final Map<String, String> SWAGGER_FILE_MAP = Map.of(
|
||||
"maritimeAisApiWebClient", "swagger/swagger_aisapi.json",
|
||||
"maritimeApiWebClient", "swagger/swagger_shipsapi.json",
|
||||
"maritimeServiceApiWebClient", "swagger/swagger_webservices.json"
|
||||
);
|
||||
|
||||
/**
|
||||
* swagger.json에서 응답 스키마를 추출하여 DTO Java 클래스를 자동 생성합니다.
|
||||
* @return 생성된 DTO의 FQCN, 스키마를 찾을 수 없으면 null
|
||||
*/
|
||||
public String generateResponseDto(String webclientBean, String externalPath, boolean force) {
|
||||
String swaggerFile = SWAGGER_FILE_MAP.get(webclientBean);
|
||||
if (swaggerFile == null) {
|
||||
log.warn("Unknown webclientBean: {}, skipping DTO generation", webclientBean);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
JsonNode swagger = loadSwaggerJson(swaggerFile);
|
||||
if (swagger == null) return null;
|
||||
|
||||
// Find path in swagger
|
||||
JsonNode paths = swagger.get("paths");
|
||||
if (paths == null || !paths.has(externalPath)) {
|
||||
log.info("Path {} not found in {}, skipping DTO generation", externalPath, swaggerFile);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get 200 response schema $ref
|
||||
JsonNode pathItem = paths.get(externalPath);
|
||||
// Try GET first, then POST
|
||||
JsonNode operation = pathItem.has("get") ? pathItem.get("get") : pathItem.has("post") ? pathItem.get("post") : null;
|
||||
if (operation == null) return null;
|
||||
|
||||
JsonNode responses = operation.get("responses");
|
||||
if (responses == null || !responses.has("200")) return null;
|
||||
|
||||
JsonNode response200 = responses.get("200");
|
||||
JsonNode content = response200.get("content");
|
||||
if (content == null) return null;
|
||||
|
||||
JsonNode jsonContent = content.has("application/json") ? content.get("application/json") : content.has("text/json") ? content.get("text/json") : null;
|
||||
if (jsonContent == null) return null;
|
||||
|
||||
JsonNode schema = jsonContent.get("schema");
|
||||
if (schema == null) return null;
|
||||
|
||||
// Resolve $ref - could be direct or array items
|
||||
String schemaRef = null;
|
||||
boolean isArray = "array".equals(schema.has("type") ? schema.get("type").asText() : "");
|
||||
if (isArray && schema.has("items") && schema.get("items").has("$ref")) {
|
||||
schemaRef = schema.get("items").get("$ref").asText();
|
||||
} else if (schema.has("$ref")) {
|
||||
schemaRef = schema.get("$ref").asText();
|
||||
}
|
||||
|
||||
if (schemaRef == null) return null;
|
||||
|
||||
// Extract schema name from $ref (e.g., "#/components/schemas/Foo.Bar.Baz" -> "Baz")
|
||||
String fullSchemaName = schemaRef.substring(schemaRef.lastIndexOf("/") + 1);
|
||||
String className = fullSchemaName.contains(".")
|
||||
? fullSchemaName.substring(fullSchemaName.lastIndexOf(".") + 1)
|
||||
: fullSchemaName;
|
||||
|
||||
// Get schema definition
|
||||
JsonNode schemas = swagger.at("/components/schemas/" + fullSchemaName);
|
||||
if (schemas.isMissingNode()) return null;
|
||||
|
||||
// Generate DTO file
|
||||
String projectRoot = System.getProperty("user.dir");
|
||||
String filePath = projectRoot + "/" + DTO_BASE_PATH + "/" + className + ".java";
|
||||
|
||||
if (!force && Files.exists(Path.of(filePath))) {
|
||||
log.info("DTO already exists, skipping: {}", filePath);
|
||||
return DTO_PACKAGE + "." + className;
|
||||
}
|
||||
|
||||
String javaCode = generateDtoCode(className, schemas);
|
||||
Files.createDirectories(Path.of(filePath).getParent());
|
||||
Files.writeString(Path.of(filePath), javaCode, StandardCharsets.UTF_8);
|
||||
log.info("DTO generated: {}", filePath);
|
||||
|
||||
return DTO_PACKAGE + "." + className;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to generate response DTO for {} {}: {}", webclientBean, externalPath, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* swagger.json에서 requestBody 스키마를 추출하여 DTO Java 클래스를 자동 생성합니다.
|
||||
* @return 생성된 DTO의 FQCN, 스키마를 찾을 수 없으면 null
|
||||
*/
|
||||
public String generateRequestBodyDto(String webclientBean, String externalPath, boolean force) {
|
||||
String swaggerFile = SWAGGER_FILE_MAP.get(webclientBean);
|
||||
if (swaggerFile == null) return null;
|
||||
|
||||
try {
|
||||
JsonNode swagger = loadSwaggerJson(swaggerFile);
|
||||
if (swagger == null) return null;
|
||||
|
||||
JsonNode paths = swagger.get("paths");
|
||||
if (paths == null || !paths.has(externalPath)) return null;
|
||||
|
||||
JsonNode pathItem = paths.get(externalPath);
|
||||
JsonNode operation = pathItem.has("post") ? pathItem.get("post") : null;
|
||||
if (operation == null) return null;
|
||||
|
||||
JsonNode requestBody = operation.get("requestBody");
|
||||
if (requestBody == null) return null;
|
||||
|
||||
JsonNode content = requestBody.get("content");
|
||||
if (content == null) return null;
|
||||
|
||||
JsonNode jsonContent = content.has("application/json") ? content.get("application/json")
|
||||
: content.has("application/json-patch+json") ? content.get("application/json-patch+json")
|
||||
: content.has("text/json") ? content.get("text/json") : null;
|
||||
if (jsonContent == null) return null;
|
||||
|
||||
JsonNode schema = jsonContent.get("schema");
|
||||
if (schema == null || !schema.has("$ref")) return null;
|
||||
|
||||
String schemaRef = schema.get("$ref").asText();
|
||||
String fullSchemaName = schemaRef.substring(schemaRef.lastIndexOf("/") + 1);
|
||||
String className = fullSchemaName.contains(".")
|
||||
? fullSchemaName.substring(fullSchemaName.lastIndexOf(".") + 1)
|
||||
: fullSchemaName;
|
||||
|
||||
JsonNode schemaDef = swagger.at("/components/schemas/" + fullSchemaName);
|
||||
if (schemaDef.isMissingNode()) return null;
|
||||
|
||||
String projectRoot = System.getProperty("user.dir");
|
||||
String filePath = projectRoot + "/" + DTO_BASE_PATH + "/" + className + ".java";
|
||||
|
||||
if (!force && Files.exists(Path.of(filePath))) {
|
||||
log.info("Request DTO already exists, skipping: {}", filePath);
|
||||
return DTO_PACKAGE + "." + className;
|
||||
}
|
||||
|
||||
String javaCode = generateDtoCode(className, schemaDef);
|
||||
Files.createDirectories(Path.of(filePath).getParent());
|
||||
Files.writeString(Path.of(filePath), javaCode, StandardCharsets.UTF_8);
|
||||
log.info("Request DTO generated: {}", filePath);
|
||||
|
||||
return DTO_PACKAGE + "." + className;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to generate request body DTO for {} {}: {}", webclientBean, externalPath, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private JsonNode loadSwaggerJson(String resourcePath) {
|
||||
try {
|
||||
ClassPathResource resource = new ClassPathResource(resourcePath);
|
||||
try (InputStream is = resource.getInputStream()) {
|
||||
return objectMapper.readTree(is);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to load swagger file: {}", resourcePath, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String generateDtoCode(String className, JsonNode schema) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("package ").append(DTO_PACKAGE).append(";\n\n");
|
||||
sb.append("import io.swagger.v3.oas.annotations.media.Schema;\n");
|
||||
sb.append("import lombok.Getter;\n\n");
|
||||
|
||||
String title = schema.has("title") ? schema.get("title").asText() : className;
|
||||
sb.append("/**\n");
|
||||
sb.append(" * S&P Global API 응답 스키마 (Swagger 문서용)\n");
|
||||
sb.append(" * 이 클래스는 자동 생성되었습니다. 직접 수정하지 마세요.\n");
|
||||
sb.append(" */\n");
|
||||
sb.append("@Getter\n");
|
||||
sb.append("@Schema(description = \"").append(title).append("\")\n");
|
||||
sb.append("public class ").append(className).append(" {\n");
|
||||
|
||||
JsonNode properties = schema.get("properties");
|
||||
if (properties != null) {
|
||||
Iterator<Map.Entry<String, JsonNode>> fields = properties.fields();
|
||||
while (fields.hasNext()) {
|
||||
Map.Entry<String, JsonNode> field = fields.next();
|
||||
String fieldName = field.getKey();
|
||||
JsonNode fieldSchema = field.getValue();
|
||||
|
||||
String javaType = resolveJavaType(fieldSchema);
|
||||
String example = getExampleValue(fieldSchema);
|
||||
|
||||
sb.append(" @Schema(description = \"").append(fieldName).append("\"");
|
||||
if (example != null) {
|
||||
sb.append(", example = \"").append(example).append("\"");
|
||||
}
|
||||
sb.append(")\n");
|
||||
sb.append(" private ").append(javaType).append(" ").append(fieldName).append(";\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("}\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String resolveJavaType(JsonNode fieldSchema) {
|
||||
String type = fieldSchema.has("type") ? fieldSchema.get("type").asText() : "string";
|
||||
String format = fieldSchema.has("format") ? fieldSchema.get("format").asText() : "";
|
||||
|
||||
return switch (type) {
|
||||
case "integer" -> "int64".equals(format) ? "Long" : "Integer";
|
||||
case "number" -> "Double";
|
||||
case "boolean" -> "Boolean";
|
||||
case "array" -> "java.util.List<Object>";
|
||||
default -> "String";
|
||||
};
|
||||
}
|
||||
|
||||
private String getExampleValue(JsonNode fieldSchema) {
|
||||
String type = fieldSchema.has("type") ? fieldSchema.get("type").asText() : "string";
|
||||
return switch (type) {
|
||||
case "integer" -> "0";
|
||||
case "number" -> "0.0";
|
||||
case "boolean" -> "false";
|
||||
case "array" -> null;
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
}
|
||||
3853
src/main/resources/swagger/swagger_aisapi.json
Normal file
3853
src/main/resources/swagger/swagger_aisapi.json
Normal file
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
Load Diff
11075
src/main/resources/swagger/swagger_shipsapi.json
Normal file
11075
src/main/resources/swagger/swagger_shipsapi.json
Normal file
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
Load Diff
13363
src/main/resources/swagger/swagger_webservices.json
Normal file
13363
src/main/resources/swagger/swagger_webservices.json
Normal file
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
Load Diff
불러오는 중...
Reference in New Issue
Block a user