2025-09-25 11:04:16 +09:00
|
|
|
// 배치관리 전용 외부 DB 서비스
|
|
|
|
|
// 기존 ExternalDbConnectionService와 분리하여 배치관리 시스템에 특화된 기능 제공
|
|
|
|
|
// 작성일: 2024-12-24
|
|
|
|
|
|
|
|
|
|
import prisma from "../config/database";
|
|
|
|
|
import { PasswordEncryption } from "../utils/passwordEncryption";
|
|
|
|
|
import { DatabaseConnectorFactory } from "../database/DatabaseConnectorFactory";
|
2025-09-26 17:29:20 +09:00
|
|
|
import { RestApiConnector } from "../database/RestApiConnector";
|
2025-09-25 11:04:16 +09:00
|
|
|
import { ApiResponse, ColumnInfo, TableInfo } from "../types/batchTypes";
|
|
|
|
|
|
|
|
|
|
export class BatchExternalDbService {
|
|
|
|
|
/**
|
|
|
|
|
* 배치관리용 외부 DB 연결 목록 조회
|
|
|
|
|
*/
|
|
|
|
|
static async getAvailableConnections(): Promise<ApiResponse<Array<{
|
|
|
|
|
type: 'internal' | 'external';
|
|
|
|
|
id?: number;
|
|
|
|
|
name: string;
|
|
|
|
|
db_type?: string;
|
|
|
|
|
}>>> {
|
|
|
|
|
try {
|
|
|
|
|
const connections: Array<{
|
|
|
|
|
type: 'internal' | 'external';
|
|
|
|
|
id?: number;
|
|
|
|
|
name: string;
|
|
|
|
|
db_type?: string;
|
|
|
|
|
}> = [];
|
|
|
|
|
|
|
|
|
|
// 내부 DB 추가
|
|
|
|
|
connections.push({
|
|
|
|
|
type: 'internal',
|
|
|
|
|
name: '내부 데이터베이스 (PostgreSQL)',
|
|
|
|
|
db_type: 'postgresql'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 활성화된 외부 DB 연결 조회
|
|
|
|
|
const externalConnections = await prisma.external_db_connections.findMany({
|
|
|
|
|
where: { is_active: 'Y' },
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
connection_name: true,
|
|
|
|
|
db_type: true,
|
|
|
|
|
description: true
|
|
|
|
|
},
|
|
|
|
|
orderBy: { connection_name: 'asc' }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 외부 DB 연결 추가
|
|
|
|
|
externalConnections.forEach(conn => {
|
|
|
|
|
connections.push({
|
|
|
|
|
type: 'external',
|
|
|
|
|
id: conn.id,
|
|
|
|
|
name: `${conn.connection_name} (${conn.db_type?.toUpperCase()})`,
|
|
|
|
|
db_type: conn.db_type || undefined
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: connections,
|
|
|
|
|
message: `${connections.length}개의 연결을 조회했습니다.`
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("배치관리 연결 목록 조회 실패:", error);
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "연결 목록 조회 중 오류가 발생했습니다.",
|
|
|
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 배치관리용 테이블 목록 조회
|
|
|
|
|
*/
|
|
|
|
|
static async getTablesFromConnection(
|
|
|
|
|
connectionType: 'internal' | 'external',
|
|
|
|
|
connectionId?: number
|
|
|
|
|
): Promise<ApiResponse<TableInfo[]>> {
|
|
|
|
|
try {
|
|
|
|
|
let tables: TableInfo[] = [];
|
|
|
|
|
|
|
|
|
|
if (connectionType === 'internal') {
|
|
|
|
|
// 내부 DB 테이블 조회
|
|
|
|
|
const result = await prisma.$queryRaw<Array<{ table_name: string }>>`
|
|
|
|
|
SELECT table_name
|
|
|
|
|
FROM information_schema.tables
|
|
|
|
|
WHERE table_schema = 'public'
|
|
|
|
|
AND table_type = 'BASE TABLE'
|
|
|
|
|
ORDER BY table_name
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
tables = result.map(row => ({
|
|
|
|
|
table_name: row.table_name,
|
|
|
|
|
columns: []
|
|
|
|
|
}));
|
|
|
|
|
} else if (connectionType === 'external' && connectionId) {
|
|
|
|
|
// 외부 DB 테이블 조회
|
|
|
|
|
const tablesResult = await this.getExternalTables(connectionId);
|
|
|
|
|
if (tablesResult.success && tablesResult.data) {
|
|
|
|
|
tables = tablesResult.data;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: tables,
|
|
|
|
|
message: `${tables.length}개의 테이블을 조회했습니다.`
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("배치관리 테이블 목록 조회 실패:", error);
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "테이블 목록 조회 중 오류가 발생했습니다.",
|
|
|
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 배치관리용 테이블 컬럼 정보 조회
|
|
|
|
|
*/
|
|
|
|
|
static async getTableColumns(
|
|
|
|
|
connectionType: 'internal' | 'external',
|
|
|
|
|
connectionId: number | undefined,
|
|
|
|
|
tableName: string
|
|
|
|
|
): Promise<ApiResponse<ColumnInfo[]>> {
|
|
|
|
|
try {
|
|
|
|
|
console.log(`[BatchExternalDbService] getTableColumns 호출:`, {
|
|
|
|
|
connectionType,
|
|
|
|
|
connectionId,
|
|
|
|
|
tableName
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let columns: ColumnInfo[] = [];
|
|
|
|
|
|
|
|
|
|
if (connectionType === 'internal') {
|
|
|
|
|
// 내부 DB 컬럼 조회
|
|
|
|
|
console.log(`[BatchExternalDbService] 내부 DB 컬럼 조회 시작: ${tableName}`);
|
|
|
|
|
|
|
|
|
|
const result = await prisma.$queryRaw<Array<{
|
|
|
|
|
column_name: string;
|
|
|
|
|
data_type: string;
|
|
|
|
|
is_nullable: string;
|
|
|
|
|
column_default: string | null
|
|
|
|
|
}>>`
|
|
|
|
|
SELECT
|
|
|
|
|
column_name,
|
|
|
|
|
data_type,
|
|
|
|
|
is_nullable,
|
|
|
|
|
column_default
|
|
|
|
|
FROM information_schema.columns
|
|
|
|
|
WHERE table_schema = 'public'
|
|
|
|
|
AND table_name = ${tableName}
|
|
|
|
|
ORDER BY ordinal_position
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 내부 DB 컬럼 조회 결과:`, result);
|
|
|
|
|
|
|
|
|
|
columns = result.map(row => ({
|
|
|
|
|
column_name: row.column_name,
|
|
|
|
|
data_type: row.data_type,
|
|
|
|
|
is_nullable: row.is_nullable,
|
|
|
|
|
column_default: row.column_default,
|
|
|
|
|
}));
|
|
|
|
|
} else if (connectionType === 'external' && connectionId) {
|
|
|
|
|
// 외부 DB 컬럼 조회
|
|
|
|
|
console.log(`[BatchExternalDbService] 외부 DB 컬럼 조회 시작: connectionId=${connectionId}, tableName=${tableName}`);
|
|
|
|
|
|
|
|
|
|
const columnsResult = await this.getExternalTableColumns(connectionId, tableName);
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 외부 DB 컬럼 조회 결과:`, columnsResult);
|
|
|
|
|
|
|
|
|
|
if (columnsResult.success && columnsResult.data) {
|
|
|
|
|
columns = columnsResult.data;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 최종 컬럼 목록:`, columns);
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: columns,
|
|
|
|
|
message: `${columns.length}개의 컬럼을 조회했습니다.`
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[BatchExternalDbService] 컬럼 정보 조회 오류:", error);
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "컬럼 정보 조회 중 오류가 발생했습니다.",
|
|
|
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 외부 DB 테이블 목록 조회 (내부 구현)
|
|
|
|
|
*/
|
|
|
|
|
private static async getExternalTables(connectionId: number): Promise<ApiResponse<TableInfo[]>> {
|
|
|
|
|
try {
|
|
|
|
|
// 연결 정보 조회
|
|
|
|
|
const connection = await prisma.external_db_connections.findUnique({
|
|
|
|
|
where: { id: connectionId }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!connection) {
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "연결 정보를 찾을 수 없습니다."
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 비밀번호 복호화
|
|
|
|
|
const decryptedPassword = PasswordEncryption.decrypt(connection.password);
|
|
|
|
|
if (!decryptedPassword) {
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "비밀번호 복호화에 실패했습니다."
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 연결 설정 준비
|
|
|
|
|
const config = {
|
|
|
|
|
host: connection.host,
|
|
|
|
|
port: connection.port,
|
|
|
|
|
database: connection.database_name,
|
|
|
|
|
user: connection.username,
|
|
|
|
|
password: decryptedPassword,
|
|
|
|
|
connectionTimeoutMillis: connection.connection_timeout != null ? connection.connection_timeout * 1000 : undefined,
|
|
|
|
|
queryTimeoutMillis: connection.query_timeout != null ? connection.query_timeout * 1000 : undefined,
|
|
|
|
|
ssl: connection.ssl_enabled === "Y" ? { rejectUnauthorized: false } : false
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// DatabaseConnectorFactory를 통한 테이블 목록 조회
|
|
|
|
|
const connector = await DatabaseConnectorFactory.createConnector(connection.db_type, config, connectionId);
|
|
|
|
|
const tables = await connector.getTables();
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
message: "테이블 목록을 조회했습니다.",
|
|
|
|
|
data: tables
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("외부 DB 테이블 목록 조회 오류:", error);
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "테이블 목록 조회 중 오류가 발생했습니다.",
|
|
|
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 외부 DB 테이블 컬럼 정보 조회 (내부 구현)
|
|
|
|
|
*/
|
|
|
|
|
private static async getExternalTableColumns(connectionId: number, tableName: string): Promise<ApiResponse<ColumnInfo[]>> {
|
|
|
|
|
try {
|
|
|
|
|
console.log(`[BatchExternalDbService] getExternalTableColumns 호출: connectionId=${connectionId}, tableName=${tableName}`);
|
|
|
|
|
|
|
|
|
|
// 연결 정보 조회
|
|
|
|
|
const connection = await prisma.external_db_connections.findUnique({
|
|
|
|
|
where: { id: connectionId }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!connection) {
|
|
|
|
|
console.log(`[BatchExternalDbService] 연결 정보를 찾을 수 없음: connectionId=${connectionId}`);
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "연결 정보를 찾을 수 없습니다."
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 연결 정보 조회 성공:`, {
|
|
|
|
|
id: connection.id,
|
|
|
|
|
connection_name: connection.connection_name,
|
|
|
|
|
db_type: connection.db_type,
|
|
|
|
|
host: connection.host,
|
|
|
|
|
port: connection.port,
|
|
|
|
|
database_name: connection.database_name
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 비밀번호 복호화
|
|
|
|
|
const decryptedPassword = PasswordEncryption.decrypt(connection.password);
|
|
|
|
|
|
|
|
|
|
// 연결 설정 준비
|
|
|
|
|
const config = {
|
|
|
|
|
host: connection.host,
|
|
|
|
|
port: connection.port,
|
|
|
|
|
database: connection.database_name,
|
|
|
|
|
user: connection.username,
|
|
|
|
|
password: decryptedPassword,
|
|
|
|
|
connectionTimeoutMillis: connection.connection_timeout != null ? connection.connection_timeout * 1000 : undefined,
|
|
|
|
|
queryTimeoutMillis: connection.query_timeout != null ? connection.query_timeout * 1000 : undefined,
|
|
|
|
|
ssl: connection.ssl_enabled === "Y" ? { rejectUnauthorized: false } : false
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 커넥터 생성 시작: db_type=${connection.db_type}`);
|
|
|
|
|
|
|
|
|
|
// 데이터베이스 타입에 따른 커넥터 생성
|
|
|
|
|
const connector = await DatabaseConnectorFactory.createConnector(connection.db_type, config, connectionId);
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 커넥터 생성 완료, 컬럼 조회 시작: tableName=${tableName}`);
|
|
|
|
|
|
|
|
|
|
// 컬럼 정보 조회
|
|
|
|
|
console.log(`[BatchExternalDbService] connector.getColumns 호출 전`);
|
|
|
|
|
const columns = await connector.getColumns(tableName);
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 원본 컬럼 조회 결과:`, columns);
|
|
|
|
|
console.log(`[BatchExternalDbService] 원본 컬럼 개수:`, columns ? columns.length : 'null/undefined');
|
|
|
|
|
|
|
|
|
|
// 각 데이터베이스 커넥터의 반환 구조가 다르므로 통일된 구조로 변환
|
|
|
|
|
const standardizedColumns: ColumnInfo[] = columns.map((col: any) => {
|
|
|
|
|
console.log(`[BatchExternalDbService] 컬럼 변환 중:`, col);
|
|
|
|
|
|
|
|
|
|
// MySQL/MariaDB 구조: {name, dataType, isNullable, defaultValue} (MySQLConnector만)
|
|
|
|
|
if (col.name && col.dataType !== undefined) {
|
|
|
|
|
const result = {
|
|
|
|
|
column_name: col.name,
|
|
|
|
|
data_type: col.dataType,
|
|
|
|
|
is_nullable: col.isNullable ? 'YES' : 'NO',
|
|
|
|
|
column_default: col.defaultValue || null,
|
|
|
|
|
};
|
|
|
|
|
console.log(`[BatchExternalDbService] MySQL/MariaDB 구조로 변환:`, result);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
// PostgreSQL/Oracle/MSSQL/MariaDB 구조: {column_name, data_type, is_nullable, column_default}
|
|
|
|
|
else {
|
|
|
|
|
const result = {
|
|
|
|
|
column_name: col.column_name || col.COLUMN_NAME,
|
|
|
|
|
data_type: col.data_type || col.DATA_TYPE,
|
|
|
|
|
is_nullable: col.is_nullable || col.IS_NULLABLE || (col.nullable === 'Y' ? 'YES' : 'NO'),
|
|
|
|
|
column_default: col.column_default || col.COLUMN_DEFAULT || null,
|
|
|
|
|
};
|
|
|
|
|
console.log(`[BatchExternalDbService] 표준 구조로 변환:`, result);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 표준화된 컬럼 목록:`, standardizedColumns);
|
|
|
|
|
|
|
|
|
|
// 빈 배열인 경우 경고 로그
|
|
|
|
|
if (!standardizedColumns || standardizedColumns.length === 0) {
|
|
|
|
|
console.warn(`[BatchExternalDbService] 컬럼이 비어있음: connectionId=${connectionId}, tableName=${tableName}`);
|
|
|
|
|
console.warn(`[BatchExternalDbService] 연결 정보:`, {
|
|
|
|
|
db_type: connection.db_type,
|
|
|
|
|
host: connection.host,
|
|
|
|
|
port: connection.port,
|
|
|
|
|
database_name: connection.database_name,
|
|
|
|
|
username: connection.username
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 테이블 존재 여부 확인
|
|
|
|
|
console.warn(`[BatchExternalDbService] 테이블 존재 여부 확인을 위해 테이블 목록 조회 시도`);
|
|
|
|
|
try {
|
|
|
|
|
const tables = await connector.getTables();
|
|
|
|
|
console.warn(`[BatchExternalDbService] 사용 가능한 테이블 목록:`, tables.map(t => t.table_name));
|
|
|
|
|
|
|
|
|
|
// 테이블명이 정확한지 확인
|
|
|
|
|
const tableExists = tables.some(t => t.table_name.toLowerCase() === tableName.toLowerCase());
|
|
|
|
|
console.warn(`[BatchExternalDbService] 테이블 존재 여부: ${tableExists}`);
|
|
|
|
|
|
|
|
|
|
// 정확한 테이블명 찾기
|
|
|
|
|
const exactTable = tables.find(t => t.table_name.toLowerCase() === tableName.toLowerCase());
|
|
|
|
|
if (exactTable) {
|
|
|
|
|
console.warn(`[BatchExternalDbService] 정확한 테이블명: ${exactTable.table_name}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 모든 테이블명 출력
|
|
|
|
|
console.warn(`[BatchExternalDbService] 모든 테이블명:`, tables.map(t => `"${t.table_name}"`));
|
|
|
|
|
|
|
|
|
|
// 테이블명 비교
|
|
|
|
|
console.warn(`[BatchExternalDbService] 요청된 테이블명: "${tableName}"`);
|
|
|
|
|
console.warn(`[BatchExternalDbService] 테이블명 비교 결과:`, tables.map(t => ({
|
|
|
|
|
table_name: t.table_name,
|
|
|
|
|
matches: t.table_name.toLowerCase() === tableName.toLowerCase(),
|
|
|
|
|
exact_match: t.table_name === tableName
|
|
|
|
|
})));
|
|
|
|
|
|
|
|
|
|
// 정확한 테이블명으로 다시 시도
|
|
|
|
|
if (exactTable && exactTable.table_name !== tableName) {
|
|
|
|
|
console.warn(`[BatchExternalDbService] 정확한 테이블명으로 다시 시도: ${exactTable.table_name}`);
|
|
|
|
|
try {
|
|
|
|
|
const correctColumns = await connector.getColumns(exactTable.table_name);
|
|
|
|
|
console.warn(`[BatchExternalDbService] 정확한 테이블명으로 조회한 컬럼:`, correctColumns);
|
|
|
|
|
} catch (correctError) {
|
|
|
|
|
console.error(`[BatchExternalDbService] 정확한 테이블명으로 조회 실패:`, correctError);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (tableError) {
|
|
|
|
|
console.error(`[BatchExternalDbService] 테이블 목록 조회 실패:`, tableError);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: standardizedColumns,
|
|
|
|
|
message: "컬럼 정보를 조회했습니다."
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[BatchExternalDbService] 외부 DB 컬럼 정보 조회 오류:", error);
|
|
|
|
|
console.error("[BatchExternalDbService] 오류 스택:", error instanceof Error ? error.stack : 'No stack trace');
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "컬럼 정보 조회 중 오류가 발생했습니다.",
|
|
|
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 외부 DB 테이블에서 데이터 조회
|
|
|
|
|
*/
|
|
|
|
|
static async getDataFromTable(
|
|
|
|
|
connectionId: number,
|
|
|
|
|
tableName: string,
|
|
|
|
|
limit: number = 100
|
|
|
|
|
): Promise<ApiResponse<any[]>> {
|
|
|
|
|
try {
|
|
|
|
|
console.log(`[BatchExternalDbService] 외부 DB 데이터 조회: connectionId=${connectionId}, tableName=${tableName}`);
|
|
|
|
|
|
|
|
|
|
// 외부 DB 연결 정보 조회
|
|
|
|
|
const connection = await prisma.external_db_connections.findUnique({
|
|
|
|
|
where: { id: connectionId }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!connection) {
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "외부 DB 연결을 찾을 수 없습니다."
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 패스워드 복호화
|
|
|
|
|
const decryptedPassword = PasswordEncryption.decrypt(connection.password);
|
|
|
|
|
|
|
|
|
|
// DB 연결 설정
|
|
|
|
|
const config = {
|
|
|
|
|
host: connection.host,
|
|
|
|
|
port: connection.port,
|
|
|
|
|
user: connection.username,
|
|
|
|
|
password: decryptedPassword,
|
|
|
|
|
database: connection.database_name,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// DB 커넥터 생성
|
|
|
|
|
const connector = await DatabaseConnectorFactory.createConnector(
|
|
|
|
|
connection.db_type || 'postgresql',
|
|
|
|
|
config,
|
|
|
|
|
connectionId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 데이터 조회 (DB 타입에 따라 쿼리 구문 변경)
|
|
|
|
|
let query: string;
|
|
|
|
|
const dbType = connection.db_type?.toLowerCase() || 'postgresql';
|
|
|
|
|
|
|
|
|
|
if (dbType === 'oracle') {
|
|
|
|
|
query = `SELECT * FROM ${tableName} WHERE ROWNUM <= ${limit}`;
|
|
|
|
|
} else {
|
|
|
|
|
query = `SELECT * FROM ${tableName} LIMIT ${limit}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 실행할 쿼리: ${query}`);
|
|
|
|
|
const result = await connector.executeQuery(query);
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 외부 DB 데이터 조회 완료: ${result.rows.length}개 레코드`);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: result.rows
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`외부 DB 데이터 조회 오류 (connectionId: ${connectionId}, table: ${tableName}):`, error);
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "외부 DB 데이터 조회 중 오류가 발생했습니다.",
|
|
|
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 외부 DB 테이블에서 특정 컬럼들만 조회
|
|
|
|
|
*/
|
|
|
|
|
static async getDataFromTableWithColumns(
|
|
|
|
|
connectionId: number,
|
|
|
|
|
tableName: string,
|
|
|
|
|
columns: string[],
|
|
|
|
|
limit: number = 100
|
|
|
|
|
): Promise<ApiResponse<any[]>> {
|
|
|
|
|
try {
|
|
|
|
|
console.log(`[BatchExternalDbService] 외부 DB 특정 컬럼 조회: connectionId=${connectionId}, tableName=${tableName}, columns=[${columns.join(', ')}]`);
|
|
|
|
|
|
|
|
|
|
// 외부 DB 연결 정보 조회
|
|
|
|
|
const connection = await prisma.external_db_connections.findUnique({
|
|
|
|
|
where: { id: connectionId }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!connection) {
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "외부 DB 연결을 찾을 수 없습니다."
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 패스워드 복호화
|
|
|
|
|
const decryptedPassword = PasswordEncryption.decrypt(connection.password);
|
|
|
|
|
|
|
|
|
|
// DB 연결 설정
|
|
|
|
|
const config = {
|
|
|
|
|
host: connection.host,
|
|
|
|
|
port: connection.port,
|
|
|
|
|
user: connection.username,
|
|
|
|
|
password: decryptedPassword,
|
|
|
|
|
database: connection.database_name,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// DB 커넥터 생성
|
|
|
|
|
const connector = await DatabaseConnectorFactory.createConnector(
|
|
|
|
|
connection.db_type || 'postgresql',
|
|
|
|
|
config,
|
|
|
|
|
connectionId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 데이터 조회 (DB 타입에 따라 쿼리 구문 변경)
|
|
|
|
|
let query: string;
|
|
|
|
|
const dbType = connection.db_type?.toLowerCase() || 'postgresql';
|
|
|
|
|
const columnList = columns.join(', ');
|
|
|
|
|
|
|
|
|
|
if (dbType === 'oracle') {
|
|
|
|
|
query = `SELECT ${columnList} FROM ${tableName} WHERE ROWNUM <= ${limit}`;
|
|
|
|
|
} else {
|
|
|
|
|
query = `SELECT ${columnList} FROM ${tableName} LIMIT ${limit}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 실행할 쿼리: ${query}`);
|
|
|
|
|
const result = await connector.executeQuery(query);
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 외부 DB 특정 컬럼 조회 완료: ${result.rows.length}개 레코드`);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: result.rows
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`외부 DB 특정 컬럼 조회 오류 (connectionId: ${connectionId}, table: ${tableName}):`, error);
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "외부 DB 특정 컬럼 조회 중 오류가 발생했습니다.",
|
|
|
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 외부 DB 테이블에 데이터 삽입
|
|
|
|
|
*/
|
|
|
|
|
static async insertDataToTable(
|
|
|
|
|
connectionId: number,
|
|
|
|
|
tableName: string,
|
|
|
|
|
data: any[]
|
|
|
|
|
): Promise<ApiResponse<{ successCount: number; failedCount: number }>> {
|
|
|
|
|
try {
|
|
|
|
|
console.log(`[BatchExternalDbService] 외부 DB 데이터 삽입: connectionId=${connectionId}, tableName=${tableName}, ${data.length}개 레코드`);
|
|
|
|
|
|
|
|
|
|
if (!data || data.length === 0) {
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: { successCount: 0, failedCount: 0 }
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 외부 DB 연결 정보 조회
|
|
|
|
|
const connection = await prisma.external_db_connections.findUnique({
|
|
|
|
|
where: { id: connectionId }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!connection) {
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "외부 DB 연결을 찾을 수 없습니다."
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 패스워드 복호화
|
|
|
|
|
const decryptedPassword = PasswordEncryption.decrypt(connection.password);
|
|
|
|
|
|
|
|
|
|
// DB 연결 설정
|
|
|
|
|
const config = {
|
|
|
|
|
host: connection.host,
|
|
|
|
|
port: connection.port,
|
|
|
|
|
user: connection.username,
|
|
|
|
|
password: decryptedPassword,
|
|
|
|
|
database: connection.database_name,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// DB 커넥터 생성
|
|
|
|
|
const connector = await DatabaseConnectorFactory.createConnector(
|
|
|
|
|
connection.db_type || 'postgresql',
|
|
|
|
|
config,
|
|
|
|
|
connectionId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let successCount = 0;
|
|
|
|
|
let failedCount = 0;
|
|
|
|
|
|
|
|
|
|
// 각 레코드를 개별적으로 삽입 (UPSERT 방식으로 중복 처리)
|
|
|
|
|
for (const record of data) {
|
|
|
|
|
try {
|
|
|
|
|
const columns = Object.keys(record);
|
|
|
|
|
const values = Object.values(record);
|
|
|
|
|
|
|
|
|
|
// 값들을 SQL 문자열로 변환 (타입별 처리)
|
|
|
|
|
const formattedValues = values.map(value => {
|
|
|
|
|
if (value === null || value === undefined) {
|
|
|
|
|
return 'NULL';
|
|
|
|
|
} else if (value instanceof Date) {
|
|
|
|
|
// Date 객체를 MySQL/MariaDB 형식으로 변환
|
|
|
|
|
return `'${value.toISOString().slice(0, 19).replace('T', ' ')}'`;
|
|
|
|
|
} else if (typeof value === 'string') {
|
|
|
|
|
// 문자열이 날짜 형식인지 확인
|
|
|
|
|
const dateRegex = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{2}\s+\d{4}\s+\d{2}:\d{2}:\d{2}/;
|
|
|
|
|
if (dateRegex.test(value)) {
|
|
|
|
|
// JavaScript Date 문자열을 MySQL 형식으로 변환
|
|
|
|
|
const date = new Date(value);
|
|
|
|
|
return `'${date.toISOString().slice(0, 19).replace('T', ' ')}'`;
|
|
|
|
|
} else {
|
|
|
|
|
return `'${value.replace(/'/g, "''")}'`; // SQL 인젝션 방지를 위한 간단한 이스케이프
|
|
|
|
|
}
|
|
|
|
|
} else if (typeof value === 'number') {
|
|
|
|
|
return String(value);
|
|
|
|
|
} else if (typeof value === 'boolean') {
|
|
|
|
|
return value ? '1' : '0';
|
|
|
|
|
} else {
|
|
|
|
|
// 기타 객체는 문자열로 변환
|
|
|
|
|
return `'${String(value).replace(/'/g, "''")}'`;
|
|
|
|
|
}
|
|
|
|
|
}).join(', ');
|
|
|
|
|
|
|
|
|
|
// Primary Key 컬럼 추정
|
|
|
|
|
const primaryKeyColumn = columns.includes('id') ? 'id' :
|
|
|
|
|
columns.includes('user_id') ? 'user_id' :
|
|
|
|
|
columns[0];
|
|
|
|
|
|
|
|
|
|
// UPDATE SET 절 생성 (Primary Key 제외)
|
|
|
|
|
const updateColumns = columns.filter(col => col !== primaryKeyColumn);
|
|
|
|
|
|
|
|
|
|
let query: string;
|
|
|
|
|
const dbType = connection.db_type?.toLowerCase() || 'mysql';
|
|
|
|
|
|
|
|
|
|
if (dbType === 'mysql' || dbType === 'mariadb') {
|
|
|
|
|
// MySQL/MariaDB: ON DUPLICATE KEY UPDATE 사용
|
|
|
|
|
if (updateColumns.length > 0) {
|
|
|
|
|
const updateSet = updateColumns.map(col => `${col} = VALUES(${col})`).join(', ');
|
|
|
|
|
query = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${formattedValues})
|
|
|
|
|
ON DUPLICATE KEY UPDATE ${updateSet}`;
|
|
|
|
|
} else {
|
|
|
|
|
// Primary Key만 있는 경우 IGNORE 사용
|
|
|
|
|
query = `INSERT IGNORE INTO ${tableName} (${columns.join(', ')}) VALUES (${formattedValues})`;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 다른 DB는 기본 INSERT 사용
|
|
|
|
|
query = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${formattedValues})`;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 14:25:18 +09:00
|
|
|
console.log(`[BatchExternalDbService] 실행할 쿼리: ${query}`);
|
|
|
|
|
console.log(`[BatchExternalDbService] 삽입할 데이터:`, record);
|
|
|
|
|
|
2025-09-25 11:04:16 +09:00
|
|
|
await connector.executeQuery(query);
|
|
|
|
|
successCount++;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`외부 DB 레코드 UPSERT 실패:`, error);
|
|
|
|
|
failedCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 외부 DB 데이터 삽입 완료: 성공 ${successCount}개, 실패 ${failedCount}개`);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: { successCount, failedCount }
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`외부 DB 데이터 삽입 오류 (connectionId: ${connectionId}, table: ${tableName}):`, error);
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "외부 DB 데이터 삽입 중 오류가 발생했습니다.",
|
|
|
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-26 17:29:20 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* REST API에서 데이터 조회
|
|
|
|
|
*/
|
|
|
|
|
static async getDataFromRestApi(
|
|
|
|
|
apiUrl: string,
|
|
|
|
|
apiKey: string,
|
|
|
|
|
endpoint: string,
|
|
|
|
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
|
|
|
|
|
columns?: string[],
|
2025-09-29 13:48:59 +09:00
|
|
|
limit: number = 100,
|
|
|
|
|
// 파라미터 정보 추가
|
|
|
|
|
paramType?: 'url' | 'query',
|
|
|
|
|
paramName?: string,
|
|
|
|
|
paramValue?: string,
|
|
|
|
|
paramSource?: 'static' | 'dynamic'
|
2025-09-26 17:29:20 +09:00
|
|
|
): Promise<ApiResponse<any[]>> {
|
|
|
|
|
try {
|
|
|
|
|
console.log(`[BatchExternalDbService] REST API 데이터 조회: ${apiUrl}${endpoint}`);
|
|
|
|
|
|
|
|
|
|
// REST API 커넥터 생성
|
|
|
|
|
const connector = new RestApiConnector({
|
|
|
|
|
baseUrl: apiUrl,
|
|
|
|
|
apiKey: apiKey,
|
|
|
|
|
timeout: 30000
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 연결 테스트
|
|
|
|
|
await connector.connect();
|
|
|
|
|
|
2025-09-29 13:48:59 +09:00
|
|
|
// 파라미터가 있는 경우 엔드포인트 수정
|
|
|
|
|
const { logger } = await import('../utils/logger');
|
|
|
|
|
logger.info(`[BatchExternalDbService] 파라미터 정보`, {
|
|
|
|
|
paramType, paramName, paramValue, paramSource
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let finalEndpoint = endpoint;
|
|
|
|
|
if (paramType && paramName && paramValue) {
|
|
|
|
|
if (paramType === 'url') {
|
|
|
|
|
// URL 파라미터: /api/users/{userId} → /api/users/123
|
|
|
|
|
if (endpoint.includes(`{${paramName}}`)) {
|
|
|
|
|
finalEndpoint = endpoint.replace(`{${paramName}}`, paramValue);
|
|
|
|
|
} else {
|
|
|
|
|
// 엔드포인트에 {paramName}이 없으면 뒤에 추가
|
|
|
|
|
finalEndpoint = `${endpoint}/${paramValue}`;
|
|
|
|
|
}
|
|
|
|
|
} else if (paramType === 'query') {
|
|
|
|
|
// 쿼리 파라미터: /api/users?userId=123
|
|
|
|
|
const separator = endpoint.includes('?') ? '&' : '?';
|
|
|
|
|
finalEndpoint = `${endpoint}${separator}${paramName}=${paramValue}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info(`[BatchExternalDbService] 파라미터 적용된 엔드포인트: ${finalEndpoint}`);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-26 17:29:20 +09:00
|
|
|
// 데이터 조회
|
2025-09-29 13:48:59 +09:00
|
|
|
const result = await connector.executeQuery(finalEndpoint, method);
|
2025-09-26 17:29:20 +09:00
|
|
|
let data = result.rows;
|
|
|
|
|
|
|
|
|
|
// 컬럼 필터링 (지정된 컬럼만 추출)
|
|
|
|
|
if (columns && columns.length > 0) {
|
|
|
|
|
data = data.map(row => {
|
|
|
|
|
const filteredRow: any = {};
|
|
|
|
|
columns.forEach(col => {
|
|
|
|
|
if (row.hasOwnProperty(col)) {
|
|
|
|
|
filteredRow[col] = row[col];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return filteredRow;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 제한 개수 적용
|
|
|
|
|
if (limit > 0) {
|
|
|
|
|
data = data.slice(0, limit);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-29 13:48:59 +09:00
|
|
|
logger.info(`[BatchExternalDbService] REST API 데이터 조회 완료: ${data.length}개 레코드`);
|
|
|
|
|
logger.info(`[BatchExternalDbService] 조회된 데이터`, { data });
|
2025-09-26 17:29:20 +09:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: data
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`[BatchExternalDbService] REST API 데이터 조회 오류 (${apiUrl}${endpoint}):`, error);
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "REST API 데이터 조회 중 오류가 발생했습니다.",
|
|
|
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 템플릿 기반 REST API로 데이터 전송 (DB → REST API 배치용)
|
|
|
|
|
*/
|
|
|
|
|
static async sendDataToRestApiWithTemplate(
|
|
|
|
|
apiUrl: string,
|
|
|
|
|
apiKey: string,
|
|
|
|
|
endpoint: string,
|
|
|
|
|
method: 'POST' | 'PUT' | 'DELETE' = 'POST',
|
|
|
|
|
templateBody: string,
|
|
|
|
|
data: any[],
|
|
|
|
|
urlPathColumn?: string // URL 경로에 사용할 컬럼명 (PUT/DELETE용)
|
|
|
|
|
): Promise<ApiResponse<{ successCount: number; failedCount: number }>> {
|
|
|
|
|
try {
|
|
|
|
|
console.log(`[BatchExternalDbService] 템플릿 기반 REST API 데이터 전송: ${apiUrl}${endpoint}, ${data.length}개 레코드`);
|
|
|
|
|
console.log(`[BatchExternalDbService] Request Body 템플릿:`, templateBody);
|
|
|
|
|
|
|
|
|
|
// REST API 커넥터 생성
|
|
|
|
|
const connector = new RestApiConnector({
|
|
|
|
|
baseUrl: apiUrl,
|
|
|
|
|
apiKey: apiKey,
|
|
|
|
|
timeout: 30000
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 연결 테스트
|
|
|
|
|
await connector.connect();
|
|
|
|
|
|
|
|
|
|
let successCount = 0;
|
|
|
|
|
let failedCount = 0;
|
|
|
|
|
|
|
|
|
|
// 각 레코드를 개별적으로 전송
|
|
|
|
|
for (const record of data) {
|
|
|
|
|
try {
|
|
|
|
|
// 템플릿 처리: {{컬럼명}} → 실제 값으로 치환
|
|
|
|
|
let processedBody = templateBody;
|
|
|
|
|
for (const [key, value] of Object.entries(record)) {
|
|
|
|
|
const placeholder = `{{${key}}}`;
|
|
|
|
|
let stringValue = '';
|
|
|
|
|
|
|
|
|
|
if (value !== null && value !== undefined) {
|
|
|
|
|
// Date 객체인 경우 다양한 포맷으로 변환
|
|
|
|
|
if (value instanceof Date) {
|
|
|
|
|
// ISO 형식: 2025-09-25T07:22:52.000Z
|
|
|
|
|
stringValue = value.toISOString();
|
|
|
|
|
|
|
|
|
|
// 다른 포맷이 필요한 경우 여기서 처리
|
|
|
|
|
// 예: YYYY-MM-DD 형식
|
|
|
|
|
// stringValue = value.toISOString().split('T')[0];
|
|
|
|
|
|
|
|
|
|
// 예: YYYY-MM-DD HH:mm:ss 형식
|
|
|
|
|
// stringValue = value.toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, '');
|
|
|
|
|
} else {
|
|
|
|
|
stringValue = String(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
processedBody = processedBody.replace(new RegExp(placeholder.replace(/[{}]/g, '\\$&'), 'g'), stringValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 원본 레코드:`, record);
|
|
|
|
|
console.log(`[BatchExternalDbService] 처리된 Request Body:`, processedBody);
|
|
|
|
|
|
|
|
|
|
// JSON 파싱하여 객체로 변환
|
|
|
|
|
let requestData;
|
|
|
|
|
try {
|
|
|
|
|
requestData = JSON.parse(processedBody);
|
|
|
|
|
} catch (parseError) {
|
|
|
|
|
console.error(`[BatchExternalDbService] JSON 파싱 오류:`, parseError);
|
|
|
|
|
throw new Error(`Request Body JSON 파싱 실패: ${parseError}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// URL 경로 파라미터 처리 (PUT/DELETE용)
|
|
|
|
|
let finalEndpoint = endpoint;
|
|
|
|
|
if ((method === 'PUT' || method === 'DELETE') && urlPathColumn && record[urlPathColumn]) {
|
|
|
|
|
// /api/users → /api/users/user123
|
|
|
|
|
finalEndpoint = `${endpoint}/${record[urlPathColumn]}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 실행할 API 호출: ${method} ${finalEndpoint}`);
|
|
|
|
|
console.log(`[BatchExternalDbService] 전송할 데이터:`, requestData);
|
|
|
|
|
|
|
|
|
|
await connector.executeQuery(finalEndpoint, method, requestData);
|
|
|
|
|
successCount++;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`REST API 레코드 전송 실패:`, error);
|
|
|
|
|
failedCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] 템플릿 기반 REST API 데이터 전송 완료: 성공 ${successCount}개, 실패 ${failedCount}개`);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: { successCount, failedCount }
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`[BatchExternalDbService] 템플릿 기반 REST API 데이터 전송 오류:`, error);
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: `REST API 데이터 전송 실패: ${error}`,
|
|
|
|
|
data: { successCount: 0, failedCount: 0 }
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* REST API로 데이터 전송 (기존 메서드)
|
|
|
|
|
*/
|
|
|
|
|
static async sendDataToRestApi(
|
|
|
|
|
apiUrl: string,
|
|
|
|
|
apiKey: string,
|
|
|
|
|
endpoint: string,
|
|
|
|
|
method: 'POST' | 'PUT' = 'POST',
|
|
|
|
|
data: any[]
|
|
|
|
|
): Promise<ApiResponse<{ successCount: number; failedCount: number }>> {
|
|
|
|
|
try {
|
|
|
|
|
console.log(`[BatchExternalDbService] REST API 데이터 전송: ${apiUrl}${endpoint}, ${data.length}개 레코드`);
|
|
|
|
|
|
|
|
|
|
// REST API 커넥터 생성
|
|
|
|
|
const connector = new RestApiConnector({
|
|
|
|
|
baseUrl: apiUrl,
|
|
|
|
|
apiKey: apiKey,
|
|
|
|
|
timeout: 30000
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 연결 테스트
|
|
|
|
|
await connector.connect();
|
|
|
|
|
|
|
|
|
|
let successCount = 0;
|
|
|
|
|
let failedCount = 0;
|
|
|
|
|
|
|
|
|
|
// 각 레코드를 개별적으로 전송
|
|
|
|
|
for (const record of data) {
|
|
|
|
|
try {
|
|
|
|
|
console.log(`[BatchExternalDbService] 실행할 API 호출: ${method} ${endpoint}`);
|
|
|
|
|
console.log(`[BatchExternalDbService] 전송할 데이터:`, record);
|
|
|
|
|
|
|
|
|
|
await connector.executeQuery(endpoint, method, record);
|
|
|
|
|
successCount++;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`REST API 레코드 전송 실패:`, error);
|
|
|
|
|
failedCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[BatchExternalDbService] REST API 데이터 전송 완료: 성공 ${successCount}개, 실패 ${failedCount}개`);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: { successCount, failedCount }
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`[BatchExternalDbService] REST API 데이터 전송 오류 (${apiUrl}${endpoint}):`, error);
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "REST API 데이터 전송 중 오류가 발생했습니다.",
|
|
|
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-25 11:04:16 +09:00
|
|
|
}
|