feat: Add procedure and function management in flow controller

- Introduced new endpoints in FlowController for listing procedures and retrieving procedure parameters, enhancing the flow management capabilities.
- Updated FlowDataMoveService to support procedure calls during data movement, ensuring seamless integration with external and internal databases.
- Enhanced NodeFlowExecutionService to execute procedure call actions, allowing for dynamic execution of stored procedures within flow nodes.
- Updated frontend components to support procedure selection and parameter management, improving user experience in configuring flow steps.
- Added necessary types and API functions for handling procedure-related data, ensuring type safety and clarity in implementation.
This commit is contained in:
kjs
2026-03-03 14:33:17 +09:00
parent fd5c61b12a
commit f697e1e897
21 changed files with 2303 additions and 41 deletions

View File

@@ -11,6 +11,7 @@
import { query, queryOne, transaction } from "../database/db";
import { logger } from "../utils/logger";
import axios from "axios";
import { FlowProcedureService } from "./flowProcedureService";
// ===== 타입 정의 =====
@@ -36,6 +37,7 @@ export type NodeType =
| "emailAction" // 이메일 발송 액션
| "scriptAction" // 스크립트 실행 액션
| "httpRequestAction" // HTTP 요청 액션
| "procedureCallAction" // 프로시저/함수 호출 액션
| "comment"
| "log";
@@ -663,6 +665,9 @@ export class NodeFlowExecutionService {
case "httpRequestAction":
return this.executeHttpRequestAction(node, inputData, context);
case "procedureCallAction":
return this.executeProcedureCallAction(node, inputData, context, client);
case "comment":
case "log":
// 로그/코멘트는 실행 없이 통과
@@ -4856,4 +4861,105 @@ export class NodeFlowExecutionService {
);
}
}
/**
* 프로시저/함수 호출 액션 노드 실행
*/
private static async executeProcedureCallAction(
node: FlowNode,
inputData: any,
context: ExecutionContext,
client?: any
): Promise<any> {
const {
dbSource = "internal",
connectionId,
procedureName,
procedureSchema = "public",
callType = "function",
parameters = [],
} = node.data;
logger.info(
`🔧 프로시저 호출 노드 실행: ${node.data.displayName || node.id}`
);
logger.info(
` 프로시저: ${procedureSchema}.${procedureName} (${callType}), DB: ${dbSource}`
);
if (!procedureName) {
throw new Error("프로시저/함수가 선택되지 않았습니다.");
}
const dataArray = Array.isArray(inputData)
? inputData
: inputData
? [inputData]
: [{}];
const procedureService = new FlowProcedureService();
const results: any[] = [];
const config = {
type: "procedure" as const,
dbSource: dbSource as "internal" | "external",
connectionId,
procedureName,
procedureSchema,
callType: callType as "procedure" | "function",
parameters: parameters.map((p: any) => ({
name: p.name,
dataType: p.dataType,
mode: p.mode || "IN",
source: p.source || "static",
field: p.field,
value: p.value,
})),
};
for (const record of dataArray) {
try {
logger.info(` 입력 레코드 키: ${Object.keys(record).join(", ")}`);
const execResult = await procedureService.executeProcedure(
config,
record,
dbSource === "internal" ? client : undefined
);
logger.info(` ✅ 프로시저 실행 성공: ${procedureName}`);
// 프로시저 반환값을 레코드에 평탄화하여 다음 노드에서 필드로 참조 가능하게 함
let flatResult: Record<string, any> = {};
if (Array.isArray(execResult.result) && execResult.result.length > 0) {
const row = execResult.result[0];
for (const [key, val] of Object.entries(row)) {
// 함수명과 동일한 키(SELECT fn() 결과)는 _procedureReturn으로 매핑
if (key === procedureName) {
flatResult["_procedureReturn"] = val;
} else {
flatResult[key] = val;
}
}
logger.info(` 반환 필드: ${Object.keys(flatResult).join(", ")}`);
}
results.push({
...record,
...flatResult,
_procedureResult: execResult.result,
_procedureSuccess: true,
});
} catch (error: any) {
logger.error(` ❌ 프로시저 실행 실패: ${error.message}`);
throw error;
}
}
logger.info(
`🔧 프로시저 호출 완료: ${results.length}건 처리`
);
return results;
}
}