// 배치관리 전용 외부 DB 서비스 // 기존 ExternalDbConnectionService와 분리하여 배치관리 시스템에 특화된 기능 제공 // 작성일: 2024-12-24 import { query, queryOne } from "../database/db"; import { PasswordEncryption } from "../utils/passwordEncryption"; import { DatabaseConnectorFactory } from "../database/DatabaseConnectorFactory"; import { RestApiConnector } from "../database/RestApiConnector"; 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 query<{ id: number; connection_name: string; db_type: string; description: string; }>( `SELECT id, connection_name, db_type, description FROM external_db_connections WHERE is_active = 'Y' ORDER BY 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> { try { let tables: TableInfo[] = []; if (connectionType === "internal") { // 내부 DB 테이블 조회 const result = await query<{ 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> { try { console.log(`[BatchExternalDbService] getTableColumns 호출:`, { connectionType, connectionId, tableName, }); let columns: ColumnInfo[] = []; if (connectionType === "internal") { // 내부 DB 컬럼 조회 console.log( `[BatchExternalDbService] 내부 DB 컬럼 조회 시작: ${tableName}` ); const result = await query<{ 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 = $1 ORDER BY ordinal_position`, [tableName] ); 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> { try { // 연결 정보 조회 const connection = await queryOne( `SELECT * FROM external_db_connections WHERE id = $1`, [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> { try { console.log( `[BatchExternalDbService] getExternalTableColumns 호출: connectionId=${connectionId}, tableName=${tableName}` ); // 연결 정보 조회 const connection = await queryOne( `SELECT * FROM external_db_connections WHERE id = $1`, [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> { try { console.log( `[BatchExternalDbService] 외부 DB 데이터 조회: connectionId=${connectionId}, tableName=${tableName}` ); // 외부 DB 연결 정보 조회 const connection = await queryOne( `SELECT * FROM external_db_connections WHERE id = $1`, [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> { try { console.log( `[BatchExternalDbService] 외부 DB 특정 컬럼 조회: connectionId=${connectionId}, tableName=${tableName}, columns=[${columns.join(", ")}]` ); // 외부 DB 연결 정보 조회 const connection = await queryOne( `SELECT * FROM external_db_connections WHERE id = $1`, [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> { 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 queryOne( `SELECT * FROM external_db_connections WHERE id = $1`, [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})`; } console.log(`[BatchExternalDbService] 실행할 쿼리: ${query}`); console.log(`[BatchExternalDbService] 삽입할 데이터:`, record); 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 : "알 수 없는 오류", }; } } /** * REST API에서 데이터 조회 */ static async getDataFromRestApi( apiUrl: string, apiKey: string, endpoint: string, method: "GET" | "POST" | "PUT" | "DELETE" = "GET", columns?: string[], limit: number = 100, // 파라미터 정보 추가 paramType?: "url" | "query", paramName?: string, paramValue?: string, paramSource?: "static" | "dynamic" ): Promise> { try { console.log( `[BatchExternalDbService] REST API 데이터 조회: ${apiUrl}${endpoint}` ); // REST API 커넥터 생성 const connector = new RestApiConnector({ baseUrl: apiUrl, apiKey: apiKey, timeout: 30000, }); // 연결 테스트 await connector.connect(); // 파라미터가 있는 경우 엔드포인트 수정 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}` ); } // 데이터 조회 const result = await connector.executeQuery(finalEndpoint, method); 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); } logger.info( `[BatchExternalDbService] REST API 데이터 조회 완료: ${data.length}개 레코드` ); logger.info(`[BatchExternalDbService] 조회된 데이터`, { data }); 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> { 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> { 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 : "알 수 없는 오류", }; } } }