restapi 버튼 동작

This commit is contained in:
kjs
2025-09-29 12:17:10 +09:00
parent cedb5e3ec3
commit c9afdec09f
19 changed files with 1910 additions and 81 deletions

View File

@@ -384,3 +384,66 @@ export const copyDataflowDiagram = async (
throw error;
}
};
/**
* 🔥 전체 관계 목록 조회 (버튼 제어용)
* dataflow_diagrams 테이블에서 관계도 데이터를 조회 (데이터 흐름 관계 화면과 동일)
*/
export const getAllRelationshipsForButtonControl = async (
companyCode: string
): Promise<Array<{
id: string;
name: string;
sourceTable: string;
targetTable: string;
category: string;
}>> => {
try {
logger.info(`전체 관계 목록 조회 시작 - companyCode: ${companyCode}`);
// dataflow_diagrams 테이블에서 관계도들을 조회
const diagrams = await prisma.dataflow_diagrams.findMany({
where: {
company_code: companyCode,
},
select: {
diagram_id: true,
diagram_name: true,
relationships: true,
},
orderBy: {
updated_at: "desc",
},
});
const allRelationships = diagrams.map((diagram) => {
// relationships 구조에서 테이블 정보 추출
const relationships = diagram.relationships as any || {};
// 테이블 정보 추출
let sourceTable = "";
let targetTable = "";
if (relationships.fromTable?.tableName) {
sourceTable = relationships.fromTable.tableName;
}
if (relationships.toTable?.tableName) {
targetTable = relationships.toTable.tableName;
}
return {
id: diagram.diagram_id.toString(),
name: diagram.diagram_name || `관계 ${diagram.diagram_id}`,
sourceTable: sourceTable,
targetTable: targetTable,
category: "데이터 흐름",
};
});
logger.info(`전체 관계 ${allRelationships.length}개 조회 완료`);
return allRelationships;
} catch (error) {
logger.error("전체 관계 목록 조회 서비스 오류:", error);
throw error;
}
};

View File

