Merge branch 'dev' of http://39.117.244.52:3000/kjs/ERP-node into dataflowMng
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { logger } from "../utils/logger";
|
||||
import { cache, CacheKeys } from "../utils/cache";
|
||||
import {
|
||||
TableInfo,
|
||||
ColumnTypeInfo,
|
||||
@@ -21,6 +22,13 @@ export class TableManagementService {
|
||||
try {
|
||||
logger.info("테이블 목록 조회 시작");
|
||||
|
||||
// 캐시에서 먼저 확인
|
||||
const cachedTables = cache.get<TableInfo[]>(CacheKeys.TABLE_LIST);
|
||||
if (cachedTables) {
|
||||
logger.info(`테이블 목록 캐시에서 조회: ${cachedTables.length}개`);
|
||||
return cachedTables;
|
||||
}
|
||||
|
||||
// information_schema는 여전히 $queryRaw 사용
|
||||
const rawTables = await prisma.$queryRaw<any[]>`
|
||||
SELECT
|
||||
@@ -44,6 +52,9 @@ export class TableManagementService {
|
||||
columnCount: Number(table.columnCount), // BigInt → Number 변환
|
||||
}));
|
||||
|
||||
// 캐시에 저장 (10분 TTL)
|
||||
cache.set(CacheKeys.TABLE_LIST, tables, 10 * 60 * 1000);
|
||||
|
||||
logger.info(`테이블 목록 조회 완료: ${tables.length}개`);
|
||||
return tables;
|
||||
} catch (error) {
|
||||
@@ -55,14 +66,59 @@ export class TableManagementService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 컬럼 정보 조회
|
||||
* 테이블 컬럼 정보 조회 (페이지네이션 지원)
|
||||
* 메타데이터 조회는 Prisma로 변경 불가
|
||||
*/
|
||||
async getColumnList(tableName: string): Promise<ColumnTypeInfo[]> {
|
||||
async getColumnList(
|
||||
tableName: string,
|
||||
page: number = 1,
|
||||
size: number = 50
|
||||
): Promise<{
|
||||
columns: ColumnTypeInfo[];
|
||||
total: number;
|
||||
page: number;
|
||||
size: number;
|
||||
totalPages: number;
|
||||
}> {
|
||||
try {
|
||||
logger.info(`컬럼 정보 조회 시작: ${tableName}`);
|
||||
logger.info(
|
||||
`컬럼 정보 조회 시작: ${tableName} (page: ${page}, size: ${size})`
|
||||
);
|
||||
|
||||
// information_schema는 여전히 $queryRaw 사용
|
||||
// 캐시 키 생성
|
||||
const cacheKey = CacheKeys.TABLE_COLUMNS(tableName, page, size);
|
||||
const countCacheKey = CacheKeys.TABLE_COLUMN_COUNT(tableName);
|
||||
|
||||
// 캐시에서 먼저 확인
|
||||
const cachedResult = cache.get<{
|
||||
columns: ColumnTypeInfo[];
|
||||
total: number;
|
||||
page: number;
|
||||
size: number;
|
||||
totalPages: number;
|
||||
}>(cacheKey);
|
||||
if (cachedResult) {
|
||||
logger.info(
|
||||
`컬럼 정보 캐시에서 조회: ${tableName}, ${cachedResult.columns.length}/${cachedResult.total}개`
|
||||
);
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
// 전체 컬럼 수 조회 (캐시 확인)
|
||||
let total = cache.get<number>(countCacheKey);
|
||||
if (!total) {
|
||||
const totalResult = await prisma.$queryRaw<[{ count: bigint }]>`
|
||||
SELECT COUNT(*) as count
|
||||
FROM information_schema.columns c
|
||||
WHERE c.table_name = ${tableName}
|
||||
`;
|
||||
total = Number(totalResult[0].count);
|
||||
// 컬럼 수는 자주 변하지 않으므로 30분 캐시
|
||||
cache.set(countCacheKey, total, 30 * 60 * 1000);
|
||||
}
|
||||
|
||||
// 페이지네이션 적용한 컬럼 조회
|
||||
const offset = (page - 1) * size;
|
||||
const rawColumns = await prisma.$queryRaw<any[]>`
|
||||
SELECT
|
||||
c.column_name as "columnName",
|
||||
@@ -98,6 +154,7 @@ export class TableManagementService {
|
||||
) pk ON c.column_name = pk.column_name AND c.table_name = pk.table_name
|
||||
WHERE c.table_name = ${tableName}
|
||||
ORDER BY c.ordinal_position
|
||||
LIMIT ${size} OFFSET ${offset}
|
||||
`;
|
||||
|
||||
// BigInt를 Number로 변환하여 JSON 직렬화 문제 해결
|
||||
@@ -111,8 +168,23 @@ export class TableManagementService {
|
||||
displayOrder: column.displayOrder ? Number(column.displayOrder) : null,
|
||||
}));
|
||||
|
||||
logger.info(`컬럼 정보 조회 완료: ${tableName}, ${columns.length}개`);
|
||||
return columns;
|
||||
const totalPages = Math.ceil(total / size);
|
||||
|
||||
const result = {
|
||||
columns,
|
||||
total,
|
||||
page,
|
||||
size,
|
||||
totalPages,
|
||||
};
|
||||
|
||||
// 캐시에 저장 (5분 TTL)
|
||||
cache.set(cacheKey, result, 5 * 60 * 1000);
|
||||
|
||||
logger.info(
|
||||
`컬럼 정보 조회 완료: ${tableName}, ${columns.length}/${total}개 (${page}/${totalPages} 페이지)`
|
||||
);
|
||||
return result;
|
||||
} catch (error) {
|
||||
logger.error(`컬럼 정보 조회 중 오류 발생: ${tableName}`, error);
|
||||
throw new Error(
|
||||
@@ -148,6 +220,40 @@ export class TableManagementService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 라벨 업데이트
|
||||
*/
|
||||
async updateTableLabel(
|
||||
tableName: string,
|
||||
displayName: string,
|
||||
description?: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
logger.info(`테이블 라벨 업데이트 시작: ${tableName}`);
|
||||
|
||||
// table_labels 테이블에 UPSERT
|
||||
await prisma.$executeRaw`
|
||||
INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date)
|
||||
VALUES (${tableName}, ${displayName}, ${description || ""}, NOW(), NOW())
|
||||
ON CONFLICT (table_name)
|
||||
DO UPDATE SET
|
||||
table_label = EXCLUDED.table_label,
|
||||
description = EXCLUDED.description,
|
||||
updated_date = NOW()
|
||||
`;
|
||||
|
||||
// 캐시 무효화
|
||||
cache.delete(CacheKeys.TABLE_LIST);
|
||||
|
||||
logger.info(`테이블 라벨 업데이트 완료: ${tableName}`);
|
||||
} catch (error) {
|
||||
logger.error("테이블 라벨 업데이트 중 오류 발생:", error);
|
||||
throw new Error(
|
||||
`테이블 라벨 업데이트 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 컬럼 설정 업데이트 (UPSERT 방식)
|
||||
* Prisma ORM으로 변경
|
||||
@@ -562,14 +668,33 @@ export class TableManagementService {
|
||||
for (const fileColumn of fileColumns) {
|
||||
const filePath = row[fileColumn];
|
||||
if (filePath && typeof filePath === "string") {
|
||||
// 파일 경로에서 실제 파일 정보 조회
|
||||
const fileInfo = await this.getFileInfoByPath(filePath);
|
||||
if (fileInfo) {
|
||||
// 🎯 컴포넌트별 파일 정보 조회
|
||||
// 파일 경로에서 컴포넌트 ID 추출하거나 컬럼명 사용
|
||||
const componentId =
|
||||
this.extractComponentIdFromPath(filePath) || fileColumn;
|
||||
const fileInfos = await this.getFileInfoByColumnAndTarget(
|
||||
componentId,
|
||||
row.id || row.objid || row.seq, // 기본키 값
|
||||
tableName
|
||||
);
|
||||
|
||||
if (fileInfos && fileInfos.length > 0) {
|
||||
// 파일 정보를 JSON 형태로 저장
|
||||
const totalSize = fileInfos.reduce(
|
||||
(sum, file) => sum + (file.size || 0),
|
||||
0
|
||||
);
|
||||
enrichedRow[fileColumn] = JSON.stringify({
|
||||
files: [fileInfo],
|
||||
totalCount: 1,
|
||||
totalSize: fileInfo.size,
|
||||
files: fileInfos,
|
||||
totalCount: fileInfos.length,
|
||||
totalSize: totalSize,
|
||||
});
|
||||
} else {
|
||||
// 파일이 없으면 빈 상태로 설정
|
||||
enrichedRow[fileColumn] = JSON.stringify({
|
||||
files: [],
|
||||
totalCount: 0,
|
||||
totalSize: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -588,7 +713,70 @@ export class TableManagementService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 파일 경로로 파일 정보 조회
|
||||
* 파일 경로에서 컴포넌트 ID 추출 (현재는 사용하지 않음)
|
||||
*/
|
||||
private extractComponentIdFromPath(filePath: string): string | null {
|
||||
// 현재는 파일 경로에서 컴포넌트 ID를 추출할 수 없으므로 null 반환
|
||||
// 추후 필요시 구현
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 컬럼별 파일 정보 조회 (컬럼명과 target_objid로 구분)
|
||||
*/
|
||||
private async getFileInfoByColumnAndTarget(
|
||||
columnName: string,
|
||||
targetObjid: any,
|
||||
tableName: string
|
||||
): Promise<any[]> {
|
||||
try {
|
||||
logger.info(
|
||||
`컬럼별 파일 정보 조회: ${tableName}.${columnName}, target: ${targetObjid}`
|
||||
);
|
||||
|
||||
// 🎯 컬럼명을 doc_type으로 사용하여 파일 구분
|
||||
const fileInfos = await prisma.attach_file_info.findMany({
|
||||
where: {
|
||||
target_objid: String(targetObjid),
|
||||
doc_type: columnName, // 컬럼명으로 파일 구분
|
||||
status: "ACTIVE",
|
||||
},
|
||||
select: {
|
||||
objid: true,
|
||||
real_file_name: true,
|
||||
file_size: true,
|
||||
file_ext: true,
|
||||
file_path: true,
|
||||
doc_type: true,
|
||||
doc_type_name: true,
|
||||
regdate: true,
|
||||
writer: true,
|
||||
},
|
||||
orderBy: {
|
||||
regdate: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
// 파일 정보 포맷팅
|
||||
return fileInfos.map((fileInfo) => ({
|
||||
name: fileInfo.real_file_name,
|
||||
size: Number(fileInfo.file_size) || 0,
|
||||
path: fileInfo.file_path,
|
||||
ext: fileInfo.file_ext,
|
||||
objid: String(fileInfo.objid),
|
||||
docType: fileInfo.doc_type,
|
||||
docTypeName: fileInfo.doc_type_name,
|
||||
regdate: fileInfo.regdate?.toISOString(),
|
||||
writer: fileInfo.writer,
|
||||
}));
|
||||
} catch (error) {
|
||||
logger.warn(`컬럼별 파일 정보 조회 실패: ${columnName}`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 파일 경로로 파일 정보 조회 (기존 메서드 - 호환성 유지)
|
||||
*/
|
||||
private async getFileInfoByPath(filePath: string): Promise<any | null> {
|
||||
try {
|
||||
@@ -680,8 +868,9 @@ export class TableManagementService {
|
||||
|
||||
logger.info(`테이블 데이터 조회: ${tableName}`, options);
|
||||
|
||||
// 🎯 파일 타입 컬럼 감지
|
||||
const fileColumns = await this.getFileTypeColumns(tableName);
|
||||
// 🎯 파일 타입 컬럼 감지 (비활성화됨 - 자동 파일 컬럼 생성 방지)
|
||||
// const fileColumns = await this.getFileTypeColumns(tableName);
|
||||
const fileColumns: string[] = []; // 자동 파일 컬럼 생성 비활성화
|
||||
|
||||
// WHERE 조건 구성
|
||||
let whereConditions: string[] = [];
|
||||
|
||||
Reference in New Issue
Block a user