워크플로우 restapi도 연결가능하고여러개 가능하게 구현시켜놓음
This commit is contained in:
@@ -66,11 +66,12 @@ export class FlowController {
|
||||
return;
|
||||
}
|
||||
|
||||
// REST API인 경우 테이블 존재 확인 스킵
|
||||
const isRestApi = dbSourceType === "restapi";
|
||||
// REST API 또는 다중 연결인 경우 테이블 존재 확인 스킵
|
||||
const isRestApi = dbSourceType === "restapi" || dbSourceType === "multi_restapi";
|
||||
const isMultiConnection = dbSourceType === "multi_restapi" || dbSourceType === "multi_external_db";
|
||||
|
||||
// 테이블 이름이 제공된 경우에만 존재 확인 (REST API 제외)
|
||||
if (tableName && !isRestApi && !tableName.startsWith("_restapi_")) {
|
||||
// 테이블 이름이 제공된 경우에만 존재 확인 (REST API 및 다중 연결 제외)
|
||||
if (tableName && !isRestApi && !isMultiConnection && !tableName.startsWith("_restapi_") && !tableName.startsWith("_multi_restapi_") && !tableName.startsWith("_multi_external_db_")) {
|
||||
const tableExists =
|
||||
await this.flowDefinitionService.checkTableExists(tableName);
|
||||
if (!tableExists) {
|
||||
@@ -92,6 +93,7 @@ export class FlowController {
|
||||
restApiConnectionId,
|
||||
restApiEndpoint,
|
||||
restApiJsonPath,
|
||||
restApiConnections: req.body.restApiConnections, // 다중 REST API 설정
|
||||
},
|
||||
userId,
|
||||
userCompanyCode
|
||||
|
||||
@@ -1091,4 +1091,150 @@ export class ExternalRestApiConnectionService {
|
||||
throw new Error("올바르지 않은 인증 타입입니다.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 다중 REST API 데이터 조회 및 병합
|
||||
* 여러 REST API의 응답을 병합하여 하나의 데이터셋으로 반환
|
||||
*/
|
||||
static async fetchMultipleData(
|
||||
configs: Array<{
|
||||
connectionId: number;
|
||||
endpoint: string;
|
||||
jsonPath: string;
|
||||
alias: string;
|
||||
}>,
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<{
|
||||
rows: any[];
|
||||
columns: Array<{ columnName: string; columnLabel: string; dataType: string; sourceApi: string }>;
|
||||
total: number;
|
||||
sources: Array<{ connectionId: number; connectionName: string; rowCount: number }>;
|
||||
}>> {
|
||||
try {
|
||||
logger.info(`다중 REST API 데이터 조회 시작: ${configs.length}개 API`);
|
||||
|
||||
// 각 API에서 데이터 조회
|
||||
const results = await Promise.all(
|
||||
configs.map(async (config) => {
|
||||
try {
|
||||
const result = await this.fetchData(
|
||||
config.connectionId,
|
||||
config.endpoint,
|
||||
config.jsonPath,
|
||||
userCompanyCode
|
||||
);
|
||||
|
||||
if (result.success && result.data) {
|
||||
return {
|
||||
success: true,
|
||||
connectionId: config.connectionId,
|
||||
connectionName: result.data.connectionInfo.connectionName,
|
||||
alias: config.alias,
|
||||
rows: result.data.rows,
|
||||
columns: result.data.columns,
|
||||
};
|
||||
} else {
|
||||
logger.warn(`API ${config.connectionId} 조회 실패:`, result.message);
|
||||
return {
|
||||
success: false,
|
||||
connectionId: config.connectionId,
|
||||
connectionName: "",
|
||||
alias: config.alias,
|
||||
rows: [],
|
||||
columns: [],
|
||||
error: result.message,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`API ${config.connectionId} 조회 오류:`, error);
|
||||
return {
|
||||
success: false,
|
||||
connectionId: config.connectionId,
|
||||
connectionName: "",
|
||||
alias: config.alias,
|
||||
rows: [],
|
||||
columns: [],
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 성공한 결과만 필터링
|
||||
const successfulResults = results.filter(r => r.success);
|
||||
|
||||
if (successfulResults.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: "모든 REST API 조회에 실패했습니다.",
|
||||
error: {
|
||||
code: "ALL_APIS_FAILED",
|
||||
details: results.map(r => ({ connectionId: r.connectionId, error: r.error })),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 컬럼 병합 (별칭 적용)
|
||||
const mergedColumns: Array<{ columnName: string; columnLabel: string; dataType: string; sourceApi: string }> = [];
|
||||
|
||||
for (const result of successfulResults) {
|
||||
for (const col of result.columns) {
|
||||
const prefixedColumnName = result.alias ? `${result.alias}${col.columnName}` : col.columnName;
|
||||
mergedColumns.push({
|
||||
columnName: prefixedColumnName,
|
||||
columnLabel: `${col.columnLabel} (${result.connectionName})`,
|
||||
dataType: col.dataType,
|
||||
sourceApi: result.connectionName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 데이터 병합 (가로 병합: 각 API의 첫 번째 행끼리 병합)
|
||||
// 참고: 실제 사용 시에는 조인 키가 필요할 수 있음
|
||||
const maxRows = Math.max(...successfulResults.map(r => r.rows.length));
|
||||
const mergedRows: any[] = [];
|
||||
|
||||
for (let i = 0; i < maxRows; i++) {
|
||||
const mergedRow: any = {};
|
||||
|
||||
for (const result of successfulResults) {
|
||||
const row = result.rows[i] || {};
|
||||
|
||||
for (const [key, value] of Object.entries(row)) {
|
||||
const prefixedKey = result.alias ? `${result.alias}${key}` : key;
|
||||
mergedRow[prefixedKey] = value;
|
||||
}
|
||||
}
|
||||
|
||||
mergedRows.push(mergedRow);
|
||||
}
|
||||
|
||||
logger.info(`다중 REST API 데이터 병합 완료: ${mergedRows.length}개 행, ${mergedColumns.length}개 컬럼`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
rows: mergedRows,
|
||||
columns: mergedColumns,
|
||||
total: mergedRows.length,
|
||||
sources: successfulResults.map(r => ({
|
||||
connectionId: r.connectionId,
|
||||
connectionName: r.connectionName,
|
||||
rowCount: r.rows.length,
|
||||
})),
|
||||
},
|
||||
message: `${successfulResults.length}개 API에서 총 ${mergedRows.length}개 데이터를 조회했습니다.`,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error("다중 REST API 데이터 조회 오류:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "다중 REST API 데이터 조회에 실패했습니다.",
|
||||
error: {
|
||||
code: "MULTI_FETCH_ERROR",
|
||||
details: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export class FlowDefinitionService {
|
||||
restApiConnectionId: request.restApiConnectionId,
|
||||
restApiEndpoint: request.restApiEndpoint,
|
||||
restApiJsonPath: request.restApiJsonPath,
|
||||
restApiConnections: request.restApiConnections,
|
||||
companyCode,
|
||||
userId,
|
||||
});
|
||||
@@ -38,9 +39,9 @@ export class FlowDefinitionService {
|
||||
INSERT INTO flow_definition (
|
||||
name, description, table_name, db_source_type, db_connection_id,
|
||||
rest_api_connection_id, rest_api_endpoint, rest_api_json_path,
|
||||
company_code, created_by
|
||||
rest_api_connections, company_code, created_by
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
@@ -52,7 +53,8 @@ export class FlowDefinitionService {
|
||||
request.dbConnectionId || null,
|
||||
request.restApiConnectionId || null,
|
||||
request.restApiEndpoint || null,
|
||||
request.restApiJsonPath || "data",
|
||||
request.restApiJsonPath || "response",
|
||||
request.restApiConnections ? JSON.stringify(request.restApiConnections) : null,
|
||||
companyCode,
|
||||
userId,
|
||||
];
|
||||
@@ -209,6 +211,19 @@ export class FlowDefinitionService {
|
||||
* DB 행을 FlowDefinition 객체로 변환
|
||||
*/
|
||||
private mapToFlowDefinition(row: any): FlowDefinition {
|
||||
// rest_api_connections 파싱 (JSONB → 배열)
|
||||
let restApiConnections = undefined;
|
||||
if (row.rest_api_connections) {
|
||||
try {
|
||||
restApiConnections = typeof row.rest_api_connections === 'string'
|
||||
? JSON.parse(row.rest_api_connections)
|
||||
: row.rest_api_connections;
|
||||
} catch (e) {
|
||||
console.warn("Failed to parse rest_api_connections:", e);
|
||||
restApiConnections = [];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
@@ -216,10 +231,12 @@ export class FlowDefinitionService {
|
||||
tableName: row.table_name,
|
||||
dbSourceType: row.db_source_type || "internal",
|
||||
dbConnectionId: row.db_connection_id,
|
||||
// REST API 관련 필드
|
||||
// REST API 관련 필드 (단일)
|
||||
restApiConnectionId: row.rest_api_connection_id,
|
||||
restApiEndpoint: row.rest_api_endpoint,
|
||||
restApiJsonPath: row.rest_api_json_path,
|
||||
// 다중 REST API 관련 필드
|
||||
restApiConnections: restApiConnections,
|
||||
companyCode: row.company_code || "*",
|
||||
isActive: row.is_active,
|
||||
createdBy: row.created_by,
|
||||
|
||||
@@ -2,18 +2,38 @@
|
||||
* 플로우 관리 시스템 타입 정의
|
||||
*/
|
||||
|
||||
// 다중 REST API 연결 설정
|
||||
export interface RestApiConnectionConfig {
|
||||
connectionId: number;
|
||||
connectionName: string;
|
||||
endpoint: string;
|
||||
jsonPath: string;
|
||||
alias: string; // 컬럼 접두어 (예: "api1_")
|
||||
}
|
||||
|
||||
// 다중 외부 DB 연결 설정
|
||||
export interface ExternalDbConnectionConfig {
|
||||
connectionId: number;
|
||||
connectionName: string;
|
||||
dbType: string;
|
||||
tableName: string;
|
||||
alias: string; // 컬럼 접두어 (예: "db1_")
|
||||
}
|
||||
|
||||
// 플로우 정의
|
||||
export interface FlowDefinition {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
tableName: string;
|
||||
dbSourceType?: "internal" | "external" | "restapi"; // 데이터 소스 타입
|
||||
dbSourceType?: "internal" | "external" | "restapi" | "multi_restapi" | "multi_external_db"; // 데이터 소스 타입
|
||||
dbConnectionId?: number; // 외부 DB 연결 ID (external인 경우)
|
||||
// REST API 관련 필드
|
||||
// REST API 관련 필드 (단일)
|
||||
restApiConnectionId?: number; // REST API 연결 ID (restapi인 경우)
|
||||
restApiEndpoint?: string; // REST API 엔드포인트
|
||||
restApiJsonPath?: string; // JSON 응답에서 데이터 경로 (기본: data)
|
||||
// 다중 REST API 관련 필드
|
||||
restApiConnections?: RestApiConnectionConfig[]; // 다중 REST API 설정 배열
|
||||
companyCode: string; // 회사 코드 (* = 공통)
|
||||
isActive: boolean;
|
||||
createdBy?: string;
|
||||
@@ -26,12 +46,14 @@ export interface CreateFlowDefinitionRequest {
|
||||
name: string;
|
||||
description?: string;
|
||||
tableName: string;
|
||||
dbSourceType?: "internal" | "external" | "restapi"; // 데이터 소스 타입
|
||||
dbSourceType?: "internal" | "external" | "restapi" | "multi_restapi" | "multi_external_db"; // 데이터 소스 타입
|
||||
dbConnectionId?: number; // 외부 DB 연결 ID
|
||||
// REST API 관련 필드
|
||||
// REST API 관련 필드 (단일)
|
||||
restApiConnectionId?: number; // REST API 연결 ID
|
||||
restApiEndpoint?: string; // REST API 엔드포인트
|
||||
restApiJsonPath?: string; // JSON 응답에서 데이터 경로
|
||||
// 다중 REST API 관련 필드
|
||||
restApiConnections?: RestApiConnectionConfig[]; // 다중 REST API 설정 배열
|
||||
companyCode?: string; // 회사 코드 (미제공 시 사용자의 company_code 사용)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user