@@ -308,6 +308,265 @@ export class ExternalCallConfigService {
};
}
}
/**
* 🔥 데이터 매핑과 함께 외부호출 실행
*/
async executeConfigWithDataMapping(
configId: number,
requestData: Record<string, any>,
contextData: Record<string, any>
): Promise<{
success: boolean;
message: string;
data?: any;
executionTime: number;
error?: string;
}> {
const startTime = performance.now();
try {
logger.info(`=== 외부호출 실행 시작 (ID: ${configId}) ===`);
// 1. 설정 조회
const config = await this.getConfigById(configId);
if (!config) {
throw new Error(`외부호출 설정을 찾을 수 없습니다: ${configId}`);
}
// 2. 데이터 매핑 처리 (있는 경우)
let processedData = requestData;
const configData = config.config_data as any;
if (configData?.dataMappingConfig?.outboundMapping) {
logger.info("Outbound 데이터 매핑 처리 중...");
processedData = await this.processOutboundMapping(
configData.dataMappingConfig.outboundMapping,
requestData
);
}
// 3. 외부 API 호출
const callResult = await this.executeExternalCall(config, processedData, contextData);
// 4. Inbound 데이터 매핑 처리 (있는 경우)
if (
callResult.success &&
configData?.dataMappingConfig?.inboundMapping
) {
logger.info("Inbound 데이터 매핑 처리 중...");
await this.processInboundMapping(
configData.dataMappingConfig.inboundMapping,
callResult.data
);
}
const executionTime = performance.now() - startTime;
logger.info(`외부호출 실행 완료: ${executionTime.toFixed(2)}ms`);
return {
success: callResult.success,
message: callResult.success
? `외부호출 '${config.config_name}' 실행 완료`
: `외부호출 '${config.config_name}' 실행 실패`,
data: callResult.data,
executionTime,
error: callResult.error,
};
} catch (error) {
const executionTime = performance.now() - startTime;
logger.error("외부호출 실행 실패:", error);
const errorMessage = error instanceof Error ? error.message : "알 수 없는 오류";
return {
success: false,
message: `외부호출 실행 실패: ${errorMessage}`,
executionTime,
error: errorMessage,
};
}
}
/**
* 🔥 버튼 제어용 외부호출 설정 목록 조회 (간소화된 정보)
*/
async getConfigsForButtonControl(companyCode: string): Promise<Array<{
id: string;
name: string;
description?: string;
apiUrl: string;
method: string;
hasDataMapping: boolean;
}>> {
try {
const configs = await prisma.external_call_configs.findMany({
where: {
company_code: companyCode,
is_active: "Y",
},
select: {
id: true,
config_name: true,
description: true,
config_data: true,
},
orderBy: {
config_name: "asc",
},
});
return configs.map((config) => {
const configData = config.config_data as any;
return {
id: config.id.toString(),
name: config.config_name,
description: config.description || undefined,
apiUrl: configData?.restApiSettings?.apiUrl || "",
method: configData?.restApiSettings?.httpMethod || "GET",
hasDataMapping: !!(configData?.dataMappingConfig),
};
});
} catch (error) {
logger.error("버튼 제어용 외부호출 설정 조회 실패:", error);
throw error;
}
}
/**
* 🔥 실제 외부 API 호출 실행
*/
private async executeExternalCall(
config: ExternalCallConfig,
requestData: Record<string, any>,
contextData: Record<string, any>
): Promise<{ success: boolean; data?: any; error?: string }> {
try {
const configData = config.config_data as any;
const restApiSettings = configData?.restApiSettings;
if (!restApiSettings) {
throw new Error("REST API 설정이 없습니다.");
}
const { apiUrl, httpMethod, headers = {}, timeout = 30000 } = restApiSettings;
// 요청 헤더 준비
const requestHeaders = {
"Content-Type": "application/json",
...headers,
};
// 인증 처리
if (restApiSettings.authentication?.type === "basic") {
const { username, password } = restApiSettings.authentication;
const credentials = Buffer.from(`${username}:${password}`).toString("base64");
requestHeaders["Authorization"] = `Basic ${credentials}`;
} else if (restApiSettings.authentication?.type === "bearer") {
const { token } = restApiSettings.authentication;
requestHeaders["Authorization"] = `Bearer ${token}`;
}
// 요청 본문 준비
let requestBody = undefined;
if (["POST", "PUT", "PATCH"].includes(httpMethod.toUpperCase())) {
requestBody = JSON.stringify({
...requestData,
_context: contextData, // 컨텍스트 정보 추가
});
}
logger.info(`외부 API 호출: ${httpMethod} ${apiUrl}`);
// 실제 HTTP 요청 (여기서는 간단한 예시)
// 실제 구현에서는 axios나 fetch를 사용
const response = await fetch(apiUrl, {
method: httpMethod,
headers: requestHeaders,
body: requestBody,
signal: AbortSignal.timeout(timeout),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const responseData = await response.json();
return {
success: true,
data: responseData,
};
} catch (error) {
logger.error("외부 API 호출 실패:", error);
const errorMessage = error instanceof Error ? error.message : "알 수 없는 오류";
return {
success: false,
error: errorMessage,
};
}
}
/**
* 🔥 Outbound 데이터 매핑 처리
*/
private async processOutboundMapping(
mapping: any,
sourceData: Record<string, any>
): Promise<Record<string, any>> {
try {
// 간단한 매핑 로직 (실제로는 더 복잡한 변환 로직 필요)
const mappedData: Record<string, any> = {};
if (mapping.fieldMappings) {
for (const fieldMapping of mapping.fieldMappings) {
const { sourceField, targetField, transformation } = fieldMapping;
let value = sourceData[sourceField];
// 변환 로직 적용
if (transformation) {
switch (transformation.type) {
case "format":
// 포맷 변환 로직
break;
case "calculate":
// 계산 로직
break;
default:
// 기본값 그대로 사용
break;
}
}
mappedData[targetField] = value;
}
}
return mappedData;
} catch (error) {
logger.error("Outbound 데이터 매핑 처리 실패:", error);
return sourceData; // 실패 시 원본 데이터 반환
}
}
/**
* 🔥 Inbound 데이터 매핑 처리
*/
private async processInboundMapping(
mapping: any,
responseData: any
): Promise<void> {
try {
// Inbound 매핑 로직 (응답 데이터를 내부 시스템에 저장)
logger.info("Inbound 데이터 매핑 처리:", mapping);
// 실제 구현에서는 응답 데이터를 파싱하여 내부 테이블에 저장하는 로직 필요
// 예: 외부 API에서 받은 사용자 정보를 내부 사용자 테이블에 업데이트
} catch (error) {
logger.error("Inbound 데이터 매핑 처리 실패:", error);
// Inbound 매핑 실패는 전체 플로우를 중단하지 않음
}
}
}
export default new ExternalCallConfigService();