From b3d993842220dc50bc9ade8251d0c05c6a16c378 Mon Sep 17 00:00:00 2001 From: HYOJIN Date: Wed, 1 Apr 2026 08:35:07 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20S&P=20Bypass=20=ED=94=BC=EB=93=9C?= =?UTF-8?q?=EB=B0=B1=20=EB=B0=98=EC=98=81=20(#123)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Response JSON 원본 반환 (ApiResponse 래핑 제거, executeRaw 추가) - 메뉴명 변경: Bypass API → API 관리 - 사용자용 API 카탈로그 페이지 (/bypass-catalog) 추가 - 운영 환경 코드 생성 차단 (app.environment=prod 시 비활성화) - Bypass API 코드 생성 (compliance, risk 도메인) Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/App.tsx | 2 + frontend/src/components/Navbar.tsx | 5 +- frontend/src/pages/BypassCatalog.tsx | 311 ++++++++++++++++++ frontend/src/pages/BypassConfig.tsx | 21 +- frontend/src/pages/MainMenu.tsx | 2 +- .../web/controller/BaseBypassController.java | 17 + .../controller/BypassConfigController.java | 17 + .../global/controller/WebViewController.java | 4 +- .../controller/ComplianceController.java | 37 +++ .../service/CompliancesByImosService.java | 32 ++ .../web/risk/controller/RiskController.java | 51 +++ .../web/risk/service/RisksByImosService.java | 32 ++ .../service/UpdatedComplianceListService.java | 33 ++ .../batch/service/BypassCodeGenerator.java | 7 +- src/main/resources/application-prod.yml | 1 + src/main/resources/application.yml | 1 + 16 files changed, 562 insertions(+), 11 deletions(-) create mode 100644 frontend/src/pages/BypassCatalog.tsx 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/service/CompliancesByImosService.java create mode 100644 src/main/java/com/snp/batch/jobs/web/risk/controller/RiskController.java create mode 100644 src/main/java/com/snp/batch/jobs/web/risk/service/RisksByImosService.java create mode 100644 src/main/java/com/snp/batch/jobs/web/risk/service/UpdatedComplianceListService.java diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 416167e..6a4d349 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -16,6 +16,7 @@ const RecollectDetail = lazy(() => import('./pages/RecollectDetail')); const Schedules = lazy(() => import('./pages/Schedules')); const Timeline = lazy(() => import('./pages/Timeline')); const BypassConfig = lazy(() => import('./pages/BypassConfig')); +const BypassCatalog = lazy(() => import('./pages/BypassCatalog')); const ScreeningGuide = lazy(() => import('./pages/ScreeningGuide')); const RiskComplianceHistory = lazy(() => import('./pages/RiskComplianceHistory')); @@ -51,6 +52,7 @@ function AppLayout() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index 564edcf..ee7a070 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -45,9 +45,10 @@ const MENU_STRUCTURE: MenuSection[] = [ ), - defaultPath: '/bypass-config', + defaultPath: '/bypass-catalog', children: [ - { id: 'bypass-config', label: 'Bypass API', path: '/bypass-config' }, + { id: 'bypass-catalog', label: 'API 카탈로그', path: '/bypass-catalog' }, + { id: 'bypass-config', label: 'API 관리', path: '/bypass-config' }, ], }, { diff --git a/frontend/src/pages/BypassCatalog.tsx b/frontend/src/pages/BypassCatalog.tsx new file mode 100644 index 0000000..e74e7f7 --- /dev/null +++ b/frontend/src/pages/BypassCatalog.tsx @@ -0,0 +1,311 @@ +import { useState, useEffect, useMemo } from 'react'; + +interface BypassParam { + paramName: string; + paramType: string; + paramIn: string; + required: boolean; + description: string; + example: string; +} + +interface BypassConfig { + id: number; + domainName: string; + endpointName: string; + displayName: string; + httpMethod: string; + externalPath: string; + description: string; + generated: boolean; + createdAt: string; + params: BypassParam[]; +} + +interface ApiResponse { + success: boolean; + data: T; +} + +type ViewMode = 'card' | 'table'; + +const METHOD_COLORS: Record = { + GET: 'bg-emerald-100 text-emerald-700', + POST: 'bg-blue-100 text-blue-700', + PUT: 'bg-amber-100 text-amber-700', + DELETE: 'bg-red-100 text-red-700', +}; + +const SWAGGER_URL = '/snp-api/swagger-ui/index.html?urls.primaryName=3.%20Bypass%20API'; + +export default function BypassCatalog() { + const [configs, setConfigs] = useState([]); + const [loading, setLoading] = useState(true); + const [searchTerm, setSearchTerm] = useState(''); + const [selectedDomain, setSelectedDomain] = useState(''); + const [viewMode, setViewMode] = useState('table'); + + useEffect(() => { + fetch('/snp-api/api/bypass-config') + .then(res => res.json()) + .then((res: ApiResponse) => setConfigs((res.data ?? []).filter(c => c.generated))) + .catch(() => setConfigs([])) + .finally(() => setLoading(false)); + }, []); + + const domainNames = useMemo(() => { + const names = [...new Set(configs.map((c) => c.domainName))]; + return names.sort(); + }, [configs]); + + const filtered = useMemo(() => { + return configs.filter((c) => { + const matchesSearch = + !searchTerm.trim() || + c.domainName.toLowerCase().includes(searchTerm.toLowerCase()) || + c.displayName.toLowerCase().includes(searchTerm.toLowerCase()) || + (c.description || '').toLowerCase().includes(searchTerm.toLowerCase()); + const matchesDomain = !selectedDomain || c.domainName === selectedDomain; + return matchesSearch && matchesDomain; + }); + }, [configs, searchTerm, selectedDomain]); + + if (loading) { + return ( +
+
API 목록을 불러오는 중...
+
+ ); + } + + return ( +
+ {/* 헤더 */} +
+
+

Bypass API 카탈로그

+

+ 등록된 Bypass API 목록입니다. Swagger UI에서 직접 테스트할 수 있습니다. +

+
+ + Swagger UI + +
+ + {/* 검색 + 필터 + 뷰 전환 */} +
+
+ {/* 검색 */} +
+ + + + + + setSearchTerm(e.target.value)} + className="w-full pl-10 pr-4 py-2 border border-wing-border rounded-lg text-sm + focus:ring-2 focus:ring-wing-accent focus:border-wing-accent outline-none bg-wing-surface text-wing-text" + /> + {searchTerm && ( + + )} +
+ + {/* 도메인 드롭다운 필터 */} + + + {/* 뷰 전환 토글 */} +
+ + +
+
+ + {(searchTerm || selectedDomain) && ( +

+ {filtered.length}개 API 검색됨 +

+ )} +
+ + {/* 빈 상태 */} + {configs.length === 0 ? ( +
+

등록된 API가 없습니다.

+

관리자에게 문의해주세요.

+
+ ) : filtered.length === 0 ? ( +
+

검색 결과가 없습니다.

+

다른 검색어를 사용해 보세요.

+
+ ) : viewMode === 'card' ? ( + /* 카드 뷰 */ +
+ {filtered.map((config) => ( +
+
+
+

{config.displayName}

+

{config.domainName}

+
+ + {config.httpMethod} + +
+
+

{config.externalPath}

+ {config.description && ( +

{config.description}

+ )} +
+ {config.params.length > 0 && ( +
+
Parameters
+
+ {config.params.map((p) => ( + + {p.paramName} + {p.required && *} + + ))} +
+
+ )} + +
+ ))} +
+ ) : ( + /* 테이블 뷰 */ +
+
+ + + + + + + + + + + + + {filtered.map((config) => ( + + + + + + + + + ))} + +
도메인명표시명HTTP외부 경로파라미터Swagger
{config.domainName}{config.displayName} + + {config.httpMethod} + + + {config.externalPath} + +
+ {config.params.map((p) => ( + + {p.paramName} + {p.required && *} + + ))} +
+
+ + 테스트 → + +
+
+
+ )} +
+ ); +} diff --git a/frontend/src/pages/BypassConfig.tsx b/frontend/src/pages/BypassConfig.tsx index a1ea498..77446cc 100644 --- a/frontend/src/pages/BypassConfig.tsx +++ b/frontend/src/pages/BypassConfig.tsx @@ -41,6 +41,7 @@ export default function BypassConfig() { const [confirmAction, setConfirmAction] = useState(null); const [generationResult, setGenerationResult] = useState(null); + const [codeGenEnabled, setCodeGenEnabled] = useState(true); const loadConfigs = useCallback(async () => { try { @@ -59,6 +60,10 @@ export default function BypassConfig() { bypassApi.getWebclientBeans() .then((res) => setWebclientBeans(res.data ?? [])) .catch((err) => console.error(err)); + fetch('/snp-api/api/bypass-config/environment') + .then(res => res.json()) + .then(res => setCodeGenEnabled(res.data?.codeGenerationEnabled ?? true)) + .catch(() => {}); }, [loadConfigs]); const handleCreate = () => { @@ -314,7 +319,13 @@ export default function BypassConfig() { @@ -416,7 +427,13 @@ export default function BypassConfig() { diff --git a/frontend/src/pages/MainMenu.tsx b/frontend/src/pages/MainMenu.tsx index 60a1723..f2f8735 100644 --- a/frontend/src/pages/MainMenu.tsx +++ b/frontend/src/pages/MainMenu.tsx @@ -15,7 +15,7 @@ const sections = [ title: 'S&P Bypass', description: 'S&P Bypass API 관리', detail: 'API 등록, 코드 생성 관리, 테스트', - path: '/bypass-config', + path: '/bypass-catalog', icon: '🔗', iconClass: 'gc-card-icon gc-card-icon-guide', menuCount: 1, diff --git a/src/main/java/com/snp/batch/common/web/controller/BaseBypassController.java b/src/main/java/com/snp/batch/common/web/controller/BaseBypassController.java index 7563d2e..8d83650 100644 --- a/src/main/java/com/snp/batch/common/web/controller/BaseBypassController.java +++ b/src/main/java/com/snp/batch/common/web/controller/BaseBypassController.java @@ -25,4 +25,21 @@ public abstract class BaseBypassController { .body(ApiResponse.error("처리 실패: " + e.getMessage())); } } + + /** + * 외부 API 응답을 ApiResponse 래핑 없이 원본 그대로 반환 + */ + protected ResponseEntity executeRaw(Supplier action) { + try { + T result = action.get(); + return ResponseEntity.ok(result); + } catch (WebClientResponseException e) { + log.error("외부 API 호출 실패 - status: {}, body: {}", + e.getStatusCode(), e.getResponseBodyAsString()); + return ResponseEntity.status(e.getStatusCode()).body(null); + } catch (Exception e) { + log.error("API 처리 중 오류", e); + return ResponseEntity.internalServerError().body(null); + } + } } 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 28f648e..d056ac2 100644 --- a/src/main/java/com/snp/batch/global/controller/BypassConfigController.java +++ b/src/main/java/com/snp/batch/global/controller/BypassConfigController.java @@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -40,6 +41,9 @@ public class BypassConfigController { private final BypassCodeGenerator bypassCodeGenerator; private final BypassApiConfigRepository configRepository; + @Value("${app.environment:dev}") + private String environment; + @Operation(summary = "설정 목록 조회") @GetMapping public ResponseEntity>> getConfigs() { @@ -82,6 +86,10 @@ public class BypassConfigController { public ResponseEntity> generateCode( @PathVariable Long id, @RequestParam(defaultValue = "false") boolean force) { + if ("prod".equals(environment)) { + return ResponseEntity.badRequest() + .body(ApiResponse.error("운영 환경에서는 코드 생성이 불가합니다. 개발 환경에서 생성 후 배포해주세요.")); + } try { BypassApiConfig config = configRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("설정을 찾을 수 없습니다: " + id)); @@ -101,6 +109,15 @@ public class BypassConfigController { } } + @Operation(summary = "환경 정보", description = "현재 서버 환경 정보를 반환합니다 (dev/prod).") + @GetMapping("/environment") + public ResponseEntity>> getEnvironment() { + return ResponseEntity.ok(ApiResponse.success(Map.of( + "environment", environment, + "codeGenerationEnabled", !"prod".equals(environment) + ))); + } + @Operation(summary = "WebClient 빈 목록", description = "사용 가능한 WebClient 빈 이름 목록을 반환합니다.") @GetMapping("/webclient-beans") public ResponseEntity>>> getWebclientBeans() { diff --git a/src/main/java/com/snp/batch/global/controller/WebViewController.java b/src/main/java/com/snp/batch/global/controller/WebViewController.java index bf2206b..dcfa009 100644 --- a/src/main/java/com/snp/batch/global/controller/WebViewController.java +++ b/src/main/java/com/snp/batch/global/controller/WebViewController.java @@ -15,10 +15,10 @@ public class WebViewController { @GetMapping({"/", "/dashboard", "/jobs", "/executions", "/executions/{id:\\d+}", "/recollects", "/recollects/{id:\\d+}", "/schedules", "/schedule-timeline", "/monitoring", - "/bypass-config", "/screening-guide", "/risk-compliance-history", + "/bypass-catalog", "/bypass-config", "/screening-guide", "/risk-compliance-history", "/dashboard/**", "/jobs/**", "/executions/**", "/recollects/**", "/schedules/**", "/schedule-timeline/**", "/monitoring/**", - "/bypass-config/**", "/screening-guide/**", "/risk-compliance-history/**"}) + "/bypass-catalog/**", "/bypass-config/**", "/screening-guide/**", "/risk-compliance-history/**"}) public String forward() { return "forward:/index.html"; } 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..d4836fe --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/compliance/controller/ComplianceController.java @@ -0,0 +1,37 @@ +package com.snp.batch.jobs.web.compliance.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 org.springframework.web.bind.annotation.RequestParam; +import com.snp.batch.jobs.web.compliance.service.CompliancesByImosService; + +/** + * Compliance bypass API + * S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환 + */ +@RestController +@RequestMapping("/api/compliance") +@RequiredArgsConstructor +@Tag(name = "Compliance", description = "[Service API] Compliance bypass API") +public class ComplianceController extends BaseBypassController { + + private final CompliancesByImosService compliancesByImosService; + + @Operation( + summary = "IMO 기반 선박 규정준수 조회", + description = "Gets details of the IMOs of ships with full compliance details that match given IMOs" + ) + @GetMapping("/CompliancesByImos") + public ResponseEntity getCompliancesByImosData(@Parameter(description = "Comma separated IMOs up to a total of 100", example = "9876543") + @RequestParam(required = true) String imos) { + return executeRaw(() -> compliancesByImosService.getCompliancesByImosData(imos)); + } +} 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..3f96e32 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/compliance/service/CompliancesByImosService.java @@ -0,0 +1,32 @@ +package com.snp.batch.jobs.web.compliance.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; + +/** + * IMO 기반 선박 규정준수 조회 bypass 서비스 + * 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환 + */ +@Service +public class CompliancesByImosService extends BaseBypassService { + + public CompliancesByImosService( + @Qualifier("maritimeServiceApiWebClient") WebClient webClient) { + super(webClient, "/RiskAndCompliance/CompliancesByImos", "IMO 기반 선박 규정준수 조회", + new ParameterizedTypeReference<>() {}, + new ParameterizedTypeReference<>() {}); + } + + /** + * IMO 기반 선박 규정준수 조회 데이터를 조회합니다. + */ + public JsonNode getCompliancesByImosData(String imos) { + return fetchRawGet(uri -> uri.path(getApiPath()) + .queryParam("imos", imos) + .build()); + } +} diff --git a/src/main/java/com/snp/batch/jobs/web/risk/controller/RiskController.java b/src/main/java/com/snp/batch/jobs/web/risk/controller/RiskController.java new file mode 100644 index 0000000..088bf59 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/risk/controller/RiskController.java @@ -0,0 +1,51 @@ +package com.snp.batch.jobs.web.risk.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 org.springframework.web.bind.annotation.RequestParam; +import com.snp.batch.jobs.web.risk.service.RisksByImosService; +import com.snp.batch.jobs.web.risk.service.UpdatedComplianceListService; + +/** + * Risk bypass API + * S&P Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환 + */ +@RestController +@RequestMapping("/api/risk") +@RequiredArgsConstructor +@Tag(name = "Risk", description = "[Service API] Risk bypass API") +public class RiskController extends BaseBypassController { + + private final RisksByImosService risksByImosService; + private final UpdatedComplianceListService updatedComplianceListService; + + @Operation( + summary = "IMO 기반 선박 위험지표 조회", + description = "Gets details of the IMOs of all ships with risk updates as a collection" + ) + @GetMapping("/RisksByImos") + public ResponseEntity getRisksByImosData(@Parameter(description = "Comma separated IMOs up to a total of 100", example = "9876543") + @RequestParam(required = true) String imos) { + return executeRaw(() -> risksByImosService.getRisksByImosData(imos)); + } + + @Operation( + summary = "기간 내 변경된 위험지표 조회", + description = "Gets details of the IMOs of all ships with compliance updates" + ) + @GetMapping("/UpdatedComplianceList") + public ResponseEntity getUpdatedComplianceListData(@Parameter(description = "Time/seconds are optional", example = "2026-03-30T07:01:27.000Z") + @RequestParam(required = true) String fromDate, + @Parameter(description = "Time/seconds are optional. If unspecified, the current UTC date and time is used", example = "2026-03-31T07:01:27.000Z") + @RequestParam(required = true) String toDate) { + return executeRaw(() -> updatedComplianceListService.getUpdatedComplianceListData(fromDate, toDate)); + } +} diff --git a/src/main/java/com/snp/batch/jobs/web/risk/service/RisksByImosService.java b/src/main/java/com/snp/batch/jobs/web/risk/service/RisksByImosService.java new file mode 100644 index 0000000..0496927 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/risk/service/RisksByImosService.java @@ -0,0 +1,32 @@ +package com.snp.batch.jobs.web.risk.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; + +/** + * IMO 기반 선박 위험지표 조회 bypass 서비스 + * 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환 + */ +@Service +public class RisksByImosService extends BaseBypassService { + + public RisksByImosService( + @Qualifier("maritimeServiceApiWebClient") WebClient webClient) { + super(webClient, "/RiskAndCompliance/RisksByImos", "IMO 기반 선박 위험지표 조회", + new ParameterizedTypeReference<>() {}, + new ParameterizedTypeReference<>() {}); + } + + /** + * IMO 기반 선박 위험지표 조회 데이터를 조회합니다. + */ + public JsonNode getRisksByImosData(String imos) { + return fetchRawGet(uri -> uri.path(getApiPath()) + .queryParam("imos", imos) + .build()); + } +} diff --git a/src/main/java/com/snp/batch/jobs/web/risk/service/UpdatedComplianceListService.java b/src/main/java/com/snp/batch/jobs/web/risk/service/UpdatedComplianceListService.java new file mode 100644 index 0000000..add4913 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/web/risk/service/UpdatedComplianceListService.java @@ -0,0 +1,33 @@ +package com.snp.batch.jobs.web.risk.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; + +/** + * 기간 내 변경된 위험지표 조회 bypass 서비스 + * 외부 Maritime API에서 데이터를 실시간 조회하여 JSON을 그대로 반환 + */ +@Service +public class UpdatedComplianceListService extends BaseBypassService { + + public UpdatedComplianceListService( + @Qualifier("maritimeServiceApiWebClient") WebClient webClient) { + super(webClient, "/RiskAndCompliance/UpdatedComplianceList", "기간 내 변경된 위험지표 조회", + new ParameterizedTypeReference<>() {}, + new ParameterizedTypeReference<>() {}); + } + + /** + * 기간 내 변경된 위험지표 조회 데이터를 조회합니다. + */ + public JsonNode getUpdatedComplianceListData(String fromDate, String toDate) { + return fetchRawGet(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 540896d..9fdbc7f 100644 --- a/src/main/java/com/snp/batch/service/BypassCodeGenerator.java +++ b/src/main/java/com/snp/batch/service/BypassCodeGenerator.java @@ -131,7 +131,7 @@ public class BypassCodeGenerator { /** * Controller 코드 생성 (RAW 모드). - * 모든 엔드포인트가 ResponseEntity>를 반환합니다. + * 모든 엔드포인트가 ResponseEntity를 반환합니다 (외부 API 원본 JSON 그대로). */ private String generateControllerCode(String domain, List configs) { String packageName = BASE_PACKAGE + "." + domain + ".controller"; @@ -142,7 +142,6 @@ public class BypassCodeGenerator { // imports (중복 제거) Set importSet = new LinkedHashSet<>(); importSet.add("import com.fasterxml.jackson.databind.JsonNode;"); - importSet.add("import com.snp.batch.common.web.ApiResponse;"); importSet.add("import com.snp.batch.common.web.controller.BaseBypassController;"); importSet.add("import io.swagger.v3.oas.annotations.Operation;"); importSet.add("import io.swagger.v3.oas.annotations.Parameter;"); @@ -207,12 +206,12 @@ public class BypassCodeGenerator { methods.append(" description = \"").append(opDescription).append("\"\n"); methods.append(" )\n"); methods.append(" ").append(mappingAnnotation).append(mappingPath).append("\n"); - methods.append(" public ResponseEntity> ").append(methodName).append("("); + methods.append(" public ResponseEntity ").append(methodName).append("("); if (!paramAnnotations.isEmpty()) { methods.append(paramAnnotations); } methods.append(") {\n"); - methods.append(" return execute(() -> ").append(serviceField) + methods.append(" return executeRaw(() -> ").append(serviceField) .append(".").append(methodName).append("(").append(serviceCallArgs).append("));\n"); methods.append(" }\n"); } diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 0a3c8e9..7b5b5b2 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -78,6 +78,7 @@ logging: # Custom Application Properties app: + environment: prod batch: chunk-size: 1000 schedule: diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 29200fa..15de717 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -76,6 +76,7 @@ logging: # Custom Application Properties app: + environment: dev batch: chunk-size: 1000 target-schema: