744 lines
22 KiB
TypeScript
744 lines
22 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 getTables(
|
|
connectionId: number
|
|
): Promise<ApiResponse<TableInfo[]>> {
|
|
try {
|
|
// 연결 정보 조회
|
|
const connection = await this.getConnectionById(connectionId);
|
|
if (!connection) {
|
|
throw new Error("외부 DB 연결 정보를 찾을 수 없습니다.");
|
|
}
|
|
|
|
// 커넥터 생성
|
|
const connector = await DatabaseConnectorFactory.createConnector(
|
|
connection.db_type,
|
|
{
|
|
host: connection.host,
|
|
port: connection.port,
|
|
database: connection.database_name,
|
|
user: connection.username,
|
|
password: connection.password,
|
|
},
|
|
connectionId
|
|
);
|
|
|
|
// 연결
|
|
await connector.connect();
|
|
|
|
// 테이블 목록 조회
|
|
const tables = await connector.getTables();
|
|
|
|
// 연결 종료
|
|
await connector.disconnect();
|
|
|
|
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 getColumns(
|
|
connectionId: number,
|
|
tableName: string
|
|
): Promise<ApiResponse<ColumnInfo[]>> {
|
|
try {
|
|
// 연결 정보 조회
|
|
const connection = await this.getConnectionById(connectionId);
|
|
if (!connection) {
|
|
throw new Error("외부 DB 연결 정보를 찾을 수 없습니다.");
|
|
}
|
|
|
|
// 커넥터 생성
|
|
const connector = await DatabaseConnectorFactory.createConnector(
|
|
connection.db_type,
|
|
{
|
|
host: connection.host,
|
|
port: connection.port,
|
|
database: connection.database_name,
|
|
user: connection.username,
|
|
password: connection.password,
|
|
},
|
|
connectionId
|
|
);
|
|
|
|
// 연결
|
|
await connector.connect();
|
|
|
|
// 컬럼 목록 조회
|
|
const columns = await connector.getColumns(tableName);
|
|
|
|
// 연결 종료
|
|
await connector.disconnect();
|
|
|
|
// BatchColumnInfo 형식으로 변환
|
|
const batchColumns: ColumnInfo[] = columns.map((col) => ({
|
|
column_name: col.column_name,
|
|
data_type: col.data_type,
|
|
is_nullable: col.is_nullable,
|
|
column_default: col.column_default,
|
|
}));
|
|
|
|
return {
|
|
success: true,
|
|
data: batchColumns,
|
|
message: `${batchColumns.length}개의 컬럼을 조회했습니다.`,
|
|
};
|
|
} catch (error) {
|
|
console.error("컬럼 목록 조회 실패:", error);
|
|
return {
|
|
success: false,
|
|
message: "컬럼 목록 조회 중 오류가 발생했습니다.",
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 연결 정보 조회 (내부 메서드)
|
|
*/
|
|
private static async getConnectionById(id: number) {
|
|
const connections = await query<any>(
|
|
`SELECT * FROM external_db_connections WHERE id = $1`,
|
|
[id]
|
|
);
|
|
|
|
if (connections.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const connection = connections[0];
|
|
|
|
// 비밀번호 복호화
|
|
if (connection.password) {
|
|
try {
|
|
const passwordEncryption = new PasswordEncryption();
|
|
connection.password = passwordEncryption.decrypt(connection.password);
|
|
} catch (error) {
|
|
console.error("비밀번호 복호화 실패:", error);
|
|
// 복호화 실패 시 원본 사용 (또는 에러 처리)
|
|
}
|
|
}
|
|
|
|
return connection;
|
|
}
|
|
|
|
/**
|
|
* REST API 데이터 미리보기
|
|
*/
|
|
static async previewRestApiData(
|
|
apiUrl: string,
|
|
apiKey: string,
|
|
endpoint: string,
|
|
method: "GET" | "POST" | "PUT" | "DELETE" = "GET",
|
|
paramInfo?: {
|
|
paramType: "url" | "query";
|
|
paramName: string;
|
|
paramValue: string;
|
|
paramSource: "static" | "dynamic";
|
|
},
|
|
// 👇 body 파라미터 추가
|
|
body?: string
|
|
): Promise<ApiResponse<any>> {
|
|
try {
|
|
// REST API 커넥터 생성
|
|
const connector = new RestApiConnector({
|
|
baseUrl: apiUrl,
|
|
apiKey: apiKey,
|
|
timeout: 10000, // 미리보기는 짧은 타임아웃
|
|
});
|
|
|
|
// 파라미터 적용
|
|
let finalEndpoint = endpoint;
|
|
if (
|
|
paramInfo &&
|
|
paramInfo.paramName &&
|
|
paramInfo.paramValue &&
|
|
paramInfo.paramSource === "static"
|
|
) {
|
|
if (paramInfo.paramType === "url") {
|
|
finalEndpoint = endpoint.replace(
|
|
`{${paramInfo.paramName}}`,
|
|
paramInfo.paramValue
|
|
);
|
|
} else if (paramInfo.paramType === "query") {
|
|
const separator = endpoint.includes("?") ? "&" : "?";
|
|
finalEndpoint = `${endpoint}${separator}${paramInfo.paramName}=${paramInfo.paramValue}`;
|
|
}
|
|
}
|
|
|
|
// JSON body 파싱
|
|
let requestData;
|
|
if (body) {
|
|
try {
|
|
requestData = JSON.parse(body);
|
|
} catch (e) {
|
|
console.warn("JSON 파싱 실패, 원본 문자열 전송");
|
|
requestData = body;
|
|
}
|
|
}
|
|
|
|
// 데이터 조회 (직접 RestApiConnector 메서드 호출)
|
|
// 타입 단언을 사용하여 private/protected 메서드 우회 또는 인터페이스 확장 필요
|
|
// 여기서는 executeRequest가 public이라고 가정
|
|
const result = await (connector as any).executeRequest(
|
|
finalEndpoint,
|
|
method,
|
|
requestData
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
data: result.data || result, // 데이터가 없으면 전체 결과 반환
|
|
message: "데이터 미리보기 성공",
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
message: "데이터 미리보기 실패",
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 외부 DB 테이블 데이터 조회
|
|
*/
|
|
static async getDataFromTable(
|
|
connectionId: number,
|
|
tableName: string
|
|
): Promise<ApiResponse<any[]>> {
|
|
try {
|
|
// 연결 정보 조회
|
|
const connection = await this.getConnectionById(connectionId);
|
|
if (!connection) {
|
|
throw new Error("외부 DB 연결 정보를 찾을 수 없습니다.");
|
|
}
|
|
|
|
// 커넥터 생성
|
|
const connector = await DatabaseConnectorFactory.createConnector(
|
|
connection.db_type,
|
|
{
|
|
host: connection.host,
|
|
port: connection.port,
|
|
database: connection.database_name,
|
|
user: connection.username,
|
|
password: connection.password,
|
|
},
|
|
connectionId
|
|
);
|
|
|
|
// 연결
|
|
await connector.connect();
|
|
|
|
// 데이터 조회 (기본 100건)
|
|
const result = await connector.executeQuery(
|
|
`SELECT * FROM ${tableName} LIMIT 100`
|
|
);
|
|
|
|
// 연결 종료
|
|
await connector.disconnect();
|
|
|
|
return {
|
|
success: true,
|
|
data: result.rows,
|
|
message: `${result.rows.length}개의 데이터를 조회했습니다.`,
|
|
};
|
|
} catch (error) {
|
|
console.error("테이블 데이터 조회 실패:", error);
|
|
return {
|
|
success: false,
|
|
message: "테이블 데이터 조회 중 오류가 발생했습니다.",
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 외부 DB 테이블 데이터 조회 (컬럼 지정)
|
|
*/
|
|
static async getDataFromTableWithColumns(
|
|
connectionId: number,
|
|
tableName: string,
|
|
columns: string[]
|
|
): Promise<ApiResponse<any[]>> {
|
|
try {
|
|
// 연결 정보 조회
|
|
const connection = await this.getConnectionById(connectionId);
|
|
if (!connection) {
|
|
throw new Error("외부 DB 연결 정보를 찾을 수 없습니다.");
|
|
}
|
|
|
|
// 커넥터 생성
|
|
const connector = await DatabaseConnectorFactory.createConnector(
|
|
connection.db_type,
|
|
{
|
|
host: connection.host,
|
|
port: connection.port,
|
|
database: connection.database_name,
|
|
user: connection.username,
|
|
password: connection.password,
|
|
},
|
|
connectionId
|
|
);
|
|
|
|
// 연결
|
|
await connector.connect();
|
|
|
|
// 컬럼 목록 쿼리 구성
|
|
const columnString = columns.join(", ");
|
|
|
|
// 데이터 조회 (기본 100건)
|
|
const result = await connector.executeQuery(
|
|
`SELECT ${columnString} FROM ${tableName} LIMIT 100`
|
|
);
|
|
|
|
// 연결 종료
|
|
await connector.disconnect();
|
|
|
|
return {
|
|
success: true,
|
|
data: result.rows,
|
|
message: `${result.rows.length}개의 데이터를 조회했습니다.`,
|
|
};
|
|
} catch (error) {
|
|
console.error("테이블 데이터 조회 실패:", error);
|
|
return {
|
|
success: false,
|
|
message: "테이블 데이터 조회 중 오류가 발생했습니다.",
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 테이블에 데이터 삽입
|
|
*/
|
|
static async insertDataToTable(
|
|
connectionId: number,
|
|
tableName: string,
|
|
data: any[]
|
|
): Promise<ApiResponse<{ successCount: number; failedCount: number }>> {
|
|
try {
|
|
// 연결 정보 조회
|
|
const connection = await this.getConnectionById(connectionId);
|
|
if (!connection) {
|
|
throw new Error("외부 DB 연결 정보를 찾을 수 없습니다.");
|
|
}
|
|
|
|
// 커넥터 생성
|
|
const connector = await DatabaseConnectorFactory.createConnector(
|
|
connection.db_type,
|
|
{
|
|
host: connection.host,
|
|
port: connection.port,
|
|
database: connection.database_name,
|
|
user: connection.username,
|
|
password: connection.password,
|
|
},
|
|
connectionId
|
|
);
|
|
|
|
// 연결
|
|
await connector.connect();
|
|
|
|
let successCount = 0;
|
|
let failedCount = 0;
|
|
|
|
// 트랜잭션 시작 (지원하는 경우)
|
|
// await connector.beginTransaction();
|
|
|
|
try {
|
|
// 각 레코드를 개별적으로 삽입
|
|
for (const record of data) {
|
|
try {
|
|
// 쿼리 빌더 사용 (간단한 구현)
|
|
const columns = Object.keys(record);
|
|
const values = Object.values(record);
|
|
const placeholders = values
|
|
.map((_, i) => (connection.db_type === "postgresql" ? `$${i + 1}` : "?"))
|
|
.join(", ");
|
|
|
|
const query = `INSERT INTO ${tableName} (${columns.join(
|
|
", "
|
|
)}) VALUES (${placeholders})`;
|
|
|
|
// 파라미터 매핑 (PostgreSQL은 $1, $2..., MySQL은 ?)
|
|
await connector.executeQuery(query, values);
|
|
successCount++;
|
|
} catch (insertError) {
|
|
console.error("레코드 삽입 실패:", insertError);
|
|
failedCount++;
|
|
}
|
|
}
|
|
|
|
// 트랜잭션 커밋
|
|
// await connector.commit();
|
|
} catch (txError) {
|
|
// 트랜잭션 롤백
|
|
// await connector.rollback();
|
|
throw txError;
|
|
} finally {
|
|
// 연결 종료
|
|
await connector.disconnect();
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: { successCount, failedCount },
|
|
message: `${successCount}건 성공, ${failedCount}건 실패`,
|
|
};
|
|
} catch (error) {
|
|
console.error("데이터 삽입 실패:", error);
|
|
return {
|
|
success: false,
|
|
message: "데이터 삽입 중 오류가 발생했습니다.",
|
|
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",
|
|
// 👇 body 파라미터 추가
|
|
body?: string
|
|
): 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}`
|
|
);
|
|
}
|
|
|
|
// 👇 Body 파싱 (POST/PUT 요청 시)
|
|
let requestData;
|
|
if (body && (method === 'POST' || method === 'PUT')) {
|
|
try {
|
|
// 템플릿 변수가 있을 수 있으므로 여기서는 원본 문자열을 사용하거나
|
|
// 정적 값만 파싱. 여기서는 일단 정적 JSON으로 가정하고 파싱 시도.
|
|
// (BatchScheduler에서 템플릿 처리 후 전달하는 것이 이상적이나,
|
|
// 현재 구조상 여기서 파싱 시도하고 실패하면 문자열 그대로 전송)
|
|
requestData = JSON.parse(body);
|
|
} catch (e) {
|
|
console.warn("JSON 파싱 실패, 원본 문자열 전송");
|
|
requestData = body;
|
|
}
|
|
}
|
|
|
|
// 데이터 조회 (REST API는 executeRequest 사용)
|
|
let result;
|
|
if ((connector as any).executeRequest) {
|
|
// executeRequest(endpoint, method, data)
|
|
result = await (connector as any).executeRequest(
|
|
finalEndpoint,
|
|
method,
|
|
requestData // body 전달
|
|
);
|
|
} else {
|
|
// Fallback (GET only)
|
|
result = await connector.executeQuery(finalEndpoint);
|
|
}
|
|
|
|
let data = result.rows || result.data || result;
|
|
|
|
// 👇 단일 객체 응답(토큰 등)인 경우 배열로 래핑하여 리스트처럼 처리
|
|
if (!Array.isArray(data)) {
|
|
data = [data];
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: data,
|
|
message: `${data.length}개의 데이터를 조회했습니다.`,
|
|
};
|
|
} catch (error) {
|
|
console.error("REST API 데이터 조회 실패:", 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]
|
|
) {
|
|
// endpoint 마지막에 ID 추가 (예: /api/users -> /api/users/123)
|
|
// 이미 /로 끝나는지 확인
|
|
const separator = finalEndpoint.endsWith("/") ? "" : "/";
|
|
finalEndpoint = `${finalEndpoint}${separator}${record[urlPathColumn]}`;
|
|
|
|
console.log(`[BatchExternalDbService] 동적 엔드포인트: ${finalEndpoint}`);
|
|
}
|
|
|
|
// 데이터 전송 (REST API는 executeRequest 사용)
|
|
if ((connector as any).executeRequest) {
|
|
await (connector as any).executeRequest(
|
|
finalEndpoint,
|
|
method,
|
|
requestData
|
|
);
|
|
} else {
|
|
// Fallback
|
|
// @ts-ignore
|
|
await connector.httpClient.request({
|
|
method: method,
|
|
url: finalEndpoint,
|
|
data: requestData
|
|
});
|
|
}
|
|
|
|
successCount++;
|
|
} catch (sendError) {
|
|
console.error("데이터 전송 실패:", sendError);
|
|
failedCount++;
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: { successCount, failedCount },
|
|
message: `${successCount}건 성공, ${failedCount}건 실패`,
|
|
};
|
|
} catch (error) {
|
|
console.error("데이터 전송 실패:", error);
|
|
return {
|
|
success: false,
|
|
message: "데이터 전송 중 오류가 발생했습니다.",
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
|
};
|
|
}
|
|
}
|
|
}
|