ERP-node/backend-node/src/services/batchExternalDbService.ts

1136 lines
36 KiB
TypeScript

// 배치관리 전용 외부 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<ApiResponse<TableInfo[]>> {
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<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 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<ApiResponse<TableInfo[]>> {
try {
// 연결 정보 조회
const connection = await queryOne<any>(
`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<ApiResponse<ColumnInfo[]>> {
try {
console.log(
`[BatchExternalDbService] getExternalTableColumns 호출: connectionId=${connectionId}, tableName=${tableName}`
);
// 연결 정보 조회
const connection = await queryOne<any>(
`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<ApiResponse<any[]>> {
try {
console.log(
`[BatchExternalDbService] 외부 DB 데이터 조회: connectionId=${connectionId}, tableName=${tableName}`
);
// 외부 DB 연결 정보 조회
const connection = await queryOne<any>(
`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<ApiResponse<any[]>> {
try {
console.log(
`[BatchExternalDbService] 외부 DB 특정 컬럼 조회: connectionId=${connectionId}, tableName=${tableName}, columns=[${columns.join(", ")}]`
);
// 외부 DB 연결 정보 조회
const connection = await queryOne<any>(
`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<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 queryOne<any>(
`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<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();
// 파라미터가 있는 경우 엔드포인트 수정
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<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 : "알 수 없는 오류",
};
}
}
}