feat: 같은 도메인에 여러 BYPASS API 엔드포인트 등록 지원 (#63)
- BypassApiConfig에 endpointName 필드 추가 (externalPath에서 자동 추출) - domainName unique 제약 → domainName + endpointName 복합 unique로 변경 - 코드 생성 시 같은 도메인의 모든 설정을 합쳐 Controller 1개 생성 - Service/DTO는 엔드포인트별로 별도 생성 - CodeGenerationResult에 servicePaths/dtoPaths 목록 반환 - 프론트엔드 타입/UI 업데이트 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
부모
0132408ae3
커밋
ce27c60985
@ -41,6 +41,7 @@ export interface BypassConfigRequest {
|
|||||||
export interface BypassConfigResponse {
|
export interface BypassConfigResponse {
|
||||||
id: number;
|
id: number;
|
||||||
domainName: string;
|
domainName: string;
|
||||||
|
endpointName: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
webclientBean: string;
|
webclientBean: string;
|
||||||
externalPath: string;
|
externalPath: string;
|
||||||
@ -57,8 +58,8 @@ export interface BypassConfigResponse {
|
|||||||
|
|
||||||
export interface CodeGenerationResult {
|
export interface CodeGenerationResult {
|
||||||
controllerPath: string;
|
controllerPath: string;
|
||||||
servicePath: string;
|
servicePaths: string[];
|
||||||
dtoPath: string;
|
dtoPaths: string[];
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -463,19 +463,29 @@ export default function BypassConfig() {
|
|||||||
<p className="text-sm text-wing-text">{generationResult.message}</p>
|
<p className="text-sm text-wing-text">{generationResult.message}</p>
|
||||||
<div className="bg-wing-card rounded-lg p-3 space-y-2">
|
<div className="bg-wing-card rounded-lg p-3 space-y-2">
|
||||||
<p className="text-xs font-semibold text-wing-text mb-1">생성된 파일</p>
|
<p className="text-xs font-semibold text-wing-text mb-1">생성된 파일</p>
|
||||||
{[
|
<div className="flex gap-2 text-xs">
|
||||||
{ label: 'Controller', path: generationResult.controllerPath },
|
<span className="w-20 font-medium text-wing-accent shrink-0">Controller</span>
|
||||||
{ label: 'Service', path: generationResult.servicePath },
|
<span className="font-mono text-wing-muted break-all">{generationResult.controllerPath}</span>
|
||||||
{ label: 'DTO', path: generationResult.dtoPath },
|
</div>
|
||||||
].map(({ label, path }) => (
|
{generationResult.servicePaths.map((path, idx) => (
|
||||||
<div key={label} className="flex gap-2 text-xs">
|
<div key={`service-${idx}`} className="flex gap-2 text-xs">
|
||||||
<span className="w-20 font-medium text-wing-accent shrink-0">{label}</span>
|
<span className="w-20 font-medium text-wing-accent shrink-0">
|
||||||
|
Service {generationResult.servicePaths.length > 1 ? idx + 1 : ''}
|
||||||
|
</span>
|
||||||
|
<span className="font-mono text-wing-muted break-all">{path}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{generationResult.dtoPaths.map((path, idx) => (
|
||||||
|
<div key={`dto-${idx}`} className="flex gap-2 text-xs">
|
||||||
|
<span className="w-20 font-medium text-wing-accent shrink-0">
|
||||||
|
DTO {generationResult.dtoPaths.length > 1 ? idx + 1 : ''}
|
||||||
|
</span>
|
||||||
<span className="font-mono text-wing-muted break-all">{path}</span>
|
<span className="font-mono text-wing-muted break-all">{path}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-2 bg-amber-50 text-amber-700 rounded-lg p-3 text-xs">
|
<div className="flex items-start gap-2 bg-amber-50 text-amber-700 rounded-lg p-3 text-xs">
|
||||||
<span className="shrink-0">⚠</span>
|
<span className="shrink-0">⚠</span>
|
||||||
<span>서버를 재시작하면 새 API가 활성화됩니다.</span>
|
<span>서버를 재시작하면 새 API가 활성화됩니다.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -79,7 +79,7 @@ public class BypassConfigController {
|
|||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "코드 생성",
|
summary = "코드 생성",
|
||||||
description = "등록된 설정을 기반으로 Controller, Service, DTO Java 소스 코드를 생성합니다."
|
description = "등록된 설정의 도메인 전체를 기반으로 Controller, Service, DTO Java 소스 코드를 생성합니다. 같은 도메인의 모든 설정을 하나의 Controller로 합칩니다."
|
||||||
)
|
)
|
||||||
@PostMapping("/{id}/generate")
|
@PostMapping("/{id}/generate")
|
||||||
public ResponseEntity<ApiResponse<CodeGenerationResult>> generateCode(
|
public ResponseEntity<ApiResponse<CodeGenerationResult>> generateCode(
|
||||||
@ -89,10 +89,13 @@ public class BypassConfigController {
|
|||||||
BypassApiConfig config = configRepository.findById(id)
|
BypassApiConfig config = configRepository.findById(id)
|
||||||
.orElseThrow(() -> new IllegalArgumentException("설정을 찾을 수 없습니다: " + id));
|
.orElseThrow(() -> new IllegalArgumentException("설정을 찾을 수 없습니다: " + id));
|
||||||
|
|
||||||
CodeGenerationResult result = bypassCodeGenerator.generate(
|
// 같은 도메인의 모든 설정을 조회하여 함께 생성
|
||||||
config, config.getParams(), config.getFields(), force);
|
List<BypassApiConfig> domainConfigs = configRepository.findByDomainNameOrderById(config.getDomainName());
|
||||||
|
|
||||||
bypassConfigService.markAsGenerated(id);
|
CodeGenerationResult result = bypassCodeGenerator.generate(domainConfigs, force);
|
||||||
|
|
||||||
|
// 같은 도메인의 모든 설정을 generated로 마킹
|
||||||
|
domainConfigs.forEach(c -> bypassConfigService.markAsGenerated(c.getId()));
|
||||||
|
|
||||||
return ResponseEntity.ok(ApiResponse.success(result));
|
return ResponseEntity.ok(ApiResponse.success(result));
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
|
|||||||
@ -22,6 +22,9 @@ public class BypassConfigResponse {
|
|||||||
/** 도메인명 (패키지명/URL 경로) */
|
/** 도메인명 (패키지명/URL 경로) */
|
||||||
private String domainName;
|
private String domainName;
|
||||||
|
|
||||||
|
/** 엔드포인트명 (externalPath 마지막 세그먼트) */
|
||||||
|
private String endpointName;
|
||||||
|
|
||||||
/** 표시명 */
|
/** 표시명 */
|
||||||
private String displayName;
|
private String displayName;
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,11 @@ import lombok.Builder;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 코드 자동 생성 결과 DTO
|
* 코드 자동 생성 결과 DTO
|
||||||
|
* 같은 도메인에 N개의 엔드포인트를 지원하므로 Service/DTO는 목록으로 반환
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@Builder
|
@Builder
|
||||||
@ -17,11 +20,11 @@ public class CodeGenerationResult {
|
|||||||
/** 생성된 Controller 파일 경로 */
|
/** 생성된 Controller 파일 경로 */
|
||||||
private String controllerPath;
|
private String controllerPath;
|
||||||
|
|
||||||
/** 생성된 Service 파일 경로 */
|
/** 생성된 Service 파일 경로 목록 (엔드포인트별) */
|
||||||
private String servicePath;
|
private List<String> servicePaths;
|
||||||
|
|
||||||
/** 생성된 DTO 파일 경로 */
|
/** 생성된 DTO 파일 경로 목록 (엔드포인트별) */
|
||||||
private String dtoPath;
|
private List<String> dtoPaths;
|
||||||
|
|
||||||
/** 결과 메시지 */
|
/** 결과 메시지 */
|
||||||
private String message;
|
private String message;
|
||||||
|
|||||||
@ -14,7 +14,9 @@ import java.util.List;
|
|||||||
* JPA를 사용하므로 @PrePersist, @PreUpdate로 감사 필드 자동 설정
|
* JPA를 사용하므로 @PrePersist, @PreUpdate로 감사 필드 자동 설정
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "bypass_api_config")
|
@Table(name = "bypass_api_config", uniqueConstraints = {
|
||||||
|
@UniqueConstraint(columnNames = {"domain_name", "endpoint_name"})
|
||||||
|
})
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Builder
|
@Builder
|
||||||
@ -30,9 +32,16 @@ public class BypassApiConfig {
|
|||||||
* 도메인명 (패키지명/URL 경로)
|
* 도메인명 (패키지명/URL 경로)
|
||||||
* 예: "ship-info", "port-data"
|
* 예: "ship-info", "port-data"
|
||||||
*/
|
*/
|
||||||
@Column(name = "domain_name", unique = true, nullable = false, length = 50)
|
@Column(name = "domain_name", nullable = false, length = 50)
|
||||||
private String domainName;
|
private String domainName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 엔드포인트명 (externalPath의 마지막 세그먼트에서 자동 추출)
|
||||||
|
* 예: "CompliancesByImos", "CompanyCompliancesByImos"
|
||||||
|
*/
|
||||||
|
@Column(name = "endpoint_name", nullable = false, length = 100)
|
||||||
|
private String endpointName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 표시명
|
* 표시명
|
||||||
* 예: "선박 정보 API", "항만 데이터 API"
|
* 예: "선박 정보 API", "항만 데이터 API"
|
||||||
@ -117,12 +126,28 @@ public class BypassApiConfig {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 엔티티 저장 전 자동 호출 (INSERT 시)
|
* 엔티티 저장 전 자동 호출 (INSERT 시)
|
||||||
|
* endpointName이 null이면 externalPath에서 자동 추출 (마이그레이션 대응)
|
||||||
*/
|
*/
|
||||||
@PrePersist
|
@PrePersist
|
||||||
protected void onCreate() {
|
protected void onCreate() {
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
this.createdAt = now;
|
this.createdAt = now;
|
||||||
this.updatedAt = now;
|
this.updatedAt = now;
|
||||||
|
if (this.endpointName == null || this.endpointName.isEmpty()) {
|
||||||
|
this.endpointName = extractEndpointName(this.externalPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 엔티티 업데이트 전 자동 호출 (UPDATE 시)
|
||||||
|
* endpointName이 null이면 externalPath에서 자동 추출 (마이그레이션 대응)
|
||||||
|
*/
|
||||||
|
private static String extractEndpointName(String externalPath) {
|
||||||
|
if (externalPath == null || externalPath.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String[] segments = externalPath.split("/");
|
||||||
|
return segments[segments.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,5 +156,8 @@ public class BypassApiConfig {
|
|||||||
@PreUpdate
|
@PreUpdate
|
||||||
protected void onUpdate() {
|
protected void onUpdate() {
|
||||||
this.updatedAt = LocalDateTime.now();
|
this.updatedAt = LocalDateTime.now();
|
||||||
|
if (this.endpointName == null || this.endpointName.isEmpty()) {
|
||||||
|
this.endpointName = extractEndpointName(this.externalPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import com.snp.batch.global.model.BypassApiConfig;
|
|||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,12 +15,17 @@ import java.util.Optional;
|
|||||||
public interface BypassApiConfigRepository extends JpaRepository<BypassApiConfig, Long> {
|
public interface BypassApiConfigRepository extends JpaRepository<BypassApiConfig, Long> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 도메인명으로 BYPASS API 설정 조회
|
* 도메인명으로 BYPASS API 설정 단건 조회 (하위 호환)
|
||||||
*/
|
*/
|
||||||
Optional<BypassApiConfig> findByDomainName(String domainName);
|
Optional<BypassApiConfig> findByDomainName(String domainName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 도메인명 존재 여부 확인
|
* 도메인명으로 BYPASS API 설정 목록 조회 (ID 순)
|
||||||
*/
|
*/
|
||||||
boolean existsByDomainName(String domainName);
|
List<BypassApiConfig> findByDomainNameOrderById(String domainName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 도메인명 + 엔드포인트명 복합 유니크 존재 여부 확인
|
||||||
|
*/
|
||||||
|
boolean existsByDomainNameAndEndpointName(String domainName, String endpointName);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 ComplianceBypassDto {
|
||||||
|
|
||||||
|
@JsonProperty("dateAmended")
|
||||||
|
private String dateAmended;
|
||||||
|
|
||||||
|
@JsonProperty("legalOverall")
|
||||||
|
private Integer legalOverall;
|
||||||
|
|
||||||
|
@JsonProperty("lrimoShipNo")
|
||||||
|
private String lrimoShipNo;
|
||||||
|
|
||||||
|
@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("shipOFACAdvisoryList")
|
||||||
|
private Integer shipOFACAdvisoryList;
|
||||||
|
|
||||||
|
@JsonProperty("shipOFACNonSDNSanctionList")
|
||||||
|
private Integer shipOFACNonSDNSanctionList;
|
||||||
|
|
||||||
|
@JsonProperty("shipOFACSanctionList")
|
||||||
|
private Integer shipOFACSanctionList;
|
||||||
|
|
||||||
|
@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("shipOwnerOFACSSIList")
|
||||||
|
private Integer shipOwnerOFACSSIList;
|
||||||
|
|
||||||
|
@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("shipSTSPartnerNonComplianceLast12m")
|
||||||
|
private Integer shipSTSPartnerNonComplianceLast12m;
|
||||||
|
|
||||||
|
@JsonProperty("shipSanctionedCountryPortCallLast12m")
|
||||||
|
private Integer shipSanctionedCountryPortCallLast12m;
|
||||||
|
|
||||||
|
@JsonProperty("shipSanctionedCountryPortCallLast3m")
|
||||||
|
private Integer shipSanctionedCountryPortCallLast3m;
|
||||||
|
|
||||||
|
@JsonProperty("shipSanctionedCountryPortCallLast6m")
|
||||||
|
private Integer shipSanctionedCountryPortCallLast6m;
|
||||||
|
|
||||||
|
@JsonProperty("shipSecurityLegalDisputeEvent")
|
||||||
|
private Integer shipSecurityLegalDisputeEvent;
|
||||||
|
|
||||||
|
@JsonProperty("shipSwissSanctionList")
|
||||||
|
private Integer shipSwissSanctionList;
|
||||||
|
|
||||||
|
@JsonProperty("shipUNSanctionList")
|
||||||
|
private Integer shipUNSanctionList;
|
||||||
|
|
||||||
|
}
|
||||||
@ -11,12 +11,15 @@ import java.io.IOException;
|
|||||||
import java.nio.charset.StandardCharsets;
|
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.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BYPASS API Java 소스 코드를 자동 생성하는 서비스.
|
* BYPASS API Java 소스 코드를 자동 생성하는 서비스.
|
||||||
* RiskBypassService / RiskController 패턴을 기반으로 DTO, Service, Controller를 생성합니다.
|
* 같은 도메인의 N개 설정을 받아 N개의 DTO + Service와 1개의 Controller를 생성합니다.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@ -25,47 +28,59 @@ public class BypassCodeGenerator {
|
|||||||
private static final String BASE_PACKAGE = "com.snp.batch.jobs.web";
|
private static final String BASE_PACKAGE = "com.snp.batch.jobs.web";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BYPASS API 코드를 생성합니다.
|
* 같은 도메인의 BYPASS API 코드를 생성합니다.
|
||||||
|
* 엔드포인트별 DTO/Service를 각각 생성하고, Controller 1개에 모든 엔드포인트 메서드를 합칩니다.
|
||||||
*
|
*
|
||||||
* @param config 설정 정보
|
* @param configs 같은 domainName을 가진 설정 목록
|
||||||
* @param params 파라미터 목록
|
* @param force 기존 파일 덮어쓰기 여부
|
||||||
* @param fields DTO 필드 목록
|
|
||||||
* @param force 기존 파일 덮어쓰기 여부
|
|
||||||
* @return 생성 결과
|
* @return 생성 결과
|
||||||
*/
|
*/
|
||||||
public CodeGenerationResult generate(BypassApiConfig config,
|
public CodeGenerationResult generate(List<BypassApiConfig> configs, boolean force) {
|
||||||
List<BypassApiParam> params,
|
if (configs == null || configs.isEmpty()) {
|
||||||
List<BypassApiField> fields,
|
throw new IllegalArgumentException("생성할 설정이 없습니다.");
|
||||||
boolean force) {
|
}
|
||||||
|
|
||||||
String projectRoot = System.getProperty("user.dir");
|
String projectRoot = System.getProperty("user.dir");
|
||||||
String domain = config.getDomainName();
|
String domain = configs.get(0).getDomainName();
|
||||||
String domainCapitalized = capitalize(domain);
|
|
||||||
|
|
||||||
String dtoCode = generateDtoCode(domain, domainCapitalized, fields);
|
|
||||||
String serviceCode = generateServiceCode(domain, domainCapitalized, config, params);
|
|
||||||
String controllerCode = generateControllerCode(domain, domainCapitalized, config, params);
|
|
||||||
|
|
||||||
String basePath = projectRoot + "/src/main/java/com/snp/batch/jobs/web/" + domain;
|
String basePath = projectRoot + "/src/main/java/com/snp/batch/jobs/web/" + domain;
|
||||||
Path dtoPath = writeFile(basePath + "/dto/" + domainCapitalized + "BypassDto.java", dtoCode, force);
|
|
||||||
Path servicePath = writeFile(basePath + "/service/" + domainCapitalized + "BypassService.java", serviceCode, force);
|
|
||||||
Path controllerPath = writeFile(basePath + "/controller/" + domainCapitalized + "Controller.java", controllerCode, force);
|
|
||||||
|
|
||||||
log.info("코드 생성 완료 - domain: {}, dto: {}, service: {}, controller: {}",
|
List<String> dtoPaths = new ArrayList<>();
|
||||||
domain, dtoPath, servicePath, controllerPath);
|
List<String> servicePaths = new ArrayList<>();
|
||||||
|
|
||||||
|
for (BypassApiConfig config : configs) {
|
||||||
|
String endpointName = config.getEndpointName();
|
||||||
|
|
||||||
|
String dtoCode = generateDtoCode(domain, endpointName, config.getFields());
|
||||||
|
String serviceCode = generateServiceCode(domain, endpointName, config, config.getParams());
|
||||||
|
|
||||||
|
Path dtoFilePath = writeFile(basePath + "/dto/" + endpointName + "Dto.java", dtoCode, force);
|
||||||
|
Path serviceFilePath = writeFile(basePath + "/service/" + endpointName + "Service.java", serviceCode, force);
|
||||||
|
|
||||||
|
dtoPaths.add(dtoFilePath.toString());
|
||||||
|
servicePaths.add(serviceFilePath.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
String controllerCode = generateControllerCode(domain, configs);
|
||||||
|
String domainCapitalized = capitalize(domain);
|
||||||
|
Path controllerFilePath = writeFile(
|
||||||
|
basePath + "/controller/" + domainCapitalized + "Controller.java", controllerCode, force);
|
||||||
|
|
||||||
|
log.info("코드 생성 완료 - domain: {}, endpoints: {}, controller: {}",
|
||||||
|
domain, configs.stream().map(BypassApiConfig::getEndpointName).toList(), controllerFilePath);
|
||||||
|
|
||||||
return CodeGenerationResult.builder()
|
return CodeGenerationResult.builder()
|
||||||
.dtoPath(dtoPath.toString())
|
.controllerPath(controllerFilePath.toString())
|
||||||
.servicePath(servicePath.toString())
|
.servicePaths(servicePaths)
|
||||||
.controllerPath(controllerPath.toString())
|
.dtoPaths(dtoPaths)
|
||||||
.message("코드 생성 완료. 서버를 재시작하면 새 API가 활성화됩니다.")
|
.message("코드 생성 완료. 서버를 재시작하면 새 API가 활성화됩니다.")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO 코드 생성.
|
* DTO 코드 생성.
|
||||||
* 각 field에 대해 @JsonProperty + private 필드를 생성합니다.
|
* endpointName 기반 클래스명: {EndpointName}Dto
|
||||||
*/
|
*/
|
||||||
private String generateDtoCode(String domain, String domainCap, List<BypassApiField> fields) {
|
private String generateDtoCode(String domain, String endpointName, List<BypassApiField> fields) {
|
||||||
String packageName = BASE_PACKAGE + "." + domain + ".dto";
|
String packageName = BASE_PACKAGE + "." + domain + ".dto";
|
||||||
boolean needsLocalDateTime = fields.stream()
|
boolean needsLocalDateTime = fields.stream()
|
||||||
.anyMatch(f -> "LocalDateTime".equals(f.getFieldType()));
|
.anyMatch(f -> "LocalDateTime".equals(f.getFieldType()));
|
||||||
@ -103,24 +118,26 @@ public class BypassCodeGenerator {
|
|||||||
"""
|
"""
|
||||||
.replace("{{PACKAGE}}", packageName)
|
.replace("{{PACKAGE}}", packageName)
|
||||||
.replace("{{IMPORTS}}", imports.toString())
|
.replace("{{IMPORTS}}", imports.toString())
|
||||||
.replace("{{CLASS_NAME}}", domainCap + "BypassDto")
|
.replace("{{CLASS_NAME}}", endpointName + "Dto")
|
||||||
.replace("{{FIELDS}}", fieldLines.toString());
|
.replace("{{FIELDS}}", fieldLines.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service 코드 생성.
|
* Service 코드 생성.
|
||||||
* BaseBypassService를 상속하여 GET/POST, LIST/SINGLE 조합에 맞는 fetch 메서드를 생성합니다.
|
* endpointName 기반 클래스명: {EndpointName}Service
|
||||||
|
* BaseBypassService<{EndpointName}Dto>를 상속합니다.
|
||||||
*/
|
*/
|
||||||
private String generateServiceCode(String domain, String domainCap,
|
private String generateServiceCode(String domain, String endpointName,
|
||||||
BypassApiConfig config, List<BypassApiParam> params) {
|
BypassApiConfig config, List<BypassApiParam> params) {
|
||||||
String packageName = BASE_PACKAGE + "." + domain + ".service";
|
String packageName = BASE_PACKAGE + "." + domain + ".service";
|
||||||
String dtoPackage = BASE_PACKAGE + "." + domain + ".dto";
|
String dtoPackage = BASE_PACKAGE + "." + domain + ".dto";
|
||||||
String dtoClass = domainCap + "BypassDto";
|
String dtoClass = endpointName + "Dto";
|
||||||
|
String serviceClass = endpointName + "Service";
|
||||||
boolean isList = "LIST".equalsIgnoreCase(config.getResponseType());
|
boolean isList = "LIST".equalsIgnoreCase(config.getResponseType());
|
||||||
boolean isPost = "POST".equalsIgnoreCase(config.getHttpMethod());
|
boolean isPost = "POST".equalsIgnoreCase(config.getHttpMethod());
|
||||||
|
|
||||||
String returnType = isList ? "List<" + dtoClass + ">" : dtoClass;
|
String returnType = isList ? "List<" + dtoClass + ">" : dtoClass;
|
||||||
String methodName = "get" + domainCap + "Data";
|
String methodName = "get" + endpointName + "Data";
|
||||||
String fetchMethod = buildFetchMethodCall(config, params, isList, isPost);
|
String fetchMethod = buildFetchMethodCall(config, params, isList, isPost);
|
||||||
String methodParams = buildMethodParams(params);
|
String methodParams = buildMethodParams(params);
|
||||||
|
|
||||||
@ -141,9 +158,9 @@ public class BypassCodeGenerator {
|
|||||||
* 외부 Maritime API에서 데이터를 실시간 조회하여 그대로 반환
|
* 외부 Maritime API에서 데이터를 실시간 조회하여 그대로 반환
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class {{DOMAIN_CAP}}BypassService extends BaseBypassService<{{DTO_CLASS}}> {
|
public class {{SERVICE_CLASS}} extends BaseBypassService<{{DTO_CLASS}}> {
|
||||||
|
|
||||||
public {{DOMAIN_CAP}}BypassService(
|
public {{SERVICE_CLASS}}(
|
||||||
@Qualifier("{{WEBCLIENT_BEAN}}") WebClient webClient) {
|
@Qualifier("{{WEBCLIENT_BEAN}}") WebClient webClient) {
|
||||||
super(webClient, "{{EXTERNAL_PATH}}", "{{DISPLAY_NAME}}",
|
super(webClient, "{{EXTERNAL_PATH}}", "{{DISPLAY_NAME}}",
|
||||||
new ParameterizedTypeReference<>() {},
|
new ParameterizedTypeReference<>() {},
|
||||||
@ -164,7 +181,7 @@ public class BypassCodeGenerator {
|
|||||||
.replace("{{DTO_IMPORT}}", dtoPackage + "." + dtoClass)
|
.replace("{{DTO_IMPORT}}", dtoPackage + "." + dtoClass)
|
||||||
.replace("{{LIST_IMPORT}}", listImport)
|
.replace("{{LIST_IMPORT}}", listImport)
|
||||||
.replace("{{DISPLAY_NAME}}", config.getDisplayName())
|
.replace("{{DISPLAY_NAME}}", config.getDisplayName())
|
||||||
.replace("{{DOMAIN_CAP}}", domainCap)
|
.replace("{{SERVICE_CLASS}}", serviceClass)
|
||||||
.replace("{{DTO_CLASS}}", dtoClass)
|
.replace("{{DTO_CLASS}}", dtoClass)
|
||||||
.replace("{{WEBCLIENT_BEAN}}", config.getWebclientBean())
|
.replace("{{WEBCLIENT_BEAN}}", config.getWebclientBean())
|
||||||
.replace("{{EXTERNAL_PATH}}", config.getExternalPath())
|
.replace("{{EXTERNAL_PATH}}", config.getExternalPath())
|
||||||
@ -176,95 +193,126 @@ public class BypassCodeGenerator {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller 코드 생성.
|
* Controller 코드 생성.
|
||||||
* BaseBypassController를 상속하여 GET/POST 엔드포인트를 생성합니다.
|
* 같은 도메인의 모든 설정을 합쳐 하나의 Controller에 N개의 엔드포인트 메서드를 생성합니다.
|
||||||
*/
|
*/
|
||||||
private String generateControllerCode(String domain, String domainCap,
|
private String generateControllerCode(String domain, List<BypassApiConfig> configs) {
|
||||||
BypassApiConfig config, List<BypassApiParam> params) {
|
|
||||||
String packageName = BASE_PACKAGE + "." + domain + ".controller";
|
String packageName = BASE_PACKAGE + "." + domain + ".controller";
|
||||||
String dtoPackage = BASE_PACKAGE + "." + domain + ".dto";
|
|
||||||
String servicePackage = BASE_PACKAGE + "." + domain + ".service";
|
String servicePackage = BASE_PACKAGE + "." + domain + ".service";
|
||||||
String dtoClass = domainCap + "BypassDto";
|
String dtoPackage = BASE_PACKAGE + "." + domain + ".dto";
|
||||||
String serviceClass = domainCap + "BypassService";
|
String domainCap = capitalize(domain);
|
||||||
String serviceField = Character.toLowerCase(serviceClass.charAt(0)) + serviceClass.substring(1);
|
|
||||||
|
|
||||||
boolean isList = "LIST".equalsIgnoreCase(config.getResponseType());
|
|
||||||
boolean isPost = "POST".equalsIgnoreCase(config.getHttpMethod());
|
|
||||||
|
|
||||||
String responseGeneric = isList ? "List<" + dtoClass + ">" : dtoClass;
|
|
||||||
String mappingAnnotation = isPost ? "@PostMapping" : "@GetMapping";
|
|
||||||
String listImport = isList ? "import java.util.List;\n" : "";
|
|
||||||
|
|
||||||
String paramAnnotations = buildControllerParamAnnotations(params);
|
|
||||||
String methodParams = buildMethodParams(params);
|
|
||||||
String serviceCallArgs = buildServiceCallArgs(params);
|
|
||||||
String pathVariableImport = params.stream().anyMatch(p -> "PATH".equalsIgnoreCase(p.getParamIn()))
|
|
||||||
? "import org.springframework.web.bind.annotation.PathVariable;\n" : "";
|
|
||||||
String requestParamImport = params.stream().anyMatch(p -> "QUERY".equalsIgnoreCase(p.getParamIn()))
|
|
||||||
? "import org.springframework.web.bind.annotation.RequestParam;\n" : "";
|
|
||||||
String requestBodyImport = isPost
|
|
||||||
? "import org.springframework.web.bind.annotation.RequestBody;\n" : "";
|
|
||||||
String mappingImport = isPost
|
|
||||||
? "import org.springframework.web.bind.annotation.PostMapping;\n"
|
|
||||||
: "import org.springframework.web.bind.annotation.GetMapping;\n";
|
|
||||||
|
|
||||||
String mappingPath = buildMappingPath(params, config.getExternalPath());
|
|
||||||
String requestMappingPath = "/api/" + domain;
|
String requestMappingPath = "/api/" + domain;
|
||||||
|
|
||||||
return """
|
// imports 합산 (중복 제거)
|
||||||
package {{PACKAGE}};
|
Set<String> importSet = new LinkedHashSet<>();
|
||||||
|
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;");
|
||||||
|
importSet.add("import io.swagger.v3.oas.annotations.tags.Tag;");
|
||||||
|
importSet.add("import lombok.RequiredArgsConstructor;");
|
||||||
|
importSet.add("import org.springframework.http.ResponseEntity;");
|
||||||
|
importSet.add("import org.springframework.web.bind.annotation.RequestMapping;");
|
||||||
|
importSet.add("import org.springframework.web.bind.annotation.RestController;");
|
||||||
|
|
||||||
import {{DTO_IMPORT}};
|
boolean anyList = configs.stream().anyMatch(c -> "LIST".equalsIgnoreCase(c.getResponseType()));
|
||||||
import {{SERVICE_IMPORT}};
|
boolean anyPost = configs.stream().anyMatch(c -> "POST".equalsIgnoreCase(c.getHttpMethod()));
|
||||||
import com.snp.batch.common.web.ApiResponse;
|
boolean anyGet = configs.stream().anyMatch(c -> !"POST".equalsIgnoreCase(c.getHttpMethod()));
|
||||||
import com.snp.batch.common.web.controller.BaseBypassController;
|
boolean anyPath = configs.stream()
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
.anyMatch(c -> c.getParams().stream().anyMatch(p -> "PATH".equalsIgnoreCase(p.getParamIn())));
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
boolean anyQuery = configs.stream()
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
.anyMatch(c -> c.getParams().stream().anyMatch(p -> "QUERY".equalsIgnoreCase(p.getParamIn())));
|
||||||
{{LIST_IMPORT}}import lombok.RequiredArgsConstructor;
|
boolean anyBody = configs.stream()
|
||||||
import org.springframework.http.ResponseEntity;
|
.anyMatch(c -> c.getParams().stream().anyMatch(p -> "BODY".equalsIgnoreCase(p.getParamIn())));
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
{{MAPPING_IMPORT}}{{PATH_VARIABLE_IMPORT}}{{REQUEST_PARAM_IMPORT}}{{REQUEST_BODY_IMPORT}}
|
|
||||||
/**
|
|
||||||
* {{DISPLAY_NAME}} bypass API
|
|
||||||
* S&P Maritime API에서 데이터를 실시간 조회하여 그대로 반환
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("{{REQUEST_MAPPING_PATH}}")
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Tag(name = "{{DOMAIN_CAP}}", description = "{{TAG_PREFIX}} {{DISPLAY_NAME}} bypass API")
|
|
||||||
public class {{DOMAIN_CAP}}Controller extends BaseBypassController {
|
|
||||||
|
|
||||||
private final {{SERVICE_CLASS}} {{SERVICE_FIELD}};
|
if (anyList) {
|
||||||
|
importSet.add("import java.util.List;");
|
||||||
|
}
|
||||||
|
if (anyPost) {
|
||||||
|
importSet.add("import org.springframework.web.bind.annotation.PostMapping;");
|
||||||
|
}
|
||||||
|
if (anyGet) {
|
||||||
|
importSet.add("import org.springframework.web.bind.annotation.GetMapping;");
|
||||||
|
}
|
||||||
|
if (anyPath) {
|
||||||
|
importSet.add("import org.springframework.web.bind.annotation.PathVariable;");
|
||||||
|
}
|
||||||
|
if (anyQuery) {
|
||||||
|
importSet.add("import org.springframework.web.bind.annotation.RequestParam;");
|
||||||
|
}
|
||||||
|
if (anyBody) {
|
||||||
|
importSet.add("import org.springframework.web.bind.annotation.RequestBody;");
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(
|
for (BypassApiConfig config : configs) {
|
||||||
summary = "{{DISPLAY_NAME}} 조회",
|
String endpointName = config.getEndpointName();
|
||||||
description = "S&P API에서 {{DISPLAY_NAME}} 데이터를 요청하고 응답을 그대로 반환합니다."
|
importSet.add("import " + dtoPackage + "." + endpointName + "Dto;");
|
||||||
)
|
importSet.add("import " + servicePackage + "." + endpointName + "Service;");
|
||||||
{{MAPPING_ANNOTATION}}
|
}
|
||||||
public ResponseEntity<ApiResponse<{{RESPONSE_GENERIC}}>> get{{DOMAIN_CAP}}Data({{PARAM_ANNOTATIONS}}) {
|
|
||||||
return execute(() -> {{SERVICE_FIELD}}.get{{DOMAIN_CAP}}Data({{SERVICE_CALL_ARGS}}));
|
String importsStr = importSet.stream().collect(Collectors.joining("\n"));
|
||||||
}
|
|
||||||
}
|
// 필드 선언부
|
||||||
"""
|
StringBuilder fields = new StringBuilder();
|
||||||
.replace("{{PACKAGE}}", packageName)
|
for (BypassApiConfig config : configs) {
|
||||||
.replace("{{DTO_IMPORT}}", dtoPackage + "." + dtoClass)
|
String endpointName = config.getEndpointName();
|
||||||
.replace("{{SERVICE_IMPORT}}", servicePackage + "." + serviceClass)
|
String serviceClass = endpointName + "Service";
|
||||||
.replace("{{LIST_IMPORT}}", listImport)
|
String serviceField = Character.toLowerCase(serviceClass.charAt(0)) + serviceClass.substring(1);
|
||||||
.replace("{{PATH_VARIABLE_IMPORT}}", pathVariableImport)
|
fields.append(" private final ").append(serviceClass).append(" ").append(serviceField).append(";\n");
|
||||||
.replace("{{REQUEST_PARAM_IMPORT}}", requestParamImport)
|
}
|
||||||
.replace("{{REQUEST_BODY_IMPORT}}", requestBodyImport)
|
|
||||||
.replace("{{MAPPING_IMPORT}}", mappingImport)
|
// 태그 description은 첫 번째 config의 webclientBean 기준
|
||||||
.replace("{{TAG_PREFIX}}", getTagPrefix(config.getWebclientBean()))
|
String tagPrefix = getTagPrefix(configs.get(0).getWebclientBean());
|
||||||
.replace("{{DISPLAY_NAME}}", config.getDisplayName())
|
String tagDescription = tagPrefix + " " + domainCap + " bypass API";
|
||||||
.replace("{{DOMAIN_CAP}}", domainCap)
|
|
||||||
.replace("{{REQUEST_MAPPING_PATH}}", requestMappingPath)
|
// 엔드포인트 메서드 목록
|
||||||
.replace("{{SERVICE_CLASS}}", serviceClass)
|
StringBuilder methods = new StringBuilder();
|
||||||
.replace("{{SERVICE_FIELD}}", serviceField)
|
for (BypassApiConfig config : configs) {
|
||||||
.replace("{{MAPPING_ANNOTATION}}", mappingAnnotation + mappingPath)
|
String endpointName = config.getEndpointName();
|
||||||
.replace("{{RESPONSE_GENERIC}}", responseGeneric)
|
String dtoClass = endpointName + "Dto";
|
||||||
.replace("{{PARAM_ANNOTATIONS}}", paramAnnotations.isEmpty() ? "" : paramAnnotations)
|
String serviceClass = endpointName + "Service";
|
||||||
.replace("{{SERVICE_CALL_ARGS}}", serviceCallArgs);
|
String serviceField = Character.toLowerCase(serviceClass.charAt(0)) + serviceClass.substring(1);
|
||||||
|
boolean isList = "LIST".equalsIgnoreCase(config.getResponseType());
|
||||||
|
boolean isPost = "POST".equalsIgnoreCase(config.getHttpMethod());
|
||||||
|
|
||||||
|
String responseGeneric = isList ? "List<" + dtoClass + ">" : dtoClass;
|
||||||
|
String mappingAnnotation = isPost ? "@PostMapping" : "@GetMapping";
|
||||||
|
String mappingPath = buildMappingPath(config.getParams(), config.getExternalPath());
|
||||||
|
String paramAnnotations = buildControllerParamAnnotations(config.getParams());
|
||||||
|
String serviceCallArgs = buildServiceCallArgs(config.getParams());
|
||||||
|
String methodName = "get" + endpointName + "Data";
|
||||||
|
|
||||||
|
methods.append("\n");
|
||||||
|
methods.append(" @Operation(\n");
|
||||||
|
methods.append(" summary = \"").append(config.getDisplayName()).append(" 조회\",\n");
|
||||||
|
methods.append(" description = \"S&P API에서 ").append(config.getDisplayName())
|
||||||
|
.append(" 데이터를 요청하고 응답을 그대로 반환합니다.\"\n");
|
||||||
|
methods.append(" )\n");
|
||||||
|
methods.append(" ").append(mappingAnnotation).append(mappingPath).append("\n");
|
||||||
|
methods.append(" public ResponseEntity<ApiResponse<").append(responseGeneric).append(">> ")
|
||||||
|
.append(methodName).append("(");
|
||||||
|
if (!paramAnnotations.isEmpty()) {
|
||||||
|
methods.append(paramAnnotations);
|
||||||
|
}
|
||||||
|
methods.append(") {\n");
|
||||||
|
methods.append(" return execute(() -> ").append(serviceField)
|
||||||
|
.append(".").append(methodName).append("(").append(serviceCallArgs).append("));\n");
|
||||||
|
methods.append(" }\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "package " + packageName + ";\n\n"
|
||||||
|
+ importsStr + "\n\n"
|
||||||
|
+ "/**\n"
|
||||||
|
+ " * " + domainCap + " bypass API\n"
|
||||||
|
+ " * S&P Maritime API에서 데이터를 실시간 조회하여 그대로 반환\n"
|
||||||
|
+ " */\n"
|
||||||
|
+ "@RestController\n"
|
||||||
|
+ "@RequestMapping(\"" + requestMappingPath + "\")\n"
|
||||||
|
+ "@RequiredArgsConstructor\n"
|
||||||
|
+ "@Tag(name = \"" + domainCap + "\", description = \"" + tagDescription + "\")\n"
|
||||||
|
+ "public class " + domainCap + "Controller extends BaseBypassController {\n\n"
|
||||||
|
+ fields
|
||||||
|
+ methods
|
||||||
|
+ "}\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -355,7 +403,6 @@ public class BypassCodeGenerator {
|
|||||||
* PATH 파라미터 imo 추가 시 → ("/CompliancesByImos/{imo}")
|
* PATH 파라미터 imo 추가 시 → ("/CompliancesByImos/{imo}")
|
||||||
*/
|
*/
|
||||||
private String buildMappingPath(List<BypassApiParam> params, String externalPath) {
|
private String buildMappingPath(List<BypassApiParam> params, String externalPath) {
|
||||||
// externalPath에서 마지막 세그먼트 추출
|
|
||||||
String endpointSegment = "";
|
String endpointSegment = "";
|
||||||
if (externalPath != null && !externalPath.isEmpty()) {
|
if (externalPath != null && !externalPath.isEmpty()) {
|
||||||
String[] segments = externalPath.split("/");
|
String[] segments = externalPath.split("/");
|
||||||
@ -364,7 +411,6 @@ public class BypassCodeGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PATH 파라미터 추가
|
|
||||||
List<BypassApiParam> pathParams = params.stream()
|
List<BypassApiParam> pathParams = params.stream()
|
||||||
.filter(p -> "PATH".equalsIgnoreCase(p.getParamIn()))
|
.filter(p -> "PATH".equalsIgnoreCase(p.getParamIn()))
|
||||||
.sorted((a, b) -> Integer.compare(a.getSortOrder(), b.getSortOrder()))
|
.sorted((a, b) -> Integer.compare(a.getSortOrder(), b.getSortOrder()))
|
||||||
|
|||||||
@ -57,8 +57,10 @@ public class BypassConfigService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public BypassConfigResponse createConfig(BypassConfigRequest request) {
|
public BypassConfigResponse createConfig(BypassConfigRequest request) {
|
||||||
if (configRepository.existsByDomainName(request.getDomainName())) {
|
String endpointName = extractEndpointName(request.getExternalPath());
|
||||||
throw new IllegalArgumentException("이미 존재하는 도메인입니다: " + request.getDomainName());
|
if (configRepository.existsByDomainNameAndEndpointName(request.getDomainName(), endpointName)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"이미 존재하는 도메인+엔드포인트 조합입니다: " + request.getDomainName() + "/" + endpointName);
|
||||||
}
|
}
|
||||||
BypassApiConfig config = toEntity(request);
|
BypassApiConfig config = toEntity(request);
|
||||||
|
|
||||||
@ -155,6 +157,7 @@ public class BypassConfigService {
|
|||||||
return BypassConfigResponse.builder()
|
return BypassConfigResponse.builder()
|
||||||
.id(config.getId())
|
.id(config.getId())
|
||||||
.domainName(config.getDomainName())
|
.domainName(config.getDomainName())
|
||||||
|
.endpointName(config.getEndpointName())
|
||||||
.displayName(config.getDisplayName())
|
.displayName(config.getDisplayName())
|
||||||
.webclientBean(config.getWebclientBean())
|
.webclientBean(config.getWebclientBean())
|
||||||
.externalPath(config.getExternalPath())
|
.externalPath(config.getExternalPath())
|
||||||
@ -173,6 +176,7 @@ public class BypassConfigService {
|
|||||||
private BypassApiConfig toEntity(BypassConfigRequest request) {
|
private BypassApiConfig toEntity(BypassConfigRequest request) {
|
||||||
return BypassApiConfig.builder()
|
return BypassApiConfig.builder()
|
||||||
.domainName(request.getDomainName())
|
.domainName(request.getDomainName())
|
||||||
|
.endpointName(extractEndpointName(request.getExternalPath()))
|
||||||
.displayName(request.getDisplayName())
|
.displayName(request.getDisplayName())
|
||||||
.webclientBean(request.getWebclientBean())
|
.webclientBean(request.getWebclientBean())
|
||||||
.externalPath(request.getExternalPath())
|
.externalPath(request.getExternalPath())
|
||||||
@ -182,6 +186,18 @@ public class BypassConfigService {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* externalPath의 마지막 세그먼트를 endpointName으로 추출
|
||||||
|
* 예: "/RiskAndCompliance/CompliancesByImos" → "CompliancesByImos"
|
||||||
|
*/
|
||||||
|
private String extractEndpointName(String externalPath) {
|
||||||
|
if (externalPath == null || externalPath.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String[] segments = externalPath.split("/");
|
||||||
|
return segments[segments.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
private BypassApiParam toParamEntity(BypassParamDto dto) {
|
private BypassApiParam toParamEntity(BypassParamDto dto) {
|
||||||
return BypassApiParam.builder()
|
return BypassApiParam.builder()
|
||||||
.paramName(dto.getParamName())
|
.paramName(dto.getParamName())
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user