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]
|
## [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]
|
## [2026-04-08.2]
|
||||||
|
|
||||||
### 추가
|
### 추가
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export interface BypassConfigResponse {
|
|||||||
description: string;
|
description: string;
|
||||||
generated: boolean;
|
generated: boolean;
|
||||||
generatedAt: string | null;
|
generatedAt: string | null;
|
||||||
|
responseSchemaClass: string | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
params: BypassParamDto[];
|
params: BypassParamDto[];
|
||||||
|
|||||||
@ -14,6 +14,7 @@ interface BypassConfig {
|
|||||||
domainName: string;
|
domainName: string;
|
||||||
endpointName: string;
|
endpointName: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
webclientBean: string;
|
||||||
httpMethod: string;
|
httpMethod: string;
|
||||||
externalPath: string;
|
externalPath: string;
|
||||||
description: string;
|
description: string;
|
||||||
@ -36,15 +37,18 @@ const METHOD_COLORS: Record<string, string> = {
|
|||||||
DELETE: 'bg-red-100 text-red-700',
|
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 {
|
function buildSwaggerDeepLink(config: BypassConfig): string {
|
||||||
// Swagger UI deep link: #/{Tag}/{operationId}
|
const group = SWAGGER_GROUP_MAP[config.webclientBean] ?? '3-1.%20Ships%20API';
|
||||||
// Tag = domainName 첫글자 대문자 (예: compliance → Compliance)
|
const base = `/snp-global/swagger-ui/index.html?urls.primaryName=${group}`;
|
||||||
// operationId = get{EndpointName}Data (SpringDoc 기본 패턴)
|
|
||||||
const tag = config.domainName.charAt(0).toUpperCase() + config.domainName.slice(1);
|
const tag = config.domainName.charAt(0).toUpperCase() + config.domainName.slice(1);
|
||||||
const operationId = `get${config.endpointName}Data`;
|
const operationId = `get${config.endpointName}Data`;
|
||||||
return `${SWAGGER_BASE}#/${tag}/${operationId}`;
|
return `${base}#/${tag}/${operationId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BypassCatalog() {
|
export default function BypassCatalog() {
|
||||||
@ -98,7 +102,7 @@ export default function BypassCatalog() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href={SWAGGER_BASE}
|
href="/snp-global/swagger-ui/index.html"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
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"
|
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.Info;
|
||||||
import io.swagger.v3.oas.models.info.License;
|
import io.swagger.v3.oas.models.info.License;
|
||||||
import io.swagger.v3.oas.models.servers.Server;
|
import io.swagger.v3.oas.models.servers.Server;
|
||||||
|
import io.swagger.v3.oas.models.tags.Tag;
|
||||||
import org.springdoc.core.models.GroupedOpenApi;
|
import org.springdoc.core.models.GroupedOpenApi;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
@ -12,6 +13,8 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Swagger/OpenAPI 3.0 설정
|
* Swagger/OpenAPI 3.0 설정
|
||||||
@ -41,17 +44,16 @@ public class SwaggerConfig {
|
|||||||
@ConditionalOnProperty(name = "app.environment", havingValue = "dev", matchIfMissing = true)
|
@ConditionalOnProperty(name = "app.environment", havingValue = "dev", matchIfMissing = true)
|
||||||
public GroupedOpenApi bypassConfigApi() {
|
public GroupedOpenApi bypassConfigApi() {
|
||||||
return GroupedOpenApi.builder()
|
return GroupedOpenApi.builder()
|
||||||
.group("2. Bypass Config")
|
.group("2. API Config")
|
||||||
.pathsToMatch("/api/bypass-config/**")
|
.pathsToMatch("/api/bypass-config/**")
|
||||||
.addOpenApiCustomizer(openApi -> openApi.info(new Info()
|
.addOpenApiCustomizer(openApi -> openApi.info(new Info()
|
||||||
.title("Bypass Config API")
|
.title("API Config")
|
||||||
.description("Bypass API 설정 및 코드 생성 관리 API")
|
.description("API 설정 및 코드 생성 관리")
|
||||||
.version("v1.0.0")))
|
.version("v1.0.0")))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(name = "app.environment", havingValue = "dev", matchIfMissing = true)
|
|
||||||
public GroupedOpenApi screeningGuideApi() {
|
public GroupedOpenApi screeningGuideApi() {
|
||||||
return GroupedOpenApi.builder()
|
return GroupedOpenApi.builder()
|
||||||
.group("4. Screening Guide")
|
.group("4. Screening Guide")
|
||||||
@ -64,16 +66,59 @@ public class SwaggerConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@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()
|
return GroupedOpenApi.builder()
|
||||||
.group("3. Bypass API")
|
.group(group)
|
||||||
.pathsToMatch("/api/**")
|
.pathsToMatch("/api/**")
|
||||||
.pathsToExclude("/api/bypass-config/**", "/api/screening-guide/**", "/api/bypass-account/**")
|
.pathsToExclude("/api/bypass-config/**", "/api/screening-guide/**", "/api/bypass-account/**")
|
||||||
.addOpenApiCustomizer(openApi -> {
|
.addOpenApiCustomizer(openApi -> {
|
||||||
openApi.info(new Info()
|
openApi.info(new Info()
|
||||||
.title("Bypass API")
|
.title(group)
|
||||||
.description("S&P Global 선박/해운 데이터를 제공합니다.")
|
.description(description)
|
||||||
.version("v1.0.0"));
|
.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();
|
.build();
|
||||||
}
|
}
|
||||||
@ -82,11 +127,11 @@ public class SwaggerConfig {
|
|||||||
@ConditionalOnProperty(name = "app.environment", havingValue = "dev", matchIfMissing = true)
|
@ConditionalOnProperty(name = "app.environment", havingValue = "dev", matchIfMissing = true)
|
||||||
public GroupedOpenApi bypassAccountApi() {
|
public GroupedOpenApi bypassAccountApi() {
|
||||||
return GroupedOpenApi.builder()
|
return GroupedOpenApi.builder()
|
||||||
.group("5. Bypass Account")
|
.group("5. Account Management")
|
||||||
.pathsToMatch("/api/bypass-account/**")
|
.pathsToMatch("/api/bypass-account/**")
|
||||||
.addOpenApiCustomizer(openApi -> openApi.info(new Info()
|
.addOpenApiCustomizer(openApi -> openApi.info(new Info()
|
||||||
.title("Bypass Account Management API")
|
.title("Account Management API")
|
||||||
.description("Bypass API 계정 및 신청 관리 API")
|
.description("API 계정 및 신청 관리")
|
||||||
.version("v1.0.0")))
|
.version("v1.0.0")))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
@ -123,10 +168,10 @@ public class SwaggerConfig {
|
|||||||
S&P Global Maritime 데이터 서비스 REST API 문서입니다.
|
S&P Global Maritime 데이터 서비스 REST API 문서입니다.
|
||||||
|
|
||||||
### 제공 API
|
### 제공 API
|
||||||
- **Bypass API**: S&P Global 선박/해운 데이터 조회
|
- **Ships / AIS / Web Services API**: S&P Global 선박/해운 데이터 조회
|
||||||
- **Bypass Config API**: Bypass API 설정 관리
|
- **API Config**: API 설정 관리
|
||||||
- **Screening Guide API**: Risk & Compliance 스크리닝 가이드
|
- **Screening Guide API**: Risk & Compliance 스크리닝 가이드
|
||||||
- **Bypass Account API**: API 계정 관리
|
- **Account Management API**: API 계정 관리
|
||||||
|
|
||||||
### 버전 정보
|
### 버전 정보
|
||||||
- API Version: v1.0.0
|
- API Version: v1.0.0
|
||||||
|
|||||||
@ -34,6 +34,9 @@ public class BypassConfigRequest {
|
|||||||
/** 설명 */
|
/** 설명 */
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
|
/** Swagger 응답 스키마 DTO 클래스 (FQCN) */
|
||||||
|
private String responseSchemaClass;
|
||||||
|
|
||||||
/** 파라미터 목록 */
|
/** 파라미터 목록 */
|
||||||
private List<BypassParamDto> params;
|
private List<BypassParamDto> params;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,6 +40,9 @@ public class BypassConfigResponse {
|
|||||||
/** 설명 */
|
/** 설명 */
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
|
/** Swagger 응답 스키마 DTO 클래스 (FQCN) */
|
||||||
|
private String responseSchemaClass;
|
||||||
|
|
||||||
/** 코드 생성 완료 여부 */
|
/** 코드 생성 완료 여부 */
|
||||||
private Boolean generated;
|
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")
|
@Column(name = "generated_at")
|
||||||
private LocalDateTime generatedAt;
|
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;
|
import com.snp.batch.jobs.web.compliance.service.CompliancesByImosService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compliance bypass API
|
* Compliance API
|
||||||
* S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
* S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/compliance")
|
@RequestMapping("/api/compliance")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Tag(name = "Compliance", description = "[Service API] Compliance bypass API")
|
@Tag(name = "Compliance", description = "[Service API] Compliance API")
|
||||||
public class ComplianceController extends BaseBypassController {
|
public class ComplianceController extends BaseBypassController {
|
||||||
|
|
||||||
private final CompliancesByImosService compliancesByImosService;
|
private final CompliancesByImosService compliancesByImosService;
|
||||||
@ -29,6 +29,16 @@ public class ComplianceController extends BaseBypassController {
|
|||||||
summary = "IMO 기반 선박 규정준수 조회",
|
summary = "IMO 기반 선박 규정준수 조회",
|
||||||
description = "Gets details of the IMOs of ships with full compliance details that match given IMOs"
|
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")
|
@GetMapping("/CompliancesByImos")
|
||||||
public ResponseEntity<JsonNode> getCompliancesByImosData(@Parameter(description = "Comma separated IMOs up to a total of 100", example = "9876543")
|
public ResponseEntity<JsonNode> getCompliancesByImosData(@Parameter(description = "Comma separated IMOs up to a total of 100", example = "9876543")
|
||||||
@RequestParam(required = true) String imos) {
|
@RequestParam(required = true) String imos) {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IMO 기반 선박 규정준수 조회 bypass 서비스
|
* IMO 기반 선박 규정준수 조회 서비스
|
||||||
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||||
*/
|
*/
|
||||||
@Service
|
@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;
|
import com.snp.batch.jobs.web.risk.service.UpdatedComplianceListService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Risk bypass API
|
* Risk API
|
||||||
* S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
* S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/risk")
|
@RequestMapping("/api/risk")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Tag(name = "Risk", description = "[Service API] Risk bypass API")
|
@Tag(name = "Risk", description = "[Service API] Risk API")
|
||||||
public class RiskController extends BaseBypassController {
|
public class RiskController extends BaseBypassController {
|
||||||
|
|
||||||
private final RisksByImosService risksByImosService;
|
private final RisksByImosService risksByImosService;
|
||||||
@ -31,6 +31,16 @@ public class RiskController extends BaseBypassController {
|
|||||||
summary = "IMO 기반 선박 위험지표 조회",
|
summary = "IMO 기반 선박 위험지표 조회",
|
||||||
description = "Gets details of the IMOs of all ships with risk updates as a collection"
|
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")
|
@GetMapping("/RisksByImos")
|
||||||
public ResponseEntity<JsonNode> getRisksByImosData(@Parameter(description = "Comma separated IMOs up to a total of 100", example = "9876543")
|
public ResponseEntity<JsonNode> getRisksByImosData(@Parameter(description = "Comma separated IMOs up to a total of 100", example = "9876543")
|
||||||
@RequestParam(required = true) String imos) {
|
@RequestParam(required = true) String imos) {
|
||||||
@ -41,6 +51,16 @@ public class RiskController extends BaseBypassController {
|
|||||||
summary = "기간 내 변경된 위험지표 조회",
|
summary = "기간 내 변경된 위험지표 조회",
|
||||||
description = "Gets details of the IMOs of all ships with risk updates"
|
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")
|
@GetMapping("/UpdatedRiskList")
|
||||||
public ResponseEntity<JsonNode> getUpdatedComplianceListData(@Parameter(description = "Time/seconds are optional", example = "2026-03-30T07:01:27.000Z")
|
public ResponseEntity<JsonNode> getUpdatedComplianceListData(@Parameter(description = "Time/seconds are optional", example = "2026-03-30T07:01:27.000Z")
|
||||||
@RequestParam(required = true) String fromDate,
|
@RequestParam(required = true) String fromDate,
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IMO 기반 선박 위험지표 조회 bypass 서비스
|
* IMO 기반 선박 위험지표 조회 서비스
|
||||||
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 기간 내 변경된 위험지표 조회 bypass 서비스
|
* 기간 내 변경된 위험지표 조회 서비스
|
||||||
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
|||||||
@ -14,13 +14,13 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import com.snp.batch.jobs.web.ship.service.GetShipDataByIHSLRorIMOService;
|
import com.snp.batch.jobs.web.ship.service.GetShipDataByIHSLRorIMOService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ship bypass API
|
* Ship API
|
||||||
* S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
* S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/ship")
|
@RequestMapping("/api/ship")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Tag(name = "Ship", description = "[Ship API] Ship bypass API")
|
@Tag(name = "Ship", description = "[Ship API] Ship API")
|
||||||
public class ShipController extends BaseBypassController {
|
public class ShipController extends BaseBypassController {
|
||||||
|
|
||||||
private final GetShipDataByIHSLRorIMOService getShipDataByIHSLRorIMOService;
|
private final GetShipDataByIHSLRorIMOService getShipDataByIHSLRorIMOService;
|
||||||
@ -29,6 +29,16 @@ public class ShipController extends BaseBypassController {
|
|||||||
summary = "IMO 기반 선박제원정보 조회",
|
summary = "IMO 기반 선박제원정보 조회",
|
||||||
description = "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")
|
@GetMapping("/GetShipDataByIHSLRorIMO")
|
||||||
public ResponseEntity<JsonNode> getGetShipDataByIHSLRorIMOData(@Parameter(description = "", example = "9876543")
|
public ResponseEntity<JsonNode> getGetShipDataByIHSLRorIMOData(@Parameter(description = "", example = "9876543")
|
||||||
@RequestParam(required = true) String ihslrOrImo) {
|
@RequestParam(required = true) String ihslrOrImo) {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IMO 기반 선박제원정보 조회 bypass 서비스
|
* IMO 기반 선박제원정보 조회 서비스
|
||||||
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.snp.batch.service;
|
|||||||
import com.snp.batch.global.dto.CodeGenerationResult;
|
import com.snp.batch.global.dto.CodeGenerationResult;
|
||||||
import com.snp.batch.global.model.BypassApiConfig;
|
import com.snp.batch.global.model.BypassApiConfig;
|
||||||
import com.snp.batch.global.model.BypassApiParam;
|
import com.snp.batch.global.model.BypassApiParam;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@ -11,8 +12,10 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -23,8 +26,11 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class BypassCodeGenerator {
|
public class BypassCodeGenerator {
|
||||||
|
|
||||||
|
private final SwaggerSchemaGenerator swaggerSchemaGenerator;
|
||||||
|
|
||||||
private static final String BASE_PACKAGE = "com.snp.batch.jobs.web";
|
private static final String BASE_PACKAGE = "com.snp.batch.jobs.web";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,6 +49,26 @@ public class BypassCodeGenerator {
|
|||||||
|
|
||||||
List<String> servicePaths = new ArrayList<>();
|
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) {
|
for (BypassApiConfig config : configs) {
|
||||||
String endpointName = config.getEndpointName();
|
String endpointName = config.getEndpointName();
|
||||||
String servicePath = basePath + "/service/" + endpointName + "Service.java";
|
String servicePath = basePath + "/service/" + endpointName + "Service.java";
|
||||||
@ -58,7 +84,7 @@ public class BypassCodeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Controller: 모든 엔드포인트를 합치므로 항상 재생성
|
// Controller: 모든 엔드포인트를 합치므로 항상 재생성
|
||||||
String controllerCode = generateControllerCode(domain, configs);
|
String controllerCode = generateControllerCode(domain, configs, responseSchemaMap, requestBodySchemaMap);
|
||||||
String domainCapitalized = capitalize(domain);
|
String domainCapitalized = capitalize(domain);
|
||||||
Path controllerFilePath = writeFile(
|
Path controllerFilePath = writeFile(
|
||||||
basePath + "/controller/" + domainCapitalized + "Controller.java", controllerCode, true);
|
basePath + "/controller/" + domainCapitalized + "Controller.java", controllerCode, true);
|
||||||
@ -83,9 +109,23 @@ public class BypassCodeGenerator {
|
|||||||
String serviceClass = endpointName + "Service";
|
String serviceClass = endpointName + "Service";
|
||||||
boolean isPost = "POST".equalsIgnoreCase(config.getHttpMethod());
|
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 methodName = "get" + endpointName + "Data";
|
||||||
String fetchMethod = buildFetchMethodCall(config, params, isPost);
|
String fetchMethod = buildFetchMethodCall(config, effectiveParams, isPost);
|
||||||
String methodParams = buildMethodParams(params);
|
String methodParams = buildMethodParams(effectiveParams);
|
||||||
|
|
||||||
return """
|
return """
|
||||||
package {{PACKAGE}};
|
package {{PACKAGE}};
|
||||||
@ -98,7 +138,7 @@ public class BypassCodeGenerator {
|
|||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {{DISPLAY_NAME}} bypass 서비스
|
* {{DISPLAY_NAME}} 서비스
|
||||||
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
* 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@ -133,7 +173,7 @@ public class BypassCodeGenerator {
|
|||||||
* Controller 코드 생성 (RAW 모드).
|
* Controller 코드 생성 (RAW 모드).
|
||||||
* 모든 엔드포인트가 ResponseEntity<JsonNode>를 반환합니다 (외부 API 원본 JSON 그대로).
|
* 모든 엔드포인트가 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 packageName = BASE_PACKAGE + "." + domain + ".controller";
|
||||||
String servicePackage = BASE_PACKAGE + "." + domain + ".service";
|
String servicePackage = BASE_PACKAGE + "." + domain + ".service";
|
||||||
String domainCap = capitalize(domain);
|
String domainCap = capitalize(domain);
|
||||||
@ -157,7 +197,7 @@ public class BypassCodeGenerator {
|
|||||||
.anyMatch(c -> c.getParams().stream().anyMatch(p -> "PATH".equalsIgnoreCase(p.getParamIn())));
|
.anyMatch(c -> c.getParams().stream().anyMatch(p -> "PATH".equalsIgnoreCase(p.getParamIn())));
|
||||||
boolean anyQuery = configs.stream()
|
boolean anyQuery = configs.stream()
|
||||||
.anyMatch(c -> c.getParams().stream().anyMatch(p -> "QUERY".equalsIgnoreCase(p.getParamIn())));
|
.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())));
|
.anyMatch(c -> c.getParams().stream().anyMatch(p -> "BODY".equalsIgnoreCase(p.getParamIn())));
|
||||||
|
|
||||||
if (anyPost) importSet.add("import org.springframework.web.bind.annotation.PostMapping;");
|
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 tagPrefix = getTagPrefix(configs.get(0).getWebclientBean());
|
||||||
String tagDescription = tagPrefix + " " + domainCap + " bypass API";
|
String tagDescription = tagPrefix + " " + domainCap + " API";
|
||||||
|
|
||||||
// 엔드포인트 메서드 목록
|
// 엔드포인트 메서드 목록
|
||||||
StringBuilder methods = new StringBuilder();
|
StringBuilder methods = new StringBuilder();
|
||||||
@ -191,10 +231,25 @@ public class BypassCodeGenerator {
|
|||||||
String serviceField = Character.toLowerCase(serviceClass.charAt(0)) + serviceClass.substring(1);
|
String serviceField = Character.toLowerCase(serviceClass.charAt(0)) + serviceClass.substring(1);
|
||||||
boolean isPost = "POST".equalsIgnoreCase(config.getHttpMethod());
|
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 mappingAnnotation = isPost ? "@PostMapping" : "@GetMapping";
|
||||||
String mappingPath = buildMappingPath(config.getParams(), config.getExternalPath());
|
String mappingPath = buildMappingPath(ctrlParams, config.getExternalPath());
|
||||||
String paramAnnotations = buildControllerParamAnnotations(config.getParams());
|
String reqBodySchema = requestBodySchemaMap.get(config.getId());
|
||||||
String serviceCallArgs = buildServiceCallArgs(config.getParams());
|
String paramAnnotations = buildControllerParamAnnotations(ctrlParams, reqBodySchema);
|
||||||
|
String serviceCallArgs = buildServiceCallArgs(ctrlParams);
|
||||||
String methodName = "get" + endpointName + "Data";
|
String methodName = "get" + endpointName + "Data";
|
||||||
|
|
||||||
methods.append("\n");
|
methods.append("\n");
|
||||||
@ -205,6 +260,20 @@ public class BypassCodeGenerator {
|
|||||||
: config.getDisplayName() + " 데이터를 요청하고 응답을 그대로 반환합니다.";
|
: config.getDisplayName() + " 데이터를 요청하고 응답을 그대로 반환합니다.";
|
||||||
methods.append(" description = \"").append(opDescription).append("\"\n");
|
methods.append(" description = \"").append(opDescription).append("\"\n");
|
||||||
methods.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(" ").append(mappingAnnotation).append(mappingPath).append("\n");
|
||||||
methods.append(" public ResponseEntity<JsonNode> ").append(methodName).append("(");
|
methods.append(" public ResponseEntity<JsonNode> ").append(methodName).append("(");
|
||||||
if (!paramAnnotations.isEmpty()) {
|
if (!paramAnnotations.isEmpty()) {
|
||||||
@ -219,7 +288,7 @@ public class BypassCodeGenerator {
|
|||||||
return "package " + packageName + ";\n\n"
|
return "package " + packageName + ";\n\n"
|
||||||
+ importsStr + "\n\n"
|
+ importsStr + "\n\n"
|
||||||
+ "/**\n"
|
+ "/**\n"
|
||||||
+ " * " + domainCap + " bypass API\n"
|
+ " * " + domainCap + " API\n"
|
||||||
+ " * S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환\n"
|
+ " * S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환\n"
|
||||||
+ " */\n"
|
+ " */\n"
|
||||||
+ "@RestController\n"
|
+ "@RestController\n"
|
||||||
@ -263,7 +332,10 @@ public class BypassCodeGenerator {
|
|||||||
private String buildMethodParams(List<BypassApiParam> params) {
|
private String buildMethodParams(List<BypassApiParam> params) {
|
||||||
return params.stream()
|
return params.stream()
|
||||||
.sorted((a, b) -> Integer.compare(a.getSortOrder(), b.getSortOrder()))
|
.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(", "));
|
.collect(Collectors.joining(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +346,7 @@ public class BypassCodeGenerator {
|
|||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildControllerParamAnnotations(List<BypassApiParam> params) {
|
private String buildControllerParamAnnotations(List<BypassApiParam> params, String requestBodySchemaClass) {
|
||||||
if (params.isEmpty()) {
|
if (params.isEmpty()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -290,8 +362,17 @@ public class BypassCodeGenerator {
|
|||||||
return switch (p.getParamIn().toUpperCase()) {
|
return switch (p.getParamIn().toUpperCase()) {
|
||||||
case "PATH" -> "@Parameter(description = \"" + description + "\", example = \"" + example + "\")\n"
|
case "PATH" -> "@Parameter(description = \"" + description + "\", example = \"" + example + "\")\n"
|
||||||
+ " @PathVariable " + javaType + " " + paramName;
|
+ " @PathVariable " + javaType + " " + paramName;
|
||||||
case "BODY" -> "@Parameter(description = \"" + description + "\", example = \"" + example + "\")\n"
|
case "BODY" -> {
|
||||||
+ " @RequestBody " + javaType + " " + paramName;
|
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 -> {
|
default -> {
|
||||||
String required = Boolean.TRUE.equals(p.getRequired()) ? "true" : "false";
|
String required = Boolean.TRUE.equals(p.getRequired()) ? "true" : "false";
|
||||||
yield "@Parameter(description = \"" + description + "\", example = \"" + example + "\")\n"
|
yield "@Parameter(description = \"" + description + "\", example = \"" + example + "\")\n"
|
||||||
|
|||||||
@ -80,6 +80,7 @@ public class BypassConfigService {
|
|||||||
config.setExternalPath(request.getExternalPath());
|
config.setExternalPath(request.getExternalPath());
|
||||||
config.setHttpMethod(request.getHttpMethod());
|
config.setHttpMethod(request.getHttpMethod());
|
||||||
config.setDescription(request.getDescription());
|
config.setDescription(request.getDescription());
|
||||||
|
config.setResponseSchemaClass(request.getResponseSchemaClass());
|
||||||
|
|
||||||
// params 교체: clear → flush(DELETE 실행) → 새로 추가
|
// params 교체: clear → flush(DELETE 실행) → 새로 추가
|
||||||
config.getParams().clear();
|
config.getParams().clear();
|
||||||
@ -131,6 +132,7 @@ public class BypassConfigService {
|
|||||||
.externalPath(config.getExternalPath())
|
.externalPath(config.getExternalPath())
|
||||||
.httpMethod(config.getHttpMethod())
|
.httpMethod(config.getHttpMethod())
|
||||||
.description(config.getDescription())
|
.description(config.getDescription())
|
||||||
|
.responseSchemaClass(config.getResponseSchemaClass())
|
||||||
.generated(config.getGenerated())
|
.generated(config.getGenerated())
|
||||||
.generatedAt(config.getGeneratedAt())
|
.generatedAt(config.getGeneratedAt())
|
||||||
.createdAt(config.getCreatedAt())
|
.createdAt(config.getCreatedAt())
|
||||||
@ -148,6 +150,7 @@ public class BypassConfigService {
|
|||||||
.externalPath(request.getExternalPath())
|
.externalPath(request.getExternalPath())
|
||||||
.httpMethod(request.getHttpMethod() != null ? request.getHttpMethod() : "GET")
|
.httpMethod(request.getHttpMethod() != null ? request.getHttpMethod() : "GET")
|
||||||
.description(request.getDescription())
|
.description(request.getDescription())
|
||||||
|
.responseSchemaClass(request.getResponseSchemaClass())
|
||||||
.build();
|
.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