2025-11-27 11:32:19 +09:00
|
|
|
import cron from "node-cron";
|
2025-10-01 13:34:56 +09:00
|
|
|
import { BatchService } from "./batchService";
|
|
|
|
|
import { BatchExecutionLogService } from "./batchExecutionLogService";
|
|
|
|
|
import { logger } from "../utils/logger";
|
2025-09-25 11:04:16 +09:00
|
|
|
|
|
|
|
|
export class BatchSchedulerService {
|
|
|
|
|
private static scheduledTasks: Map<number, cron.ScheduledTask> = new Map();
|
|
|
|
|
|
|
|
|
|
/**
|
2025-11-27 11:32:19 +09:00
|
|
|
* 모든 활성 배치의 스케줄링 초기화
|
2025-09-25 11:04:16 +09:00
|
|
|
*/
|
2025-11-27 11:32:19 +09:00
|
|
|
static async initializeScheduler() {
|
2025-09-25 11:04:16 +09:00
|
|
|
try {
|
2025-11-27 11:32:19 +09:00
|
|
|
logger.info("배치 스케줄러 초기화 시작");
|
2025-09-25 11:04:16 +09:00
|
|
|
|
2025-11-27 11:32:19 +09:00
|
|
|
const batchConfigsResponse = await BatchService.getBatchConfigs({
|
|
|
|
|
is_active: "Y",
|
|
|
|
|
});
|
2025-10-01 13:34:56 +09:00
|
|
|
|
2025-11-27 11:32:19 +09:00
|
|
|
if (!batchConfigsResponse.success || !batchConfigsResponse.data) {
|
|
|
|
|
logger.warn("스케줄링할 활성 배치 설정이 없습니다.");
|
|
|
|
|
return;
|
2025-09-29 16:55:37 +09:00
|
|
|
}
|
2025-10-01 13:34:56 +09:00
|
|
|
|
2025-11-27 11:32:19 +09:00
|
|
|
const batchConfigs = batchConfigsResponse.data;
|
|
|
|
|
logger.info(`${batchConfigs.length}개의 배치 설정 스케줄링 등록`);
|
2025-09-29 16:55:37 +09:00
|
|
|
|
2025-11-27 11:32:19 +09:00
|
|
|
for (const config of batchConfigs) {
|
|
|
|
|
await this.scheduleBatch(config);
|
2025-09-25 11:04:16 +09:00
|
|
|
}
|
2025-11-27 11:32:19 +09:00
|
|
|
|
|
|
|
|
logger.info("배치 스케줄러 초기화 완료");
|
2025-09-25 11:04:16 +09:00
|
|
|
} catch (error) {
|
2025-11-27 11:32:19 +09:00
|
|
|
logger.error("배치 스케줄러 초기화 중 오류 발생:", error);
|
2025-09-25 11:04:16 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-11-27 11:32:19 +09:00
|
|
|
* 개별 배치 작업 스케줄링
|
2025-09-25 11:04:16 +09:00
|
|
|
*/
|
2025-11-27 11:32:19 +09:00
|
|
|
static async scheduleBatch(config: any) {
|
2025-09-25 11:04:16 +09:00
|
|
|
try {
|
2025-11-27 11:32:19 +09:00
|
|
|
// 기존 스케줄이 있으면 제거
|
|
|
|
|
if (this.scheduledTasks.has(config.id)) {
|
|
|
|
|
this.scheduledTasks.get(config.id)?.stop();
|
|
|
|
|
this.scheduledTasks.delete(config.id);
|
2025-09-25 11:04:16 +09:00
|
|
|
}
|
|
|
|
|
|
2025-11-27 11:32:19 +09:00
|
|
|
if (config.is_active !== "Y") {
|
|
|
|
|
logger.info(
|
|
|
|
|
`배치 스케줄링 건너뜀 (비활성 상태): ${config.batch_name} (ID: ${config.id})`
|
|
|
|
|
);
|
2025-09-25 11:04:16 +09:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-27 11:32:19 +09:00
|
|
|
if (!cron.validate(config.cron_schedule)) {
|
|
|
|
|
logger.error(
|
|
|
|
|
`유효하지 않은 Cron 표현식: ${config.cron_schedule} (Batch ID: ${config.id})`
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-10-01 13:34:56 +09:00
|
|
|
|
|
|
|
|
logger.info(
|
2025-11-27 11:32:19 +09:00
|
|
|
`배치 스케줄 등록: ${config.batch_name} (ID: ${config.id}, Cron: ${config.cron_schedule})`
|
2025-10-01 13:34:56 +09:00
|
|
|
);
|
2025-09-25 11:04:16 +09:00
|
|
|
|
2025-11-27 11:32:19 +09:00
|
|
|
const task = cron.schedule(config.cron_schedule, async () => {
|
|
|
|
|
logger.info(
|
|
|
|
|
`스케줄에 의한 배치 실행 시작: ${config.batch_name} (ID: ${config.id})`
|
|
|
|
|
);
|
|
|
|
|
await this.executeBatchConfig(config);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.scheduledTasks.set(config.id, task);
|
2025-09-25 11:04:16 +09:00
|
|
|
} catch (error) {
|
2025-11-27 11:32:19 +09:00
|
|
|
logger.error(`배치 스케줄링 중 오류 발생 (ID: ${config.id}):`, error);
|
2025-09-25 11:04:16 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-11-27 11:32:19 +09:00
|
|
|
* 배치 스케줄 업데이트 (설정 변경 시 호출)
|
2025-09-25 11:04:16 +09:00
|
|
|
*/
|
2025-10-01 13:34:56 +09:00
|
|
|
static async updateBatchSchedule(
|
|
|
|
|
configId: number,
|
|
|
|
|
executeImmediately: boolean = true
|
|
|
|
|
) {
|
2025-09-25 11:04:16 +09:00
|
|
|
try {
|
2025-11-27 11:32:19 +09:00
|
|
|
const result = await BatchService.getBatchConfigById(configId);
|
|
|
|
|
if (!result.success || !result.data) {
|
|
|
|
|
// 설정이 없으면 스케줄 제거
|
|
|
|
|
if (this.scheduledTasks.has(configId)) {
|
|
|
|
|
this.scheduledTasks.get(configId)?.stop();
|
|
|
|
|
this.scheduledTasks.delete(configId);
|
|
|
|
|
}
|
2025-09-25 11:04:16 +09:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-27 11:32:19 +09:00
|
|
|
const config = result.data;
|
2025-10-01 13:34:56 +09:00
|
|
|
|
2025-11-27 11:32:19 +09:00
|
|
|
// 스케줄 재등록
|
|
|
|
|
await this.scheduleBatch(config);
|
|
|
|
|
|
|
|
|
|
// 즉시 실행 옵션이 있으면 실행
|
|
|
|
|
/*
|
|
|
|
|
if (executeImmediately && config.is_active === "Y") {
|
|
|
|
|
logger.info(`배치 설정 변경 후 즉시 실행: ${config.batch_name}`);
|
|
|
|
|
this.executeBatchConfig(config).catch((err) =>
|
|
|
|
|
logger.error(`즉시 실행 중 오류 발생:`, err)
|
2025-10-01 13:34:56 +09:00
|
|
|
);
|
2025-09-25 11:04:16 +09:00
|
|
|
}
|
2025-11-27 11:32:19 +09:00
|
|
|
*/
|
2025-09-25 11:04:16 +09:00
|
|
|
} catch (error) {
|
|
|
|
|
logger.error(`배치 스케줄 업데이트 실패: ID ${configId}`, error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 배치 설정 실행
|
|
|
|
|
*/
|
2025-09-29 16:55:37 +09:00
|
|
|
static async executeBatchConfig(config: any) {
|
2025-09-25 11:04:16 +09:00
|
|
|
const startTime = new Date();
|
|
|
|
|
let executionLog: any = null;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
logger.info(`배치 실행 시작: ${config.batch_name} (ID: ${config.id})`);
|
|
|
|
|
|
2025-12-01 11:34:22 +09:00
|
|
|
// 매핑 정보가 없으면 상세 조회로 다시 가져오기
|
|
|
|
|
if (!config.batch_mappings || config.batch_mappings.length === 0) {
|
|
|
|
|
const fullConfig = await BatchService.getBatchConfigById(config.id);
|
|
|
|
|
if (fullConfig.success && fullConfig.data) {
|
|
|
|
|
config = fullConfig.data;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 11:04:16 +09:00
|
|
|
// 실행 로그 생성
|
2025-10-01 13:34:56 +09:00
|
|
|
const executionLogResponse =
|
|
|
|
|
await BatchExecutionLogService.createExecutionLog({
|
|
|
|
|
batch_config_id: config.id,
|
2025-11-27 11:32:19 +09:00
|
|
|
company_code: config.company_code,
|
2025-10-01 13:34:56 +09:00
|
|
|
execution_status: "RUNNING",
|
|
|
|
|
start_time: startTime,
|
|
|
|
|
total_records: 0,
|
|
|
|
|
success_records: 0,
|
|
|
|
|
failed_records: 0,
|
|
|
|
|
});
|
2025-09-25 11:04:16 +09:00
|
|
|
|
|
|
|
|
if (!executionLogResponse.success || !executionLogResponse.data) {
|
2025-10-01 13:34:56 +09:00
|
|
|
logger.error(
|
|
|
|
|
`배치 실행 로그 생성 실패: ${config.batch_name}`,
|
|
|
|
|
executionLogResponse.message
|
|
|
|
|
);
|
2025-09-29 16:55:37 +09:00
|
|
|
return {
|
|
|
|
|
totalRecords: 0,
|
|
|
|
|
successRecords: 0,
|
2025-10-01 13:34:56 +09:00
|
|
|
failedRecords: 1,
|
2025-09-29 16:55:37 +09:00
|
|
|
};
|
2025-09-25 11:04:16 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
executionLog = executionLogResponse.data;
|
|
|
|
|
|
|
|
|
|
// 실제 배치 실행 로직 (수동 실행과 동일한 로직 사용)
|
|
|
|
|
const result = await this.executeBatchMappings(config);
|
|
|
|
|
|
|
|
|
|
// 실행 로그 업데이트 (성공)
|
|
|
|
|
await BatchExecutionLogService.updateExecutionLog(executionLog.id, {
|
2025-10-01 13:34:56 +09:00
|
|
|
execution_status: "SUCCESS",
|
2025-09-25 11:04:16 +09:00
|
|
|
end_time: new Date(),
|
|
|
|
|
duration_ms: Date.now() - startTime.getTime(),
|
|
|
|
|
total_records: result.totalRecords,
|
|
|
|
|
success_records: result.successRecords,
|
2025-10-01 13:34:56 +09:00
|
|
|
failed_records: result.failedRecords,
|
2025-09-25 11:04:16 +09:00
|
|
|
});
|
|
|
|
|
|
2025-10-01 13:34:56 +09:00
|
|
|
logger.info(
|
|
|
|
|
`배치 실행 완료: ${config.batch_name} (처리된 레코드: ${result.totalRecords})`
|
|
|
|
|
);
|
|
|
|
|
|
2025-09-29 16:55:37 +09:00
|
|
|
// 성공 결과 반환
|
|
|
|
|
return result;
|
2025-09-25 11:04:16 +09:00
|
|
|
} catch (error) {
|
2025-11-27 11:32:19 +09:00
|
|
|
logger.error(`배치 실행 중 오류 발생: ${config.batch_name}`, error);
|
2025-09-25 11:04:16 +09:00
|
|
|
|
|
|
|
|
// 실행 로그 업데이트 (실패)
|
|
|
|
|
if (executionLog) {
|
|
|
|
|
await BatchExecutionLogService.updateExecutionLog(executionLog.id, {
|
2025-11-27 11:32:19 +09:00
|
|
|
execution_status: "FAILURE",
|
2025-09-25 11:04:16 +09:00
|
|
|
end_time: new Date(),
|
|
|
|
|
duration_ms: Date.now() - startTime.getTime(),
|
2025-10-01 13:34:56 +09:00
|
|
|
error_message:
|
|
|
|
|
error instanceof Error ? error.message : "알 수 없는 오류",
|
2025-09-25 11:04:16 +09:00
|
|
|
});
|
|
|
|
|
}
|
2025-10-01 13:34:56 +09:00
|
|
|
|
2025-11-27 11:32:19 +09:00
|
|
|
// 실패 결과 반환
|
2025-09-29 16:55:37 +09:00
|
|
|
return {
|
|
|
|
|
totalRecords: 0,
|
|
|
|
|
successRecords: 0,
|
2025-10-01 13:34:56 +09:00
|
|
|
failedRecords: 1,
|
2025-09-29 16:55:37 +09:00
|
|
|
};
|
2025-09-25 11:04:16 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 배치 매핑 실행 (수동 실행과 동일한 로직)
|
|
|
|
|
*/
|
|
|
|
|
private static async executeBatchMappings(config: any) {
|
|
|
|
|
let totalRecords = 0;
|
|
|
|
|
let successRecords = 0;
|
|
|
|
|
let failedRecords = 0;
|
|
|
|
|
|
|
|
|
|
if (!config.batch_mappings || config.batch_mappings.length === 0) {
|
|
|
|
|
logger.warn(`배치 매핑이 없습니다: ${config.batch_name}`);
|
|
|
|
|
return { totalRecords, successRecords, failedRecords };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 테이블별로 매핑을 그룹화
|
|
|
|
|
const tableGroups = new Map<string, typeof config.batch_mappings>();
|
2025-10-01 13:34:56 +09:00
|
|
|
|
2025-09-25 11:04:16 +09:00
|
|
|
for (const mapping of config.batch_mappings) {
|
2025-10-01 13:34:56 +09:00
|
|
|
const key = `${mapping.from_connection_type}:${mapping.from_connection_id || "internal"}:${mapping.from_table_name}`;
|
2025-09-25 11:04:16 +09:00
|
|
|
if (!tableGroups.has(key)) {
|
|
|
|
|
tableGroups.set(key, []);
|
|
|
|
|
}
|
|
|
|
|
tableGroups.get(key)!.push(mapping);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 각 테이블 그룹별로 처리
|
|
|
|
|
for (const [tableKey, mappings] of tableGroups) {
|
|
|
|
|
try {
|
|
|
|
|
const firstMapping = mappings[0];
|
2025-10-01 13:34:56 +09:00
|
|
|
logger.info(
|
|
|
|
|
`테이블 처리 시작: ${tableKey} -> ${mappings.length}개 컬럼 매핑`
|
|
|
|
|
);
|
|
|
|
|
|
2025-09-26 17:29:20 +09:00
|
|
|
let fromData: any[] = [];
|
2025-10-01 13:34:56 +09:00
|
|
|
|
2025-09-26 17:29:20 +09:00
|
|
|
// FROM 데이터 조회 (DB 또는 REST API)
|
2025-10-01 13:34:56 +09:00
|
|
|
if (firstMapping.from_connection_type === "restapi") {
|
2025-09-26 17:29:20 +09:00
|
|
|
// REST API에서 데이터 조회
|
2025-10-01 13:34:56 +09:00
|
|
|
logger.info(
|
|
|
|
|
`REST API에서 데이터 조회: ${firstMapping.from_api_url}${firstMapping.from_table_name}`
|
|
|
|
|
);
|
|
|
|
|
const { BatchExternalDbService } = await import(
|
|
|
|
|
"./batchExternalDbService"
|
|
|
|
|
);
|
2025-11-27 11:32:19 +09:00
|
|
|
|
|
|
|
|
// 👇 Body 파라미터 추가 (POST 요청 시)
|
2025-09-26 17:29:20 +09:00
|
|
|
const apiResult = await BatchExternalDbService.getDataFromRestApi(
|
|
|
|
|
firstMapping.from_api_url!,
|
|
|
|
|
firstMapping.from_api_key!,
|
|
|
|
|
firstMapping.from_table_name,
|
2025-10-01 13:34:56 +09:00
|
|
|
(firstMapping.from_api_method as
|
|
|
|
|
| "GET"
|
|
|
|
|
| "POST"
|
|
|
|
|
| "PUT"
|
|
|
|
|
| "DELETE") || "GET",
|
2025-09-29 13:48:59 +09:00
|
|
|
mappings.map((m: any) => m.from_column_name),
|
|
|
|
|
100, // limit
|
|
|
|
|
// 파라미터 정보 전달
|
|
|
|
|
firstMapping.from_api_param_type,
|
|
|
|
|
firstMapping.from_api_param_name,
|
|
|
|
|
firstMapping.from_api_param_value,
|
2025-11-27 11:32:19 +09:00
|
|
|
firstMapping.from_api_param_source,
|
|
|
|
|
// 👇 Body 전달 (FROM - REST API - POST 요청)
|
|
|
|
|
firstMapping.from_api_body
|
2025-09-26 17:29:20 +09:00
|
|
|
);
|
2025-10-01 13:34:56 +09:00
|
|
|
|
2025-09-26 17:29:20 +09:00
|
|
|
if (apiResult.success && apiResult.data) {
|
|
|
|
|
fromData = apiResult.data;
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error(`REST API 데이터 조회 실패: ${apiResult.message}`);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// DB에서 데이터 조회
|
|
|
|
|
const fromColumns = mappings.map((m: any) => m.from_column_name);
|
|
|
|
|
fromData = await BatchService.getDataFromTableWithColumns(
|
|
|
|
|
firstMapping.from_table_name,
|
|
|
|
|
fromColumns,
|
2025-10-01 13:34:56 +09:00
|
|
|
firstMapping.from_connection_type as "internal" | "external",
|
2025-09-26 17:29:20 +09:00
|
|
|
firstMapping.from_connection_id || undefined
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-10-01 13:34:56 +09:00
|
|
|
|
2025-09-25 11:04:16 +09:00
|
|
|
totalRecords += fromData.length;
|
|
|
|
|
|
|
|
|
|
// 컬럼 매핑 적용하여 TO 테이블 형식으로 변환
|
2025-11-27 11:32:19 +09:00
|
|
|
// 유틸리티 함수: 점 표기법을 사용하여 중첩된 객체 값 가져오기
|
|
|
|
|
const getValueByPath = (obj: any, path: string) => {
|
|
|
|
|
if (!path) return undefined;
|
|
|
|
|
// path가 'response.access_token' 처럼 점을 포함하는 경우
|
|
|
|
|
if (path.includes(".")) {
|
|
|
|
|
return path.split(".").reduce((acc, part) => acc && acc[part], obj);
|
|
|
|
|
}
|
|
|
|
|
// 단순 키인 경우
|
|
|
|
|
return obj[path];
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-01 13:34:56 +09:00
|
|
|
const mappedData = fromData.map((row) => {
|
2025-09-25 11:04:16 +09:00
|
|
|
const mappedRow: any = {};
|
|
|
|
|
for (const mapping of mappings) {
|
2025-09-26 17:29:20 +09:00
|
|
|
// DB → REST API 배치인지 확인
|
2025-10-01 13:34:56 +09:00
|
|
|
if (
|
|
|
|
|
firstMapping.to_connection_type === "restapi" &&
|
|
|
|
|
mapping.to_api_body
|
|
|
|
|
) {
|
2025-09-26 17:29:20 +09:00
|
|
|
// DB → REST API: 원본 컬럼명을 키로 사용 (템플릿 처리용)
|
2025-10-01 13:34:56 +09:00
|
|
|
mappedRow[mapping.from_column_name] =
|
|
|
|
|
row[mapping.from_column_name];
|
2025-09-26 17:29:20 +09:00
|
|
|
} else {
|
2025-11-27 11:32:19 +09:00
|
|
|
// REST API -> DB (POST 요청 포함) 또는 DB -> DB
|
|
|
|
|
// row[mapping.from_column_name] 대신 getValueByPath 사용
|
|
|
|
|
const value = getValueByPath(row, mapping.from_column_name);
|
|
|
|
|
|
|
|
|
|
mappedRow[mapping.to_column_name] = value;
|
2025-09-26 17:29:20 +09:00
|
|
|
}
|
2025-09-25 11:04:16 +09:00
|
|
|
}
|
2025-11-27 11:55:31 +09:00
|
|
|
|
|
|
|
|
// 멀티테넌시: TO가 DB일 때 company_code 자동 주입
|
|
|
|
|
// - 배치 설정에 company_code가 있고
|
|
|
|
|
// - 매핑에서 company_code를 명시적으로 다루지 않은 경우만
|
|
|
|
|
if (
|
|
|
|
|
firstMapping.to_connection_type !== "restapi" &&
|
|
|
|
|
config.company_code &&
|
|
|
|
|
mappedRow.company_code === undefined
|
|
|
|
|
) {
|
|
|
|
|
mappedRow.company_code = config.company_code;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 11:04:16 +09:00
|
|
|
return mappedRow;
|
|
|
|
|
});
|
|
|
|
|
|
2025-09-26 17:29:20 +09:00
|
|
|
// TO 테이블에 데이터 삽입 (DB 또는 REST API)
|
|
|
|
|
let insertResult: { successCount: number; failedCount: number };
|
2025-10-01 13:34:56 +09:00
|
|
|
|
|
|
|
|
if (firstMapping.to_connection_type === "restapi") {
|
2025-09-26 17:29:20 +09:00
|
|
|
// REST API로 데이터 전송
|
2025-10-01 13:34:56 +09:00
|
|
|
logger.info(
|
|
|
|
|
`REST API로 데이터 전송: ${firstMapping.to_api_url}${firstMapping.to_table_name}`
|
|
|
|
|
);
|
|
|
|
|
const { BatchExternalDbService } = await import(
|
|
|
|
|
"./batchExternalDbService"
|
|
|
|
|
);
|
|
|
|
|
|
2025-09-26 17:29:20 +09:00
|
|
|
// DB → REST API 배치인지 확인 (to_api_body가 있으면 템플릿 기반)
|
|
|
|
|
const hasTemplate = mappings.some((m: any) => m.to_api_body);
|
2025-10-01 13:34:56 +09:00
|
|
|
|
2025-09-26 17:29:20 +09:00
|
|
|
if (hasTemplate) {
|
|
|
|
|
// 템플릿 기반 REST API 전송 (DB → REST API 배치)
|
2025-10-01 13:34:56 +09:00
|
|
|
const templateBody = firstMapping.to_api_body || "{}";
|
|
|
|
|
logger.info(
|
|
|
|
|
`템플릿 기반 REST API 전송, Request Body 템플릿: ${templateBody}`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// URL 경로 컬럼 찾기 (PUT/DELETE용)
|
|
|
|
|
const urlPathColumn = mappings.find(
|
|
|
|
|
(m: any) => m.to_column_name === "URL_PATH_PARAM"
|
|
|
|
|
)?.from_column_name;
|
|
|
|
|
|
|
|
|
|
const apiResult =
|
|
|
|
|
await BatchExternalDbService.sendDataToRestApiWithTemplate(
|
|
|
|
|
firstMapping.to_api_url!,
|
|
|
|
|
firstMapping.to_api_key!,
|
|
|
|
|
firstMapping.to_table_name,
|
|
|
|
|
(firstMapping.to_api_method as "POST" | "PUT" | "DELETE") ||
|
|
|
|
|
"POST",
|
|
|
|
|
templateBody,
|
|
|
|
|
mappedData,
|
|
|
|
|
urlPathColumn
|
|
|
|
|
);
|
|
|
|
|
|
2025-09-26 17:29:20 +09:00
|
|
|
if (apiResult.success && apiResult.data) {
|
|
|
|
|
insertResult = apiResult.data;
|
|
|
|
|
} else {
|
2025-10-01 13:34:56 +09:00
|
|
|
throw new Error(
|
|
|
|
|
`템플릿 기반 REST API 데이터 전송 실패: ${apiResult.message}`
|
|
|
|
|
);
|
2025-09-26 17:29:20 +09:00
|
|
|
}
|
|
|
|
|
} else {
|
2025-11-27 11:32:19 +09:00
|
|
|
// 기존 REST API 전송 (REST API → DB 배치) - 사실 이 경우는 거의 없음 (REST to REST)
|
|
|
|
|
// 지원하지 않음
|
|
|
|
|
logger.warn(
|
|
|
|
|
"REST API -> REST API (단순 매핑)은 아직 지원하지 않습니다."
|
2025-09-26 17:29:20 +09:00
|
|
|
);
|
2025-11-27 11:32:19 +09:00
|
|
|
insertResult = { successCount: 0, failedCount: 0 };
|
2025-09-26 17:29:20 +09:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// DB에 데이터 삽입
|
|
|
|
|
insertResult = await BatchService.insertDataToTable(
|
|
|
|
|
firstMapping.to_table_name,
|
|
|
|
|
mappedData,
|
2025-10-01 13:34:56 +09:00
|
|
|
firstMapping.to_connection_type as "internal" | "external",
|
2025-09-26 17:29:20 +09:00
|
|
|
firstMapping.to_connection_id || undefined
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-10-01 13:34:56 +09:00
|
|
|
|
2025-09-25 11:04:16 +09:00
|
|
|
successRecords += insertResult.successCount;
|
|
|
|
|
failedRecords += insertResult.failedCount;
|
|
|
|
|
} catch (error) {
|
2025-11-27 11:32:19 +09:00
|
|
|
logger.error(`테이블 처리 중 오류 발생: ${tableKey}`, error);
|
|
|
|
|
// 해당 테이블 처리 실패는 전체 실패로 간주하지 않고, 실패 카운트만 증가?
|
|
|
|
|
// 여기서는 일단 실패 로그만 남기고 계속 진행 (필요시 정책 변경)
|
2025-09-25 11:04:16 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { totalRecords, successRecords, failedRecords };
|
|
|
|
|
}
|
|
|
|
|
}
|