restapi 버튼 동작
This commit is contained in:
@@ -38,6 +38,7 @@ import ddlRoutes from "./routes/ddlRoutes";
|
||||
import entityReferenceRoutes from "./routes/entityReferenceRoutes";
|
||||
import externalCallRoutes from "./routes/externalCallRoutes";
|
||||
import externalCallConfigRoutes from "./routes/externalCallConfigRoutes";
|
||||
import dataflowExecutionRoutes from "./routes/dataflowExecutionRoutes";
|
||||
// import collectionRoutes from "./routes/collectionRoutes"; // 임시 주석
|
||||
// import batchRoutes from "./routes/batchRoutes"; // 임시 주석
|
||||
// import userRoutes from './routes/userRoutes';
|
||||
@@ -148,6 +149,7 @@ app.use("/api/ddl", ddlRoutes);
|
||||
app.use("/api/entity-reference", entityReferenceRoutes);
|
||||
app.use("/api/external-calls", externalCallRoutes);
|
||||
app.use("/api/external-call-configs", externalCallConfigRoutes);
|
||||
app.use("/api/dataflow", dataflowExecutionRoutes);
|
||||
// app.use("/api/collections", collectionRoutes); // 임시 주석
|
||||
// app.use("/api/batch", batchRoutes); // 임시 주석
|
||||
// app.use('/api/users', userRoutes);
|
||||
|
||||
@@ -727,3 +727,35 @@ function processDataflowInBackground(
|
||||
}
|
||||
}, 1000); // 1초 후 실행 시뮬레이션
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 전체 관계 목록 조회 (버튼 제어용)
|
||||
*/
|
||||
export async function getAllRelationships(
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
try {
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
|
||||
logger.info(`전체 관계 목록 조회 요청 - companyCode: ${companyCode}`);
|
||||
|
||||
// 모든 관계도에서 관계 목록을 가져옴
|
||||
const allRelationships = await dataflowDiagramService.getAllRelationshipsForButtonControl(companyCode);
|
||||
|
||||
logger.info(`전체 관계 ${allRelationships.length}개 조회 완료`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: allRelationships,
|
||||
message: `전체 관계 ${allRelationships.length}개 조회 완료`,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("전체 관계 목록 조회 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : "전체 관계 목록 조회 실패",
|
||||
errorCode: "GET_ALL_RELATIONSHIPS_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
235
backend-node/src/controllers/dataflowExecutionController.ts
Normal file
235
backend-node/src/controllers/dataflowExecutionController.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* 🔥 데이터플로우 실행 컨트롤러
|
||||
*
|
||||
* 버튼 제어에서 관계 실행 시 사용되는 컨트롤러
|
||||
*/
|
||||
|
||||
import { Request, Response } from "express";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/**
|
||||
* 데이터 액션 실행
|
||||
*/
|
||||
export async function executeDataAction(
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { tableName, data, actionType, connection } = req.body;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
|
||||
logger.info(`데이터 액션 실행 시작: ${actionType} on ${tableName}`, {
|
||||
tableName,
|
||||
actionType,
|
||||
dataKeys: Object.keys(data),
|
||||
connection: connection?.name,
|
||||
});
|
||||
|
||||
// 연결 정보에 따라 다른 데이터베이스에 저장
|
||||
let result;
|
||||
|
||||
if (connection && connection.id !== 0) {
|
||||
// 외부 데이터베이스 연결
|
||||
result = await executeExternalDatabaseAction(tableName, data, actionType, connection);
|
||||
} else {
|
||||
// 메인 데이터베이스 (현재 시스템)
|
||||
result = await executeMainDatabaseAction(tableName, data, actionType, companyCode);
|
||||
}
|
||||
|
||||
logger.info(`데이터 액션 실행 완료: ${actionType} on ${tableName}`, result);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `데이터 액션 실행 완료: ${actionType}`,
|
||||
data: result,
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
logger.error("데이터 액션 실행 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: `데이터 액션 실행 실패: ${error.message}`,
|
||||
errorCode: "DATA_ACTION_EXECUTION_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메인 데이터베이스에서 데이터 액션 실행
|
||||
*/
|
||||
async function executeMainDatabaseAction(
|
||||
tableName: string,
|
||||
data: Record<string, any>,
|
||||
actionType: string,
|
||||
companyCode: string
|
||||
): Promise<any> {
|
||||
try {
|
||||
// 회사 코드 추가
|
||||
const dataWithCompany = {
|
||||
...data,
|
||||
company_code: companyCode,
|
||||
};
|
||||
|
||||
switch (actionType.toLowerCase()) {
|
||||
case 'insert':
|
||||
return await executeInsert(tableName, dataWithCompany);
|
||||
case 'update':
|
||||
return await executeUpdate(tableName, dataWithCompany);
|
||||
case 'upsert':
|
||||
return await executeUpsert(tableName, dataWithCompany);
|
||||
case 'delete':
|
||||
return await executeDelete(tableName, dataWithCompany);
|
||||
default:
|
||||
throw new Error(`지원하지 않는 액션 타입: ${actionType}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`메인 DB 액션 실행 오류 (${actionType}):`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 외부 데이터베이스에서 데이터 액션 실행
|
||||
*/
|
||||
async function executeExternalDatabaseAction(
|
||||
tableName: string,
|
||||
data: Record<string, any>,
|
||||
actionType: string,
|
||||
connection: any
|
||||
): Promise<any> {
|
||||
try {
|
||||
// TODO: 외부 데이터베이스 연결 및 실행 로직 구현
|
||||
// 현재는 로그만 출력하고 성공으로 처리
|
||||
logger.info(`외부 DB 액션 실행: ${connection.name} (${connection.host}:${connection.port})`);
|
||||
logger.info(`테이블: ${tableName}, 액션: ${actionType}`, data);
|
||||
|
||||
// 임시 성공 응답
|
||||
return {
|
||||
success: true,
|
||||
message: `외부 DB 액션 실행 완료: ${actionType} on ${tableName}`,
|
||||
connection: connection.name,
|
||||
affectedRows: 1,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`외부 DB 액션 실행 오류 (${actionType}):`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INSERT 실행
|
||||
*/
|
||||
async function executeInsert(tableName: string, data: Record<string, any>): Promise<any> {
|
||||
try {
|
||||
// 동적 테이블 접근을 위한 raw query 사용
|
||||
const columns = Object.keys(data).join(', ');
|
||||
const values = Object.values(data);
|
||||
const placeholders = values.map((_, index) => `$${index + 1}`).join(', ');
|
||||
|
||||
const query = `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders}) RETURNING *`;
|
||||
|
||||
logger.info(`INSERT 쿼리 실행:`, { query, values });
|
||||
|
||||
const result = await prisma.$queryRawUnsafe(query, ...values);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: 'insert',
|
||||
tableName,
|
||||
data: result,
|
||||
affectedRows: Array.isArray(result) ? result.length : 1,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`INSERT 실행 오류:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UPDATE 실행
|
||||
*/
|
||||
async function executeUpdate(tableName: string, data: Record<string, any>): Promise<any> {
|
||||
try {
|
||||
// ID 또는 기본키를 기준으로 업데이트
|
||||
const { id, ...updateData } = data;
|
||||
|
||||
if (!id) {
|
||||
throw new Error('UPDATE를 위한 ID가 필요합니다');
|
||||
}
|
||||
|
||||
const setClause = Object.keys(updateData)
|
||||
.map((key, index) => `${key} = $${index + 1}`)
|
||||
.join(', ');
|
||||
|
||||
const values = Object.values(updateData);
|
||||
const query = `UPDATE ${tableName} SET ${setClause} WHERE id = $${values.length + 1} RETURNING *`;
|
||||
|
||||
logger.info(`UPDATE 쿼리 실행:`, { query, values: [...values, id] });
|
||||
|
||||
const result = await prisma.$queryRawUnsafe(query, ...values, id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: 'update',
|
||||
tableName,
|
||||
data: result,
|
||||
affectedRows: Array.isArray(result) ? result.length : 1,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`UPDATE 실행 오류:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UPSERT 실행
|
||||
*/
|
||||
async function executeUpsert(tableName: string, data: Record<string, any>): Promise<any> {
|
||||
try {
|
||||
// 먼저 INSERT를 시도하고, 실패하면 UPDATE
|
||||
try {
|
||||
return await executeInsert(tableName, data);
|
||||
} catch (insertError) {
|
||||
// INSERT 실패 시 UPDATE 시도
|
||||
logger.info(`INSERT 실패, UPDATE 시도:`, insertError);
|
||||
return await executeUpdate(tableName, data);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`UPSERT 실행 오류:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE 실행
|
||||
*/
|
||||
async function executeDelete(tableName: string, data: Record<string, any>): Promise<any> {
|
||||
try {
|
||||
const { id } = data;
|
||||
|
||||
if (!id) {
|
||||
throw new Error('DELETE를 위한 ID가 필요합니다');
|
||||
}
|
||||
|
||||
const query = `DELETE FROM ${tableName} WHERE id = $1 RETURNING *`;
|
||||
|
||||
logger.info(`DELETE 쿼리 실행:`, { query, values: [id] });
|
||||
|
||||
const result = await prisma.$queryRawUnsafe(query, id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: 'delete',
|
||||
tableName,
|
||||
data: result,
|
||||
affectedRows: Array.isArray(result) ? result.length : 1,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`DELETE 실행 오류:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
19
backend-node/src/routes/dataflowExecutionRoutes.ts
Normal file
19
backend-node/src/routes/dataflowExecutionRoutes.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 🔥 데이터플로우 실행 라우트
|
||||
*
|
||||
* 버튼 제어에서 관계 실행 시 사용되는 API 엔드포인트
|
||||
*/
|
||||
|
||||
import express from "express";
|
||||
import { authenticateToken } from "../middleware/authMiddleware";
|
||||
import { executeDataAction } from "../controllers/dataflowExecutionController";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 🔥 모든 라우트에 인증 미들웨어 적용
|
||||
router.use(authenticateToken);
|
||||
|
||||
// 데이터 액션 실행
|
||||
router.post("/execute-data-action", executeDataAction);
|
||||
|
||||
export default router;
|
||||
@@ -249,4 +249,80 @@ router.post("/:id/test", async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 🔥 개선된 외부호출 실행 (데이터 매핑 통합)
|
||||
* POST /api/external-call-configs/:id/execute
|
||||
*/
|
||||
router.post("/:id/execute", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
if (isNaN(id)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "유효하지 않은 설정 ID입니다.",
|
||||
errorCode: "INVALID_CONFIG_ID",
|
||||
});
|
||||
}
|
||||
|
||||
const { requestData, contextData } = req.body;
|
||||
|
||||
// 사용자 정보 가져오기
|
||||
const userInfo = (req as any).user;
|
||||
const userId = userInfo?.userId || "SYSTEM";
|
||||
const companyCode = userInfo?.companyCode || "*";
|
||||
|
||||
const executionResult = await externalCallConfigService.executeConfigWithDataMapping(
|
||||
id,
|
||||
requestData || {},
|
||||
{
|
||||
...contextData,
|
||||
userId,
|
||||
companyCode,
|
||||
executedAt: new Date().toISOString(),
|
||||
}
|
||||
);
|
||||
|
||||
return res.json({
|
||||
success: executionResult.success,
|
||||
message: executionResult.message,
|
||||
data: executionResult.data,
|
||||
executionTime: executionResult.executionTime,
|
||||
error: executionResult.error,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("외부호출 실행 API 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : "외부호출 실행 실패",
|
||||
errorCode: "EXTERNAL_CALL_EXECUTE_ERROR",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 🔥 버튼 제어용 외부호출 목록 조회 (간소화된 정보)
|
||||
* GET /api/external-call-configs/for-button-control
|
||||
*/
|
||||
router.get("/for-button-control", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const userInfo = (req as any).user;
|
||||
const companyCode = userInfo?.companyCode || "*";
|
||||
|
||||
const configs = await externalCallConfigService.getConfigsForButtonControl(companyCode);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: configs,
|
||||
message: `버튼 제어용 외부호출 설정 ${configs.length}개 조회 완료`,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("버튼 제어용 외부호출 설정 조회 API 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : "외부호출 설정 조회 실패",
|
||||
errorCode: "EXTERNAL_CALL_BUTTON_CONTROL_LIST_ERROR",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
executeOptimizedButton,
|
||||
executeSimpleDataflow,
|
||||
getJobStatus,
|
||||
getAllRelationships,
|
||||
} from "../controllers/buttonDataflowController";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import config from "../config/environment";
|
||||
@@ -52,6 +53,9 @@ if (config.nodeEnv !== "production") {
|
||||
// 특정 관계도의 관계 목록 조회
|
||||
router.get("/diagrams/:diagramId/relationships", getDiagramRelationships);
|
||||
|
||||
// 🔥 전체 관계 목록 조회 (버튼 제어용)
|
||||
router.get("/relationships/all", getAllRelationships);
|
||||
|
||||
// 관계 미리보기 정보 조회
|
||||
router.get(
|
||||
"/diagrams/:diagramId/relationships/:relationshipId/preview",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user