From 9667d3a0ac90e9710e915b62ddf2483185ede796 Mon Sep 17 00:00:00 2001 From: HYOJIN Date: Fri, 27 Mar 2026 09:39:23 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=A4=91=EC=B2=A9=20JSON=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=ED=8C=8C=EC=8B=B1=20=EB=8C=80=EC=9D=91=20=EB=B0=8F?= =?UTF-8?q?=20DTO=20import=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95=20(#6?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 백엔드: - generateDtoCode에서 List/Map 타입 import 자동 추가 - JsonSchemaParser에 targetField 파라미터 추가 (특정 필드 내부만 파싱) - 배열 내 객체 감지 시 List<{FieldName}Item> 타입으로 추론 프론트엔드: - JSON 샘플 파싱 시 "전체 JSON / 특정 필드 내부" 선택 옵션 추가 - targetField 기본값 "data" (페이징 래퍼 대응) Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/api/bypassApi.ts | 5 +- .../components/bypass/BypassStepFields.tsx | 47 ++++++- .../controller/BypassConfigController.java | 8 +- .../controller/ComplianceController.java | 85 ++++++++++++ .../dto/ComplianceValuesMeaningDto.java | 23 ++++ .../compliance/dto/CompliancesByImosDto.java | 122 ++++++++++++++++++ .../dto/PagedUpdatedComplianceListDto.java | 45 +++++++ .../dto/UpdatedComplianceListDto.java | 122 ++++++++++++++++++ .../ComplianceValuesMeaningService.java | 34 +++++ .../service/CompliancesByImosService.java | 35 +++++ .../PagedUpdatedComplianceListService.java | 38 ++++++ .../service/UpdatedComplianceListService.java | 36 ++++++ .../batch/service/BypassCodeGenerator.java | 11 ++ .../snp/batch/service/JsonSchemaParser.java | 57 +++++++- 14 files changed, 659 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/snp/batch/jobs/web/compliance/controller/ComplianceController.java create mode 100644 src/main/java/com/snp/batch/jobs/web/compliance/dto/ComplianceValuesMeaningDto.java create mode 100644 src/main/java/com/snp/batch/jobs/web/compliance/dto/CompliancesByImosDto.java create mode 100644 src/main/java/com/snp/batch/jobs/web/compliance/dto/PagedUpdatedComplianceListDto.java create mode 100644 src/main/java/com/snp/batch/jobs/web/compliance/dto/UpdatedComplianceListDto.java create mode 100644 src/main/java/com/snp/batch/jobs/web/compliance/service/ComplianceValuesMeaningService.java create mode 100644 src/main/java/com/snp/batch/jobs/web/compliance/service/CompliancesByImosService.java create mode 100644 src/main/java/com/snp/batch/jobs/web/compliance/service/PagedUpdatedComplianceListService.java create mode 100644 src/main/java/com/snp/batch/jobs/web/compliance/service/UpdatedComplianceListService.java diff --git a/frontend/src/api/bypassApi.ts b/frontend/src/api/bypassApi.ts index deec912..a90532b 100644 --- a/frontend/src/api/bypassApi.ts +++ b/frontend/src/api/bypassApi.ts @@ -117,8 +117,9 @@ export const bypassApi = { deleteJson>(`${BASE}/${id}`), generateCode: (id: number, force = false) => postJson>(`${BASE}/${id}/generate?force=${force}`), - parseJson: async (jsonSample: string): Promise> => { - const res = await fetch(`${BASE}/parse-json`, { + parseJson: async (jsonSample: string, targetField?: string): Promise> => { + const params = targetField ? `?targetField=${encodeURIComponent(targetField)}` : ''; + const res = await fetch(`${BASE}/parse-json${params}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: jsonSample, // 이미 JSON 문자열이므로 JSON.stringify 하지 않음 diff --git a/frontend/src/components/bypass/BypassStepFields.tsx b/frontend/src/components/bypass/BypassStepFields.tsx index cacc8f3..6027b18 100644 --- a/frontend/src/components/bypass/BypassStepFields.tsx +++ b/frontend/src/components/bypass/BypassStepFields.tsx @@ -26,6 +26,8 @@ export default function BypassStepFields({ fields, onChange }: BypassStepFieldsP const [jsonSample, setJsonSample] = useState(''); const [parsing, setParsing] = useState(false); const [parseError, setParseError] = useState(null); + const [parseMode, setParseMode] = useState<'root' | 'field'>('root'); + const [targetField, setTargetField] = useState('data'); const handleAdd = () => { onChange([...fields, createEmptyField(fields.length)]); @@ -53,7 +55,8 @@ export default function BypassStepFields({ fields, onChange }: BypassStepFieldsP setParseError(null); setParsing(true); try { - const result = await bypassApi.parseJson(jsonSample); + const tf = parseMode === 'field' ? targetField : undefined; + const result = await bypassApi.parseJson(jsonSample.trim(), tf); if (result.success && result.data) { onChange(result.data); setActiveTab('manual'); @@ -220,6 +223,48 @@ export default function BypassStepFields({ fields, onChange }: BypassStepFieldsP placeholder={'{\n "riskScore": 85,\n "imoNumber": "1234567",\n "vesselName": "EXAMPLE SHIP"\n}'} className="w-full px-3 py-2 text-sm font-mono rounded-lg border border-wing-border bg-wing-card text-wing-text placeholder:text-wing-muted focus:outline-none focus:ring-2 focus:ring-wing-accent/50 resize-none" /> + + {/* 파싱 옵션 */} +
+ +
+ + +
+ {parseMode === 'field' && ( +
+ setTargetField(e.target.value)} + placeholder="필드명 (예: data, results, items)" + className="px-3 py-1.5 text-sm rounded-lg border border-wing-border bg-wing-surface text-wing-text placeholder:text-wing-muted focus:outline-none focus:ring-1 focus:ring-wing-accent/50" + /> + + 해당 필드가 배열이면 첫 번째 요소를, 객체면 그 내부를 파싱합니다. + +
+ )} +
+ {parseError && (

{parseError}

)} diff --git a/src/main/java/com/snp/batch/global/controller/BypassConfigController.java b/src/main/java/com/snp/batch/global/controller/BypassConfigController.java index bea3101..4b56b65 100644 --- a/src/main/java/com/snp/batch/global/controller/BypassConfigController.java +++ b/src/main/java/com/snp/batch/global/controller/BypassConfigController.java @@ -108,12 +108,14 @@ public class BypassConfigController { @Operation( summary = "JSON 샘플 파싱", - description = "JSON 샘플에서 DTO 필드 목록을 추출합니다." + description = "JSON 샘플에서 DTO 필드 목록을 추출합니다. targetField를 지정하면 해당 필드 내부를 파싱합니다. 예: targetField=data → root.data[0] 내부 필드 추출." ) @PostMapping("/parse-json") - public ResponseEntity>> parseJson(@RequestBody String jsonSample) { + public ResponseEntity>> parseJson( + @RequestBody String jsonSample, + @RequestParam(required = false) String targetField) { try { - List fields = jsonSchemaParser.parse(jsonSample); + List fields = jsonSchemaParser.parse(jsonSample, targetField); return ResponseEntity.ok(ApiResponse.success(fields)); } catch (Exception e) { return ResponseEntity.badRequest().body(ApiResponse.error("JSON 파싱 실패: " + e.getMessage())); diff --git a/src/main/java/com/snp/batch/jobs/web/compliance/controller/ComplianceController.java b/src/main/java/com/snp/batch/jobs/web/compliance/controller/ComplianceController.java new file mode 100644 index 0000000..6668cb9 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/compliance/controller/ComplianceController.java @@ -0,0 +1,85 @@ +package com.snp.batch.jobs.web.compliance.controller; + +import com.snp.batch.common.web.ApiResponse; +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 java.util.List; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import com.snp.batch.jobs.web.compliance.dto.CompliancesByImosDto; +import com.snp.batch.jobs.web.compliance.service.CompliancesByImosService; +import com.snp.batch.jobs.web.compliance.dto.UpdatedComplianceListDto; +import com.snp.batch.jobs.web.compliance.service.UpdatedComplianceListService; +import com.snp.batch.jobs.web.compliance.dto.ComplianceValuesMeaningDto; +import com.snp.batch.jobs.web.compliance.service.ComplianceValuesMeaningService; +import com.snp.batch.jobs.web.compliance.dto.PagedUpdatedComplianceListDto; +import com.snp.batch.jobs.web.compliance.service.PagedUpdatedComplianceListService; + +/** + * Compliance bypass API + * S&P Maritime API에서 데이터를 실시간 조회하여 그대로 반환 + */ +@RestController +@RequestMapping("/api/compliance") +@RequiredArgsConstructor +@Tag(name = "Compliance", description = "[Service API] Compliance bypass API") +public class ComplianceController extends BaseBypassController { + + private final CompliancesByImosService compliancesByImosService; + private final UpdatedComplianceListService updatedComplianceListService; + private final ComplianceValuesMeaningService complianceValuesMeaningService; + private final PagedUpdatedComplianceListService pagedUpdatedComplianceListService; + + @Operation( + summary = "IMO 기반 Compliance 조회 조회", + description = "S&P API에서 IMO 기반 Compliance 조회 데이터를 요청하고 응답을 그대로 반환합니다." + ) + @GetMapping("/CompliancesByImos") + public ResponseEntity>> getCompliancesByImosData(@Parameter(description = "Comma separated IMOs up to a total of 100", example = "9876543") + @RequestParam(required = true) String imos) { + return execute(() -> compliancesByImosService.getCompliancesByImosData(imos)); + } + + @Operation( + summary = "기간 내 변경 Compliance 조회 조회", + description = "S&P API에서 기간 내 변경 Compliance 조회 데이터를 요청하고 응답을 그대로 반환합니다." + ) + @GetMapping("/UpdatedComplianceList") + public ResponseEntity>> getUpdatedComplianceListData(@Parameter(description = "Time/seconds are optional", example = "9876543") + @RequestParam(required = true) String fromDate, + @Parameter(description = "Time/seconds are optional. If unspecified, the current UTC date and time is used", example = "9876543") + @RequestParam(required = true) String toDate) { + return execute(() -> updatedComplianceListService.getUpdatedComplianceListData(fromDate, toDate)); + } + + @Operation( + summary = "모든 Compliance 지표 조회 조회", + description = "S&P API에서 모든 Compliance 지표 조회 데이터를 요청하고 응답을 그대로 반환합니다." + ) + @GetMapping("/ComplianceValuesMeaning") + public ResponseEntity>> getComplianceValuesMeaningData() { + return execute(() -> complianceValuesMeaningService.getComplianceValuesMeaningData()); + } + + @Operation( + summary = "PagedUpdatedComplianceList 조회", + description = "S&P API에서 PagedUpdatedComplianceList 데이터를 요청하고 응답을 그대로 반환합니다." + ) + @GetMapping("/PagedUpdatedComplianceList") + public ResponseEntity>> getPagedUpdatedComplianceListData(@Parameter(description = "Time/seconds are optional", example = "9876543") + @RequestParam(required = true) String fromDate, + @Parameter(description = "Time/seconds are optional. If unspecified, the current UTC date and time is used", example = "9876543") + @RequestParam(required = true) String toDate, + @Parameter(description = "Page number to display.", example = "9876543") + @RequestParam(required = true) String pageNumber, + @Parameter(description = "How many elements will be on the single page. Maximum allowed is 1000.", example = "9876543") + @RequestParam(required = true) String pageSize) { + return execute(() -> pagedUpdatedComplianceListService.getPagedUpdatedComplianceListData(fromDate, toDate, pageNumber, pageSize)); + } +} diff --git a/src/main/java/com/snp/batch/jobs/web/compliance/dto/ComplianceValuesMeaningDto.java b/src/main/java/com/snp/batch/jobs/web/compliance/dto/ComplianceValuesMeaningDto.java new file mode 100644 index 0000000..2df4695 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/compliance/dto/ComplianceValuesMeaningDto.java @@ -0,0 +1,23 @@ +package com.snp.batch.jobs.web.compliance.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ComplianceValuesMeaningDto { + + @JsonProperty("complianceValue") + private Integer complianceValue; + + @JsonProperty("meaning") + private String meaning; + +} diff --git a/src/main/java/com/snp/batch/jobs/web/compliance/dto/CompliancesByImosDto.java b/src/main/java/com/snp/batch/jobs/web/compliance/dto/CompliancesByImosDto.java new file mode 100644 index 0000000..d250ac7 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/compliance/dto/CompliancesByImosDto.java @@ -0,0 +1,122 @@ +package com.snp.batch.jobs.web.compliance.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CompliancesByImosDto { + + @JsonProperty("lrimoShipNo") + private String lrimoShipNo; + + @JsonProperty("dateAmended") + private String dateAmended; + + @JsonProperty("legalOverall") + private Integer legalOverall; + + @JsonProperty("shipBESSanctionList") + private Integer shipBESSanctionList; + + @JsonProperty("shipDarkActivityIndicator") + private Integer shipDarkActivityIndicator; + + @JsonProperty("shipDetailsNoLongerMaintained") + private Integer shipDetailsNoLongerMaintained; + + @JsonProperty("shipEUSanctionList") + private Integer shipEUSanctionList; + + @JsonProperty("shipFlagDisputed") + private Integer shipFlagDisputed; + + @JsonProperty("shipFlagSanctionedCountry") + private Integer shipFlagSanctionedCountry; + + @JsonProperty("shipHistoricalFlagSanctionedCountry") + private Integer shipHistoricalFlagSanctionedCountry; + + @JsonProperty("shipOFACNonSDNSanctionList") + private Integer shipOFACNonSDNSanctionList; + + @JsonProperty("shipOFACSanctionList") + private Integer shipOFACSanctionList; + + @JsonProperty("shipOFACAdvisoryList") + private Integer shipOFACAdvisoryList; + + @JsonProperty("shipOwnerOFACSSIList") + private Integer shipOwnerOFACSSIList; + + @JsonProperty("shipOwnerAustralianSanctionList") + private Integer shipOwnerAustralianSanctionList; + + @JsonProperty("shipOwnerBESSanctionList") + private Integer shipOwnerBESSanctionList; + + @JsonProperty("shipOwnerCanadianSanctionList") + private Integer shipOwnerCanadianSanctionList; + + @JsonProperty("shipOwnerEUSanctionList") + private Integer shipOwnerEUSanctionList; + + @JsonProperty("shipOwnerFATFJurisdiction") + private Integer shipOwnerFATFJurisdiction; + + @JsonProperty("shipOwnerHistoricalOFACSanctionedCountry") + private Integer shipOwnerHistoricalOFACSanctionedCountry; + + @JsonProperty("shipOwnerOFACSanctionList") + private Integer shipOwnerOFACSanctionList; + + @JsonProperty("shipOwnerOFACSanctionedCountry") + private Integer shipOwnerOFACSanctionedCountry; + + @JsonProperty("shipOwnerParentCompanyNonCompliance") + private Integer shipOwnerParentCompanyNonCompliance; + + @JsonProperty("shipOwnerParentFATFJurisdiction") + private String shipOwnerParentFATFJurisdiction; + + @JsonProperty("shipOwnerParentOFACSanctionedCountry") + private String shipOwnerParentOFACSanctionedCountry; + + @JsonProperty("shipOwnerSwissSanctionList") + private Integer shipOwnerSwissSanctionList; + + @JsonProperty("shipOwnerUAESanctionList") + private Integer shipOwnerUAESanctionList; + + @JsonProperty("shipOwnerUNSanctionList") + private Integer shipOwnerUNSanctionList; + + @JsonProperty("shipSanctionedCountryPortCallLast12m") + private Integer shipSanctionedCountryPortCallLast12m; + + @JsonProperty("shipSanctionedCountryPortCallLast3m") + private Integer shipSanctionedCountryPortCallLast3m; + + @JsonProperty("shipSanctionedCountryPortCallLast6m") + private Integer shipSanctionedCountryPortCallLast6m; + + @JsonProperty("shipSecurityLegalDisputeEvent") + private Integer shipSecurityLegalDisputeEvent; + + @JsonProperty("shipSTSPartnerNonComplianceLast12m") + private Integer shipSTSPartnerNonComplianceLast12m; + + @JsonProperty("shipSwissSanctionList") + private Integer shipSwissSanctionList; + + @JsonProperty("shipUNSanctionList") + private Integer shipUNSanctionList; + +} diff --git a/src/main/java/com/snp/batch/jobs/web/compliance/dto/PagedUpdatedComplianceListDto.java b/src/main/java/com/snp/batch/jobs/web/compliance/dto/PagedUpdatedComplianceListDto.java new file mode 100644 index 0000000..cc75997 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/compliance/dto/PagedUpdatedComplianceListDto.java @@ -0,0 +1,45 @@ +package com.snp.batch.jobs.web.compliance.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import java.util.List; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PagedUpdatedComplianceListDto { + + @JsonProperty("pageSize") + private Integer pageSize; + + @JsonProperty("pageNumber") + private Integer pageNumber; + + @JsonProperty("firstPage") + private String firstPage; + + @JsonProperty("lastPage") + private String lastPage; + + @JsonProperty("totalPages") + private Integer totalPages; + + @JsonProperty("totalRecords") + private Integer totalRecords; + + @JsonProperty("nextPage") + private String nextPage; + + @JsonProperty("previousPage") + private String previousPage; + + @JsonProperty("data") + private List data; + +} diff --git a/src/main/java/com/snp/batch/jobs/web/compliance/dto/UpdatedComplianceListDto.java b/src/main/java/com/snp/batch/jobs/web/compliance/dto/UpdatedComplianceListDto.java new file mode 100644 index 0000000..5789c15 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/compliance/dto/UpdatedComplianceListDto.java @@ -0,0 +1,122 @@ +package com.snp.batch.jobs.web.compliance.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UpdatedComplianceListDto { + + @JsonProperty("lrimoShipNo") + private String lrimoShipNo; + + @JsonProperty("dateAmended") + private String dateAmended; + + @JsonProperty("legalOverall") + private Integer legalOverall; + + @JsonProperty("shipBESSanctionList") + private Integer shipBESSanctionList; + + @JsonProperty("shipDarkActivityIndicator") + private Integer shipDarkActivityIndicator; + + @JsonProperty("shipDetailsNoLongerMaintained") + private Integer shipDetailsNoLongerMaintained; + + @JsonProperty("shipEUSanctionList") + private Integer shipEUSanctionList; + + @JsonProperty("shipFlagDisputed") + private Integer shipFlagDisputed; + + @JsonProperty("shipFlagSanctionedCountry") + private Integer shipFlagSanctionedCountry; + + @JsonProperty("shipHistoricalFlagSanctionedCountry") + private Integer shipHistoricalFlagSanctionedCountry; + + @JsonProperty("shipOFACNonSDNSanctionList") + private Integer shipOFACNonSDNSanctionList; + + @JsonProperty("shipOFACSanctionList") + private Integer shipOFACSanctionList; + + @JsonProperty("shipOFACAdvisoryList") + private Integer shipOFACAdvisoryList; + + @JsonProperty("shipOwnerOFACSSIList") + private Integer shipOwnerOFACSSIList; + + @JsonProperty("shipOwnerAustralianSanctionList") + private Integer shipOwnerAustralianSanctionList; + + @JsonProperty("shipOwnerBESSanctionList") + private Integer shipOwnerBESSanctionList; + + @JsonProperty("shipOwnerCanadianSanctionList") + private Integer shipOwnerCanadianSanctionList; + + @JsonProperty("shipOwnerEUSanctionList") + private Integer shipOwnerEUSanctionList; + + @JsonProperty("shipOwnerFATFJurisdiction") + private Integer shipOwnerFATFJurisdiction; + + @JsonProperty("shipOwnerHistoricalOFACSanctionedCountry") + private Integer shipOwnerHistoricalOFACSanctionedCountry; + + @JsonProperty("shipOwnerOFACSanctionList") + private Integer shipOwnerOFACSanctionList; + + @JsonProperty("shipOwnerOFACSanctionedCountry") + private Integer shipOwnerOFACSanctionedCountry; + + @JsonProperty("shipOwnerParentCompanyNonCompliance") + private Integer shipOwnerParentCompanyNonCompliance; + + @JsonProperty("shipOwnerParentFATFJurisdiction") + private Integer shipOwnerParentFATFJurisdiction; + + @JsonProperty("shipOwnerParentOFACSanctionedCountry") + private Integer shipOwnerParentOFACSanctionedCountry; + + @JsonProperty("shipOwnerSwissSanctionList") + private Integer shipOwnerSwissSanctionList; + + @JsonProperty("shipOwnerUAESanctionList") + private Integer shipOwnerUAESanctionList; + + @JsonProperty("shipOwnerUNSanctionList") + private Integer shipOwnerUNSanctionList; + + @JsonProperty("shipSanctionedCountryPortCallLast12m") + private Integer shipSanctionedCountryPortCallLast12m; + + @JsonProperty("shipSanctionedCountryPortCallLast3m") + private Integer shipSanctionedCountryPortCallLast3m; + + @JsonProperty("shipSanctionedCountryPortCallLast6m") + private Integer shipSanctionedCountryPortCallLast6m; + + @JsonProperty("shipSecurityLegalDisputeEvent") + private Integer shipSecurityLegalDisputeEvent; + + @JsonProperty("shipSTSPartnerNonComplianceLast12m") + private Integer shipSTSPartnerNonComplianceLast12m; + + @JsonProperty("shipSwissSanctionList") + private Integer shipSwissSanctionList; + + @JsonProperty("shipUNSanctionList") + private Integer shipUNSanctionList; + +} diff --git a/src/main/java/com/snp/batch/jobs/web/compliance/service/ComplianceValuesMeaningService.java b/src/main/java/com/snp/batch/jobs/web/compliance/service/ComplianceValuesMeaningService.java new file mode 100644 index 0000000..7a10912 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/compliance/service/ComplianceValuesMeaningService.java @@ -0,0 +1,34 @@ +package com.snp.batch.jobs.web.compliance.service; + +import com.snp.batch.jobs.web.compliance.dto.ComplianceValuesMeaningDto; +import com.snp.batch.common.web.service.BaseBypassService; +import java.util.List; +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; + +/** + * 모든 Compliance 지표 조회 bypass 서비스 + * 외부 Maritime API에서 데이터를 실시간 조회하여 그대로 반환 + */ +@Service +public class ComplianceValuesMeaningService extends BaseBypassService { + + public ComplianceValuesMeaningService( + @Qualifier("maritimeServiceApiWebClient") WebClient webClient) { + super(webClient, "/RiskAndCompliance/ComplianceValuesMeaning", "모든 Compliance 지표 조회", + new ParameterizedTypeReference<>() {}, + new ParameterizedTypeReference<>() {}); + } + + /** + * 모든 Compliance 지표 조회 데이터를 조회합니다. + * + * @return 모든 Compliance 지표 조회 + */ + public List getComplianceValuesMeaningData() { + return fetchGetList(uri -> uri.path(getApiPath()) + .build()); + } +} diff --git a/src/main/java/com/snp/batch/jobs/web/compliance/service/CompliancesByImosService.java b/src/main/java/com/snp/batch/jobs/web/compliance/service/CompliancesByImosService.java new file mode 100644 index 0000000..7be28ee --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/compliance/service/CompliancesByImosService.java @@ -0,0 +1,35 @@ +package com.snp.batch.jobs.web.compliance.service; + +import com.snp.batch.jobs.web.compliance.dto.CompliancesByImosDto; +import com.snp.batch.common.web.service.BaseBypassService; +import java.util.List; +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; + +/** + * IMO 기반 Compliance 조회 bypass 서비스 + * 외부 Maritime API에서 데이터를 실시간 조회하여 그대로 반환 + */ +@Service +public class CompliancesByImosService extends BaseBypassService { + + public CompliancesByImosService( + @Qualifier("maritimeServiceApiWebClient") WebClient webClient) { + super(webClient, "/RiskAndCompliance/CompliancesByImos", "IMO 기반 Compliance 조회", + new ParameterizedTypeReference<>() {}, + new ParameterizedTypeReference<>() {}); + } + + /** + * IMO 기반 Compliance 조회 데이터를 조회합니다. + * + * @return IMO 기반 Compliance 조회 + */ + public List getCompliancesByImosData(String imos) { + return fetchGetList(uri -> uri.path(getApiPath()) + .queryParam("imos", imos) + .build()); + } +} diff --git a/src/main/java/com/snp/batch/jobs/web/compliance/service/PagedUpdatedComplianceListService.java b/src/main/java/com/snp/batch/jobs/web/compliance/service/PagedUpdatedComplianceListService.java new file mode 100644 index 0000000..7eb2091 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/compliance/service/PagedUpdatedComplianceListService.java @@ -0,0 +1,38 @@ +package com.snp.batch.jobs.web.compliance.service; + +import com.snp.batch.jobs.web.compliance.dto.PagedUpdatedComplianceListDto; +import com.snp.batch.common.web.service.BaseBypassService; +import java.util.List; +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; + +/** + * PagedUpdatedComplianceList bypass 서비스 + * 외부 Maritime API에서 데이터를 실시간 조회하여 그대로 반환 + */ +@Service +public class PagedUpdatedComplianceListService extends BaseBypassService { + + public PagedUpdatedComplianceListService( + @Qualifier("maritimeServiceApiWebClient") WebClient webClient) { + super(webClient, "/RiskAndCompliance/PagedUpdatedComplianceList", "PagedUpdatedComplianceList", + new ParameterizedTypeReference<>() {}, + new ParameterizedTypeReference<>() {}); + } + + /** + * PagedUpdatedComplianceList 데이터를 조회합니다. + * + * @return PagedUpdatedComplianceList + */ + public List getPagedUpdatedComplianceListData(String fromDate, String toDate, String pageNumber, String pageSize) { + return fetchGetList(uri -> uri.path(getApiPath()) + .queryParam("fromDate", fromDate) + .queryParam("toDate", toDate) + .queryParam("pageNumber", pageNumber) + .queryParam("pageSize", pageSize) + .build()); + } +} diff --git a/src/main/java/com/snp/batch/jobs/web/compliance/service/UpdatedComplianceListService.java b/src/main/java/com/snp/batch/jobs/web/compliance/service/UpdatedComplianceListService.java new file mode 100644 index 0000000..44960ab --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/compliance/service/UpdatedComplianceListService.java @@ -0,0 +1,36 @@ +package com.snp.batch.jobs.web.compliance.service; + +import com.snp.batch.jobs.web.compliance.dto.UpdatedComplianceListDto; +import com.snp.batch.common.web.service.BaseBypassService; +import java.util.List; +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; + +/** + * 기간 내 변경 Compliance 조회 bypass 서비스 + * 외부 Maritime API에서 데이터를 실시간 조회하여 그대로 반환 + */ +@Service +public class UpdatedComplianceListService extends BaseBypassService { + + public UpdatedComplianceListService( + @Qualifier("maritimeServiceApiWebClient") WebClient webClient) { + super(webClient, "/RiskAndCompliance/UpdatedComplianceList", "기간 내 변경 Compliance 조회", + new ParameterizedTypeReference<>() {}, + new ParameterizedTypeReference<>() {}); + } + + /** + * 기간 내 변경 Compliance 조회 데이터를 조회합니다. + * + * @return 기간 내 변경 Compliance 조회 + */ + public List getUpdatedComplianceListData(String fromDate, String toDate) { + return fetchGetList(uri -> uri.path(getApiPath()) + .queryParam("fromDate", fromDate) + .queryParam("toDate", toDate) + .build()); + } +} diff --git a/src/main/java/com/snp/batch/service/BypassCodeGenerator.java b/src/main/java/com/snp/batch/service/BypassCodeGenerator.java index bf9d5a6..c4619b4 100644 --- a/src/main/java/com/snp/batch/service/BypassCodeGenerator.java +++ b/src/main/java/com/snp/batch/service/BypassCodeGenerator.java @@ -99,6 +99,11 @@ public class BypassCodeGenerator { boolean needsLocalDateTime = fields.stream() .anyMatch(f -> "LocalDateTime".equals(f.getFieldType())); + boolean needsList = fields.stream() + .anyMatch(f -> f.getFieldType() != null && f.getFieldType().startsWith("List")); + boolean needsMap = fields.stream() + .anyMatch(f -> f.getFieldType() != null && f.getFieldType().startsWith("Map")); + StringBuilder imports = new StringBuilder(); imports.append("import com.fasterxml.jackson.annotation.JsonProperty;\n"); imports.append("import lombok.AllArgsConstructor;\n"); @@ -109,6 +114,12 @@ public class BypassCodeGenerator { if (needsLocalDateTime) { imports.append("import java.time.LocalDateTime;\n"); } + if (needsList) { + imports.append("import java.util.List;\n"); + } + if (needsMap) { + imports.append("import java.util.Map;\n"); + } StringBuilder fieldLines = new StringBuilder(); for (BypassApiField field : fields) { diff --git a/src/main/java/com/snp/batch/service/JsonSchemaParser.java b/src/main/java/com/snp/batch/service/JsonSchemaParser.java index 1fca930..57db339 100644 --- a/src/main/java/com/snp/batch/service/JsonSchemaParser.java +++ b/src/main/java/com/snp/batch/service/JsonSchemaParser.java @@ -32,6 +32,20 @@ public class JsonSchemaParser { * @return 필드 목록 (파싱 실패 시 빈 목록 반환) */ public List parse(String jsonSample) { + return parse(jsonSample, null); + } + + /** + * JSON 샘플 문자열에서 필드 목록을 추출합니다. + * targetField가 지정된 경우 해당 필드 내부를 파싱 대상으로 사용합니다. + * 배열이면 첫 번째 요소를 파싱합니다. + * + * @param jsonSample JSON 샘플 문자열 + * @param targetField 파싱할 대상 필드명 (null이면 루트 파싱) + * 예: "data" → root.data[0] 내부를 파싱 + * @return 필드 목록 (파싱 실패 시 빈 목록 반환) + */ + public List parse(String jsonSample, String targetField) { List result = new ArrayList<>(); try { JsonNode root = objectMapper.readTree(jsonSample); @@ -43,13 +57,35 @@ public class JsonSchemaParser { return result; } + // targetField가 지정된 경우 해당 필드 내부를 파싱 대상으로 사용 + if (targetField != null && !targetField.isEmpty()) { + JsonNode nested = target.get(targetField); + if (nested != null) { + if (nested.isArray() && nested.size() > 0) { + target = nested.get(0); + } else if (nested.isObject()) { + target = nested; + } else { + log.warn("JSON 파싱: targetField='{}' 가 객체나 배열이 아닙니다.", targetField); + return result; + } + if (!target.isObject()) { + log.warn("JSON 파싱: targetField='{}' 내부에서 유효한 객체를 찾을 수 없습니다.", targetField); + return result; + } + } else { + log.warn("JSON 파싱: targetField='{}' 를 찾을 수 없습니다.", targetField); + return result; + } + } + int sortOrder = 0; var fields = target.fields(); while (fields.hasNext()) { var entry = fields.next(); String originalKey = entry.getKey(); String fieldName = toCamelCase(originalKey); - String fieldType = inferType(entry.getValue()); + String fieldType = inferType(entry.getValue(), fieldName); // jsonProperty: fieldName과 원본 키가 다를 때만 설정 String jsonProperty = fieldName.equals(originalKey) ? null : originalKey; @@ -82,9 +118,13 @@ public class JsonSchemaParser { } /** - * JsonNode에서 Java 타입 추론 + * JsonNode에서 Java 타입 추론. + * 배열 내 첫 번째 요소가 ObjectNode인 경우 "{FieldName}Item" 타입으로 처리합니다. + * + * @param node 파싱할 노드 + * @param fieldName 필드명 (배열 내 객체 타입명 생성에 사용) */ - private String inferType(JsonNode node) { + private String inferType(JsonNode node, String fieldName) { if (node.isTextual()) { return "String"; } @@ -104,6 +144,10 @@ public class JsonSchemaParser { return "String"; } if (node.isArray()) { + if (node.size() > 0 && node.get(0).isObject()) { + String itemTypeName = capitalize(fieldName) + "Item"; + return "List<" + itemTypeName + ">"; + } return "List"; } if (node.isObject()) { @@ -111,4 +155,11 @@ public class JsonSchemaParser { } return "String"; } + + private String capitalize(String s) { + if (s == null || s.isEmpty()) { + return s; + } + return Character.toUpperCase(s.charAt(0)) + s.substring(1); + } }