플로우 위젝 라벨표시 및 , 배치관리 회사별 분리

This commit is contained in:
kjs
2025-10-28 12:06:54 +09:00
parent c0f2fbbd88
commit 7c45b3e254
8 changed files with 486 additions and 270 deletions

View File

@@ -3,7 +3,12 @@
import { Response } from "express";
import { AuthenticatedRequest } from "../types/auth";
import { BatchManagementService, BatchConnectionInfo, BatchTableInfo, BatchColumnInfo } from "../services/batchManagementService";
import {
BatchManagementService,
BatchConnectionInfo,
BatchTableInfo,
BatchColumnInfo,
} from "../services/batchManagementService";
import { BatchService } from "../services/batchService";
import { BatchSchedulerService } from "../services/batchSchedulerService";
import { BatchExternalDbService } from "../services/batchExternalDbService";
@@ -11,11 +16,16 @@ import { CreateBatchConfigRequest, BatchConfig } from "../types/batchTypes";
export class BatchManagementController {
/**
* 사용 가능한 커넥션 목록 조회
* 사용 가능한 커넥션 목록 조회 (회사별)
*/
static async getAvailableConnections(req: AuthenticatedRequest, res: Response) {
static async getAvailableConnections(
req: AuthenticatedRequest,
res: Response
) {
try {
const result = await BatchManagementService.getAvailableConnections();
const userCompanyCode = req.user?.companyCode;
const result =
await BatchManagementService.getAvailableConnections(userCompanyCode);
if (result.success) {
res.json(result);
} else {
@@ -26,28 +36,36 @@ export class BatchManagementController {
res.status(500).json({
success: false,
message: "커넥션 목록 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류"
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
/**
* 특정 커넥션의 테이블 목록 조회
* 특정 커넥션의 테이블 목록 조회 (회사별)
*/
static async getTablesFromConnection(req: AuthenticatedRequest, res: Response) {
static async getTablesFromConnection(
req: AuthenticatedRequest,
res: Response
) {
try {
const { type, id } = req.params;
if (type !== 'internal' && type !== 'external') {
const userCompanyCode = req.user?.companyCode;
if (type !== "internal" && type !== "external") {
return res.status(400).json({
success: false,
message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)"
message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)",
});
}
const connectionId = type === 'external' ? Number(id) : undefined;
const result = await BatchManagementService.getTablesFromConnection(type, connectionId);
const connectionId = type === "external" ? Number(id) : undefined;
const result = await BatchManagementService.getTablesFromConnection(
type,
connectionId,
userCompanyCode
);
if (result.success) {
return res.json(result);
} else {
@@ -58,28 +76,34 @@ export class BatchManagementController {
return res.status(500).json({
success: false,
message: "테이블 목록 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류"
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
/**
* 특정 테이블의 컬럼 정보 조회
* 특정 테이블의 컬럼 정보 조회 (회사별)
*/
static async getTableColumns(req: AuthenticatedRequest, res: Response) {
try {
const { type, id, tableName } = req.params;
if (type !== 'internal' && type !== 'external') {
const userCompanyCode = req.user?.companyCode;
if (type !== "internal" && type !== "external") {
return res.status(400).json({
success: false,
message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)"
message: "올바른 연결 타입을 지정해주세요. (internal 또는 external)",
});
}
const connectionId = type === 'external' ? Number(id) : undefined;
const result = await BatchManagementService.getTableColumns(type, connectionId, tableName);
const connectionId = type === "external" ? Number(id) : undefined;
const result = await BatchManagementService.getTableColumns(
type,
connectionId,
tableName,
userCompanyCode
);
if (result.success) {
return res.json(result);
} else {
@@ -90,7 +114,7 @@ export class BatchManagementController {
return res.status(500).json({
success: false,
message: "컬럼 정보 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류"
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
@@ -101,12 +125,19 @@ export class BatchManagementController {
*/
static async createBatchConfig(req: AuthenticatedRequest, res: Response) {
try {
const { batchName, description, cronSchedule, mappings, isActive } = req.body;
if (!batchName || !cronSchedule || !mappings || !Array.isArray(mappings)) {
const { batchName, description, cronSchedule, mappings, isActive } =
req.body;
if (
!batchName ||
!cronSchedule ||
!mappings ||
!Array.isArray(mappings)
) {
return res.status(400).json({
success: false,
message: "필수 필드가 누락되었습니다. (batchName, cronSchedule, mappings)"
message:
"필수 필드가 누락되었습니다. (batchName, cronSchedule, mappings)",
});
}
@@ -115,20 +146,20 @@ export class BatchManagementController {
description,
cronSchedule,
mappings,
isActive: isActive !== undefined ? isActive : true
isActive: isActive !== undefined ? isActive : true,
} as CreateBatchConfigRequest);
return res.status(201).json({
success: true,
data: batchConfig,
message: "배치 설정이 성공적으로 생성되었습니다."
message: "배치 설정이 성공적으로 생성되었습니다.",
});
} catch (error) {
console.error("배치 설정 생성 오류:", error);
return res.status(500).json({
success: false,
message: "배치 설정 생성에 실패했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류"
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
@@ -141,28 +172,28 @@ export class BatchManagementController {
try {
const { id } = req.params;
console.log("🔍 배치 설정 조회 요청:", id);
const result = await BatchService.getBatchConfigById(Number(id));
if (!result.success) {
return res.status(404).json({
success: false,
message: result.message || "배치 설정을 찾을 수 없습니다."
message: result.message || "배치 설정을 찾을 수 없습니다.",
});
}
console.log("📋 조회된 배치 설정:", result.data);
return res.json({
success: true,
data: result.data
data: result.data,
});
} catch (error) {
console.error("❌ 배치 설정 조회 오류:", error);
return res.status(500).json({
success: false,
message: "배치 설정 조회에 실패했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류"
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
@@ -174,27 +205,27 @@ export class BatchManagementController {
static async getBatchConfigs(req: AuthenticatedRequest, res: Response) {
try {
const { page = 1, limit = 10, search, isActive } = req.query;
const filter = {
page: Number(page),
limit: Number(limit),
search: search as string,
is_active: isActive as string
is_active: isActive as string,
};
const result = await BatchService.getBatchConfigs(filter);
res.json({
success: true,
data: result.data,
pagination: result.pagination
pagination: result.pagination,
});
} catch (error) {
console.error("배치 설정 목록 조회 오류:", error);
res.status(500).json({
success: false,
message: "배치 설정 목록 조회에 실패했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류"
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
@@ -206,20 +237,22 @@ export class BatchManagementController {
static async executeBatchConfig(req: AuthenticatedRequest, res: Response) {
try {
const { id } = req.params;
if (!id || isNaN(Number(id))) {
return res.status(400).json({
success: false,
message: "올바른 배치 설정 ID를 제공해주세요."
message: "올바른 배치 설정 ID를 제공해주세요.",
});
}
// 배치 설정 조회
const batchConfigResult = await BatchService.getBatchConfigById(Number(id));
const batchConfigResult = await BatchService.getBatchConfigById(
Number(id)
);
if (!batchConfigResult.success || !batchConfigResult.data) {
return res.status(404).json({
success: false,
message: "배치 설정을 찾을 수 없습니다."
message: "배치 설정을 찾을 수 없습니다.",
});
}
@@ -229,25 +262,28 @@ export class BatchManagementController {
console.log(`배치 수동 실행 시작: ${batchConfig.batch_name} (ID: ${id})`);
let executionLog: any = null;
try {
// 실행 로그 생성
executionLog = await BatchService.createExecutionLog({
batch_config_id: Number(id),
execution_status: 'RUNNING',
execution_status: "RUNNING",
start_time: startTime,
total_records: 0,
success_records: 0,
failed_records: 0
failed_records: 0,
});
// BatchSchedulerService의 executeBatchConfig 메서드 사용 (중복 로직 제거)
const { BatchSchedulerService } = await import('../services/batchSchedulerService');
const result = await BatchSchedulerService.executeBatchConfig(batchConfig);
const { BatchSchedulerService } = await import(
"../services/batchSchedulerService"
);
const result =
await BatchSchedulerService.executeBatchConfig(batchConfig);
// result가 undefined인 경우 처리
if (!result) {
throw new Error('배치 실행 결과를 받을 수 없습니다.');
throw new Error("배치 실행 결과를 받을 수 없습니다.");
}
const endTime = new Date();
@@ -255,12 +291,12 @@ export class BatchManagementController {
// 실행 로그 업데이트 (성공)
await BatchService.updateExecutionLog(executionLog.id, {
execution_status: 'SUCCESS',
execution_status: "SUCCESS",
end_time: endTime,
duration_ms: duration,
total_records: result.totalRecords,
success_records: result.successRecords,
failed_records: result.failedRecords
failed_records: result.failedRecords,
});
return res.json({
@@ -270,45 +306,49 @@ export class BatchManagementController {
totalRecords: result.totalRecords,
successRecords: result.successRecords,
failedRecords: result.failedRecords,
executionTime: duration
executionTime: duration,
},
message: "배치가 성공적으로 실행되었습니다."
message: "배치가 성공적으로 실행되었습니다.",
});
} catch (batchError) {
console.error(`배치 실행 실패: ${batchConfig.batch_name}`, batchError);
// 실행 로그 업데이트 (실패) - executionLog가 생성되었을 경우에만
try {
const endTime = new Date();
const duration = endTime.getTime() - startTime.getTime();
// executionLog가 정의되어 있는지 확인
if (typeof executionLog !== 'undefined') {
if (typeof executionLog !== "undefined") {
await BatchService.updateExecutionLog(executionLog.id, {
execution_status: 'FAILED',
execution_status: "FAILED",
end_time: endTime,
duration_ms: duration,
error_message: batchError instanceof Error ? batchError.message : "알 수 없는 오류"
error_message:
batchError instanceof Error
? batchError.message
: "알 수 없는 오류",
});
}
} catch (logError) {
console.error('실행 로그 업데이트 실패:', logError);
console.error("실행 로그 업데이트 실패:", logError);
}
return res.status(500).json({
success: false,
message: "배치 실행에 실패했습니다.",
error: batchError instanceof Error ? batchError.message : "알 수 없는 오류"
error:
batchError instanceof Error
? batchError.message
: "알 수 없는 오류",
});
}
} catch (error) {
console.error(`배치 실행 오류 (ID: ${req.params.id}):`, error);
return res.status(500).json({
success: false,
message: "배치 실행 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error"
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
@@ -325,26 +365,29 @@ export class BatchManagementController {
if (!id || isNaN(Number(id))) {
return res.status(400).json({
success: false,
message: "올바른 배치 설정 ID를 제공해주세요."
message: "올바른 배치 설정 ID를 제공해주세요.",
});
}
const batchConfig = await BatchService.updateBatchConfig(Number(id), updateData);
const batchConfig = await BatchService.updateBatchConfig(
Number(id),
updateData
);
// 스케줄러에서 배치 스케줄 업데이트 (즉시 실행 비활성화)
await BatchSchedulerService.updateBatchSchedule(Number(id), false);
return res.json({
success: true,
data: batchConfig,
message: "배치 설정이 성공적으로 업데이트되었습니다."
message: "배치 설정이 성공적으로 업데이트되었습니다.",
});
} catch (error) {
console.error("배치 설정 업데이트 오류:", error);
return res.status(500).json({
success: false,
message: "배치 설정 업데이트에 실패했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류"
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
@@ -354,21 +397,21 @@ export class BatchManagementController {
*/
static async previewRestApiData(req: AuthenticatedRequest, res: Response) {
try {
const {
apiUrl,
apiKey,
endpoint,
method = 'GET',
const {
apiUrl,
apiKey,
endpoint,
method = "GET",
paramType,
paramName,
paramValue,
paramSource
paramSource,
} = req.body;
if (!apiUrl || !apiKey || !endpoint) {
return res.status(400).json({
success: false,
message: "API URL, API Key, 엔드포인트는 필수입니다."
message: "API URL, API Key, 엔드포인트는 필수입니다.",
});
}
@@ -378,16 +421,16 @@ export class BatchManagementController {
paramType,
paramName,
paramValue,
paramSource
paramSource,
});
// RestApiConnector 사용하여 데이터 조회
const { RestApiConnector } = await import('../database/RestApiConnector');
const { RestApiConnector } = await import("../database/RestApiConnector");
const connector = new RestApiConnector({
baseUrl: apiUrl,
apiKey: apiKey,
timeout: 30000
timeout: 30000,
});
// 연결 테스트
@@ -396,7 +439,7 @@ export class BatchManagementController {
// 파라미터가 있는 경우 엔드포인트 수정
let finalEndpoint = endpoint;
if (paramType && paramName && paramValue) {
if (paramType === 'url') {
if (paramType === "url") {
// URL 파라미터: /api/users/{userId} → /api/users/123
if (endpoint.includes(`{${paramName}}`)) {
finalEndpoint = endpoint.replace(`{${paramName}}`, paramValue);
@@ -404,9 +447,9 @@ export class BatchManagementController {
// 엔드포인트에 {paramName}이 없으면 뒤에 추가
finalEndpoint = `${endpoint}/${paramValue}`;
}
} else if (paramType === 'query') {
} else if (paramType === "query") {
// 쿼리 파라미터: /api/users?userId=123
const separator = endpoint.includes('?') ? '&' : '?';
const separator = endpoint.includes("?") ? "&" : "?";
finalEndpoint = `${endpoint}${separator}${paramName}=${paramValue}`;
}
}
@@ -417,10 +460,11 @@ export class BatchManagementController {
const result = await connector.executeQuery(finalEndpoint, method);
console.log(`[previewRestApiData] executeQuery 결과:`, {
rowCount: result.rowCount,
rowsLength: result.rows ? result.rows.length : 'undefined',
firstRow: result.rows && result.rows.length > 0 ? result.rows[0] : 'no data'
rowsLength: result.rows ? result.rows.length : "undefined",
firstRow:
result.rows && result.rows.length > 0 ? result.rows[0] : "no data",
});
const data = result.rows.slice(0, 5); // 최대 5개 샘플만
console.log(`[previewRestApiData] 슬라이스된 데이터:`, data);
@@ -428,15 +472,15 @@ export class BatchManagementController {
// 첫 번째 객체에서 필드명 추출
const fields = Object.keys(data[0]);
console.log(`[previewRestApiData] 추출된 필드:`, fields);
return res.json({
success: true,
data: {
fields: fields,
samples: data,
totalCount: result.rowCount || data.length
totalCount: result.rowCount || data.length,
},
message: `${fields.length}개 필드, ${result.rowCount || data.length}개 레코드를 조회했습니다.`
message: `${fields.length}개 필드, ${result.rowCount || data.length}개 레코드를 조회했습니다.`,
});
} else {
return res.json({
@@ -444,9 +488,9 @@ export class BatchManagementController {
data: {
fields: [],
samples: [],
totalCount: 0
totalCount: 0,
},
message: "API에서 데이터를 가져올 수 없습니다."
message: "API에서 데이터를 가져올 수 없습니다.",
});
}
} catch (error) {
@@ -454,7 +498,7 @@ export class BatchManagementController {
return res.status(500).json({
success: false,
message: "REST API 데이터 미리보기 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류"
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
@@ -464,18 +508,19 @@ export class BatchManagementController {
*/
static async saveRestApiBatch(req: AuthenticatedRequest, res: Response) {
try {
const {
batchName,
batchType,
cronSchedule,
description,
apiMappings
} = req.body;
const { batchName, batchType, cronSchedule, description, apiMappings } =
req.body;
if (!batchName || !batchType || !cronSchedule || !apiMappings || apiMappings.length === 0) {
if (
!batchName ||
!batchType ||
!cronSchedule ||
!apiMappings ||
apiMappings.length === 0
) {
return res.status(400).json({
success: false,
message: "필수 필드가 누락되었습니다."
message: "필수 필드가 누락되었습니다.",
});
}
@@ -484,15 +529,15 @@ export class BatchManagementController {
batchType,
cronSchedule,
description,
apiMappings
apiMappings,
});
// BatchService를 사용하여 배치 설정 저장
const batchConfig: CreateBatchConfigRequest = {
batchName: batchName,
description: description || '',
description: description || "",
cronSchedule: cronSchedule,
mappings: apiMappings
mappings: apiMappings,
};
const result = await BatchService.createBatchConfig(batchConfig);
@@ -501,7 +546,9 @@ export class BatchManagementController {
// 스케줄러에 자동 등록 ✅
try {
await BatchSchedulerService.scheduleBatchConfig(result.data);
console.log(`✅ 새로운 배치가 스케줄러에 등록되었습니다: ${batchName} (ID: ${result.data.id})`);
console.log(
`✅ 새로운 배치가 스케줄러에 등록되었습니다: ${batchName} (ID: ${result.data.id})`
);
} catch (schedulerError) {
console.error(`❌ 스케줄러 등록 실패: ${batchName}`, schedulerError);
// 스케줄러 등록 실패해도 배치 저장은 성공으로 처리
@@ -510,19 +557,19 @@ export class BatchManagementController {
return res.json({
success: true,
message: "REST API 배치가 성공적으로 저장되었습니다.",
data: result.data
data: result.data,
});
} else {
return res.status(500).json({
success: false,
message: result.message || "배치 저장에 실패했습니다."
message: result.message || "배치 저장에 실패했습니다.",
});
}
} catch (error) {
console.error("REST API 배치 저장 오류:", error);
return res.status(500).json({
success: false,
message: "배치 저장 중 오류가 발생했습니다."
message: "배치 저장 중 오류가 발생했습니다.",
});
}
}