feat: 중첩 JSON 구조 파싱 대응 및 DTO import 버그 수정 (#63)
백엔드:
- generateDtoCode에서 List/Map 타입 import 자동 추가
- JsonSchemaParser에 targetField 파라미터 추가 (특정 필드 내부만 파싱)
- 배열 내 객체 감지 시 List<{FieldName}Item> 타입으로 추론
프론트엔드:
- JSON 샘플 파싱 시 "전체 JSON / 특정 필드 내부" 선택 옵션 추가
- targetField 기본값 "data" (페이징 래퍼 대응)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
부모
6391683959
커밋
9667d3a0ac
@ -117,8 +117,9 @@ export const bypassApi = {
|
|||||||
deleteJson<ApiResponse<void>>(`${BASE}/${id}`),
|
deleteJson<ApiResponse<void>>(`${BASE}/${id}`),
|
||||||
generateCode: (id: number, force = false) =>
|
generateCode: (id: number, force = false) =>
|
||||||
postJson<ApiResponse<CodeGenerationResult>>(`${BASE}/${id}/generate?force=${force}`),
|
postJson<ApiResponse<CodeGenerationResult>>(`${BASE}/${id}/generate?force=${force}`),
|
||||||
parseJson: async (jsonSample: string): Promise<ApiResponse<BypassFieldDto[]>> => {
|
parseJson: async (jsonSample: string, targetField?: string): Promise<ApiResponse<BypassFieldDto[]>> => {
|
||||||
const res = await fetch(`${BASE}/parse-json`, {
|
const params = targetField ? `?targetField=${encodeURIComponent(targetField)}` : '';
|
||||||
|
const res = await fetch(`${BASE}/parse-json${params}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: jsonSample, // 이미 JSON 문자열이므로 JSON.stringify 하지 않음
|
body: jsonSample, // 이미 JSON 문자열이므로 JSON.stringify 하지 않음
|
||||||
|
|||||||
@ -26,6 +26,8 @@ export default function BypassStepFields({ fields, onChange }: BypassStepFieldsP
|
|||||||
const [jsonSample, setJsonSample] = useState('');
|
const [jsonSample, setJsonSample] = useState('');
|
||||||
const [parsing, setParsing] = useState(false);
|
const [parsing, setParsing] = useState(false);
|
||||||
const [parseError, setParseError] = useState<string | null>(null);
|
const [parseError, setParseError] = useState<string | null>(null);
|
||||||
|
const [parseMode, setParseMode] = useState<'root' | 'field'>('root');
|
||||||
|
const [targetField, setTargetField] = useState('data');
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
onChange([...fields, createEmptyField(fields.length)]);
|
onChange([...fields, createEmptyField(fields.length)]);
|
||||||
@ -53,7 +55,8 @@ export default function BypassStepFields({ fields, onChange }: BypassStepFieldsP
|
|||||||
setParseError(null);
|
setParseError(null);
|
||||||
setParsing(true);
|
setParsing(true);
|
||||||
try {
|
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) {
|
if (result.success && result.data) {
|
||||||
onChange(result.data);
|
onChange(result.data);
|
||||||
setActiveTab('manual');
|
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}'}
|
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"
|
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"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 파싱 옵션 */}
|
||||||
|
<div className="mt-3 space-y-2">
|
||||||
|
<label className="text-sm font-medium text-wing-text">파싱 대상</label>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<label className="flex items-center gap-2 text-sm text-wing-text cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="parseMode"
|
||||||
|
checked={parseMode === 'root'}
|
||||||
|
onChange={() => setParseMode('root')}
|
||||||
|
className="accent-wing-accent"
|
||||||
|
/>
|
||||||
|
전체 JSON
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center gap-2 text-sm text-wing-text cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="parseMode"
|
||||||
|
checked={parseMode === 'field'}
|
||||||
|
onChange={() => setParseMode('field')}
|
||||||
|
className="accent-wing-accent"
|
||||||
|
/>
|
||||||
|
특정 필드 내부
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{parseMode === 'field' && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={targetField}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
<span className="text-xs text-wing-muted">
|
||||||
|
해당 필드가 배열이면 첫 번째 요소를, 객체면 그 내부를 파싱합니다.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{parseError && (
|
{parseError && (
|
||||||
<p className="text-sm text-red-500">{parseError}</p>
|
<p className="text-sm text-red-500">{parseError}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -108,12 +108,14 @@ public class BypassConfigController {
|
|||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "JSON 샘플 파싱",
|
summary = "JSON 샘플 파싱",
|
||||||
description = "JSON 샘플에서 DTO 필드 목록을 추출합니다."
|
description = "JSON 샘플에서 DTO 필드 목록을 추출합니다. targetField를 지정하면 해당 필드 내부를 파싱합니다. 예: targetField=data → root.data[0] 내부 필드 추출."
|
||||||
)
|
)
|
||||||
@PostMapping("/parse-json")
|
@PostMapping("/parse-json")
|
||||||
public ResponseEntity<ApiResponse<List<BypassFieldDto>>> parseJson(@RequestBody String jsonSample) {
|
public ResponseEntity<ApiResponse<List<BypassFieldDto>>> parseJson(
|
||||||
|
@RequestBody String jsonSample,
|
||||||
|
@RequestParam(required = false) String targetField) {
|
||||||
try {
|
try {
|
||||||
List<BypassFieldDto> fields = jsonSchemaParser.parse(jsonSample);
|
List<BypassFieldDto> fields = jsonSchemaParser.parse(jsonSample, targetField);
|
||||||
return ResponseEntity.ok(ApiResponse.success(fields));
|
return ResponseEntity.ok(ApiResponse.success(fields));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ResponseEntity.badRequest().body(ApiResponse.error("JSON 파싱 실패: " + e.getMessage()));
|
return ResponseEntity.badRequest().body(ApiResponse.error("JSON 파싱 실패: " + e.getMessage()));
|
||||||
|
|||||||
@ -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<ApiResponse<List<CompliancesByImosDto>>> 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<ApiResponse<List<UpdatedComplianceListDto>>> 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<ApiResponse<List<ComplianceValuesMeaningDto>>> getComplianceValuesMeaningData() {
|
||||||
|
return execute(() -> complianceValuesMeaningService.getComplianceValuesMeaningData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "PagedUpdatedComplianceList 조회",
|
||||||
|
description = "S&P API에서 PagedUpdatedComplianceList 데이터를 요청하고 응답을 그대로 반환합니다."
|
||||||
|
)
|
||||||
|
@GetMapping("/PagedUpdatedComplianceList")
|
||||||
|
public ResponseEntity<ApiResponse<List<PagedUpdatedComplianceListDto>>> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@ -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<Object> data;
|
||||||
|
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@ -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<ComplianceValuesMeaningDto> {
|
||||||
|
|
||||||
|
public ComplianceValuesMeaningService(
|
||||||
|
@Qualifier("maritimeServiceApiWebClient") WebClient webClient) {
|
||||||
|
super(webClient, "/RiskAndCompliance/ComplianceValuesMeaning", "모든 Compliance 지표 조회",
|
||||||
|
new ParameterizedTypeReference<>() {},
|
||||||
|
new ParameterizedTypeReference<>() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 모든 Compliance 지표 조회 데이터를 조회합니다.
|
||||||
|
*
|
||||||
|
* @return 모든 Compliance 지표 조회
|
||||||
|
*/
|
||||||
|
public List<ComplianceValuesMeaningDto> getComplianceValuesMeaningData() {
|
||||||
|
return fetchGetList(uri -> uri.path(getApiPath())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<CompliancesByImosDto> {
|
||||||
|
|
||||||
|
public CompliancesByImosService(
|
||||||
|
@Qualifier("maritimeServiceApiWebClient") WebClient webClient) {
|
||||||
|
super(webClient, "/RiskAndCompliance/CompliancesByImos", "IMO 기반 Compliance 조회",
|
||||||
|
new ParameterizedTypeReference<>() {},
|
||||||
|
new ParameterizedTypeReference<>() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IMO 기반 Compliance 조회 데이터를 조회합니다.
|
||||||
|
*
|
||||||
|
* @return IMO 기반 Compliance 조회
|
||||||
|
*/
|
||||||
|
public List<CompliancesByImosDto> getCompliancesByImosData(String imos) {
|
||||||
|
return fetchGetList(uri -> uri.path(getApiPath())
|
||||||
|
.queryParam("imos", imos)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<PagedUpdatedComplianceListDto> {
|
||||||
|
|
||||||
|
public PagedUpdatedComplianceListService(
|
||||||
|
@Qualifier("maritimeServiceApiWebClient") WebClient webClient) {
|
||||||
|
super(webClient, "/RiskAndCompliance/PagedUpdatedComplianceList", "PagedUpdatedComplianceList",
|
||||||
|
new ParameterizedTypeReference<>() {},
|
||||||
|
new ParameterizedTypeReference<>() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PagedUpdatedComplianceList 데이터를 조회합니다.
|
||||||
|
*
|
||||||
|
* @return PagedUpdatedComplianceList
|
||||||
|
*/
|
||||||
|
public List<PagedUpdatedComplianceListDto> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<UpdatedComplianceListDto> {
|
||||||
|
|
||||||
|
public UpdatedComplianceListService(
|
||||||
|
@Qualifier("maritimeServiceApiWebClient") WebClient webClient) {
|
||||||
|
super(webClient, "/RiskAndCompliance/UpdatedComplianceList", "기간 내 변경 Compliance 조회",
|
||||||
|
new ParameterizedTypeReference<>() {},
|
||||||
|
new ParameterizedTypeReference<>() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 기간 내 변경 Compliance 조회 데이터를 조회합니다.
|
||||||
|
*
|
||||||
|
* @return 기간 내 변경 Compliance 조회
|
||||||
|
*/
|
||||||
|
public List<UpdatedComplianceListDto> getUpdatedComplianceListData(String fromDate, String toDate) {
|
||||||
|
return fetchGetList(uri -> uri.path(getApiPath())
|
||||||
|
.queryParam("fromDate", fromDate)
|
||||||
|
.queryParam("toDate", toDate)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -99,6 +99,11 @@ public class BypassCodeGenerator {
|
|||||||
boolean needsLocalDateTime = fields.stream()
|
boolean needsLocalDateTime = fields.stream()
|
||||||
.anyMatch(f -> "LocalDateTime".equals(f.getFieldType()));
|
.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();
|
StringBuilder imports = new StringBuilder();
|
||||||
imports.append("import com.fasterxml.jackson.annotation.JsonProperty;\n");
|
imports.append("import com.fasterxml.jackson.annotation.JsonProperty;\n");
|
||||||
imports.append("import lombok.AllArgsConstructor;\n");
|
imports.append("import lombok.AllArgsConstructor;\n");
|
||||||
@ -109,6 +114,12 @@ public class BypassCodeGenerator {
|
|||||||
if (needsLocalDateTime) {
|
if (needsLocalDateTime) {
|
||||||
imports.append("import java.time.LocalDateTime;\n");
|
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();
|
StringBuilder fieldLines = new StringBuilder();
|
||||||
for (BypassApiField field : fields) {
|
for (BypassApiField field : fields) {
|
||||||
|
|||||||
@ -32,6 +32,20 @@ public class JsonSchemaParser {
|
|||||||
* @return 필드 목록 (파싱 실패 시 빈 목록 반환)
|
* @return 필드 목록 (파싱 실패 시 빈 목록 반환)
|
||||||
*/
|
*/
|
||||||
public List<BypassFieldDto> parse(String jsonSample) {
|
public List<BypassFieldDto> parse(String jsonSample) {
|
||||||
|
return parse(jsonSample, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 샘플 문자열에서 필드 목록을 추출합니다.
|
||||||
|
* targetField가 지정된 경우 해당 필드 내부를 파싱 대상으로 사용합니다.
|
||||||
|
* 배열이면 첫 번째 요소를 파싱합니다.
|
||||||
|
*
|
||||||
|
* @param jsonSample JSON 샘플 문자열
|
||||||
|
* @param targetField 파싱할 대상 필드명 (null이면 루트 파싱)
|
||||||
|
* 예: "data" → root.data[0] 내부를 파싱
|
||||||
|
* @return 필드 목록 (파싱 실패 시 빈 목록 반환)
|
||||||
|
*/
|
||||||
|
public List<BypassFieldDto> parse(String jsonSample, String targetField) {
|
||||||
List<BypassFieldDto> result = new ArrayList<>();
|
List<BypassFieldDto> result = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
JsonNode root = objectMapper.readTree(jsonSample);
|
JsonNode root = objectMapper.readTree(jsonSample);
|
||||||
@ -43,13 +57,35 @@ public class JsonSchemaParser {
|
|||||||
return result;
|
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;
|
int sortOrder = 0;
|
||||||
var fields = target.fields();
|
var fields = target.fields();
|
||||||
while (fields.hasNext()) {
|
while (fields.hasNext()) {
|
||||||
var entry = fields.next();
|
var entry = fields.next();
|
||||||
String originalKey = entry.getKey();
|
String originalKey = entry.getKey();
|
||||||
String fieldName = toCamelCase(originalKey);
|
String fieldName = toCamelCase(originalKey);
|
||||||
String fieldType = inferType(entry.getValue());
|
String fieldType = inferType(entry.getValue(), fieldName);
|
||||||
|
|
||||||
// jsonProperty: fieldName과 원본 키가 다를 때만 설정
|
// jsonProperty: fieldName과 원본 키가 다를 때만 설정
|
||||||
String jsonProperty = fieldName.equals(originalKey) ? null : originalKey;
|
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()) {
|
if (node.isTextual()) {
|
||||||
return "String";
|
return "String";
|
||||||
}
|
}
|
||||||
@ -104,6 +144,10 @@ public class JsonSchemaParser {
|
|||||||
return "String";
|
return "String";
|
||||||
}
|
}
|
||||||
if (node.isArray()) {
|
if (node.isArray()) {
|
||||||
|
if (node.size() > 0 && node.get(0).isObject()) {
|
||||||
|
String itemTypeName = capitalize(fieldName) + "Item";
|
||||||
|
return "List<" + itemTypeName + ">";
|
||||||
|
}
|
||||||
return "List<Object>";
|
return "List<Object>";
|
||||||
}
|
}
|
||||||
if (node.isObject()) {
|
if (node.isObject()) {
|
||||||
@ -111,4 +155,11 @@ public class JsonSchemaParser {
|
|||||||
}
|
}
|
||||||
return "String";
|
return "String";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String capitalize(String s) {
|
||||||
|
if (s == null || s.isEmpty()) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
return Character.toUpperCase(s.charAt(0)) + s.substring(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user