summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-09-25 12:19:07 +0900
committerjoonhoekim <26rote@gmail.com>2025-09-25 12:19:07 +0900
commitf69e125f1a0b47bbc22e2784208bf829bcdd24f8 (patch)
treead2591f69b2d85e0531f940d553d3fca6091303e
parent450234437267cdd9cdf196d5d37657708062bef5 (diff)
(김준회) nonsap sync 배치 처리(크론잡 임시 주석처리), 메뉴 스크롤 처리, 벤더풀 임포트 복호화 처리,
-rw-r--r--components/layout/Header.tsx8
-rw-r--r--instrumentation.ts36
-rw-r--r--lib/nonsap-sync/enhanced-sync-service.ts77
-rw-r--r--lib/vendor-pool/table/vendor-pool-excel-import-button.tsx22
4 files changed, 108 insertions, 35 deletions
diff --git a/components/layout/Header.tsx b/components/layout/Header.tsx
index 70e26212..a080d394 100644
--- a/components/layout/Header.tsx
+++ b/components/layout/Header.tsx
@@ -185,16 +185,16 @@ export function Header() {
{/* 그룹핑이 필요한 메뉴의 경우 GroupedMenuRenderer 사용 */}
{section.useGrouping ? (
- <NavigationMenuContent>
- <GroupedMenuRenderer
- items={section.items}
+ <NavigationMenuContent className="max-h-[80vh] overflow-y-auto">
+ <GroupedMenuRenderer
+ items={section.items}
lng={lng}
activeMenus={activeMenus}
t={t}
/>
</NavigationMenuContent>
) : (
- <NavigationMenuContent>
+ <NavigationMenuContent className="max-h-[70vh] overflow-y-auto">
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
{section.items.map((item) => (
<ListItem
diff --git a/instrumentation.ts b/instrumentation.ts
index 5e5b827d..b6676b67 100644
--- a/instrumentation.ts
+++ b/instrumentation.ts
@@ -1,3 +1,5 @@
+import { triggerSync } from './lib/nonsap-sync/enhanced-sync-service';
+
/**
* instrumentation 진입점 - 여러 JOB들이 등록될 수 있으므로 확장성을 위해 최소한의 메서드 호출만으로 유지
*/
@@ -6,22 +8,28 @@ export async function register() {
// Node.js runtime에서만 동기화 스케줄러 시작
if (process.env.NEXT_RUNTIME === 'nodejs') {
- try {
- // NONSAP 데이터 동기화 스케줄러 시작 (CMCTB_CDNM, CMCTB_CD)
- const { triggerSync } = await import('./lib/nonsap-sync/enhanced-sync-service');
- const cron = (await import('node-cron')).default;
+ // try {
+ // // NONSAP 데이터 동기화 스케줄러 시작 (CMCTB_CDNM, CMCTB_CD)
+ // const { triggerSync } = await import('./lib/nonsap-sync/enhanced-sync-service');
+ // const cron = (await import('node-cron')).default;
- cron.schedule('0 1 * * *', async () => {
- try {
- await triggerSync();
- } catch (error) {
- console.error('NONSAP data sync failed:', error);
- }
- }, { timezone: 'Asia/Seoul' });
+ // cron.schedule('0 1 * * *', async () => {
+ // try {
+ // await triggerSync();
+ // } catch (error) {
+ // console.error('NONSAP data sync failed:', error);
+ // }
+ // }, { timezone: 'Asia/Seoul' });
- } catch {
- console.error('Failed to start NONSAP data sync scheduler.');
- }
+ // } catch {
+ // console.error('Failed to start NONSAP data sync scheduler.');
+ // }
+
+ // try{
+ // triggerSync();
+ // } catch {
+ // console.error('Failed to trigger NONSAP data sync.');
+ // }
try {
// Knox 통합 동기화 스케줄러 시작 (직급 → 조직 → 임직원 순차 실행) --- 임직원은 안씀
diff --git a/lib/nonsap-sync/enhanced-sync-service.ts b/lib/nonsap-sync/enhanced-sync-service.ts
index 5c07cafc..5d45d3ad 100644
--- a/lib/nonsap-sync/enhanced-sync-service.ts
+++ b/lib/nonsap-sync/enhanced-sync-service.ts
@@ -10,6 +10,10 @@ import * as nonsapSchema from '@/db/schema/NONSAP/nonsap';
// 동기화할 테이블 목록 (단순화)
const TARGET_TABLES: TableName[] = ['CMCTB_CDNM', 'CMCTB_CD'];
+// 배치 처리 설정
+const BATCH_SIZE = 1000; // 한 번에 처리할 레코드 수
+const BATCH_DELAY = 100; // 배치 간 대기 시간 (ms)
+
// 간단한 로거
const logger = {
info: (message: string, ...args: unknown[]) => console.log(`[NONSAP-SYNC] ${message}`, ...args),
@@ -19,19 +23,51 @@ const logger = {
};
/**
- * Oracle에서 테이블 데이터 조회 (단순화)
+ * Oracle에서 테이블 데이터 조회 (배치 처리)
*/
async function fetchOracleData<T extends TableName>(tableName: T): Promise<DatabaseSchema[T][]> {
try {
- const query = `SELECT * FROM ${tableName}`;
- const result = await oracleKnex.raw(query);
-
- // Oracle knex raw 결과에서 실제 데이터 추출
- const rows = Array.isArray(result) ? result : result.rows || [];
- const cleanResults = rows.map((row: any) => row);
+ // 먼저 총 레코드 수 확인
+ const countQuery = `SELECT COUNT(*) as total FROM ${tableName}`;
+ const countResult = await oracleKnex.raw(countQuery);
+ const totalCount = countResult[0]?.TOTAL || 0;
+
+ logger.info(`Total records in ${tableName}: ${totalCount}`);
+
+ if (totalCount === 0) {
+ return [];
+ }
+
+ // 배치로 데이터 조회
+ const allResults: DatabaseSchema[T][] = [];
+ const totalBatches = Math.ceil(totalCount / BATCH_SIZE);
+
+ for (let i = 0; i < totalBatches; i++) {
+ const offset = i * BATCH_SIZE;
+ const query = `
+ SELECT * FROM (
+ SELECT a.*, ROWNUM rnum FROM (
+ SELECT * FROM ${tableName}
+ ) a WHERE ROWNUM <= ${offset + BATCH_SIZE}
+ ) WHERE rnum > ${offset}
+ `;
+
+ const result = await oracleKnex.raw(query);
+ const rows = Array.isArray(result) ? result : result.rows || [];
+ const cleanResults = rows.map((row: any) => row);
+
+ allResults.push(...cleanResults);
+
+ logger.info(`Fetched batch ${i + 1}/${totalBatches} (${cleanResults.length} records) from ${tableName}`);
+
+ // 배치 간 짧은 대기 (메모리 해제 시간)
+ if (i < totalBatches - 1) {
+ await new Promise(resolve => setTimeout(resolve, BATCH_DELAY));
+ }
+ }
- logger.info(`Fetched ${cleanResults.length} records from ${tableName}`);
- return cleanResults as DatabaseSchema[T][];
+ logger.info(`Total fetched ${allResults.length} records from ${tableName}`);
+ return allResults as DatabaseSchema[T][];
} catch (error) {
logger.error(`Error fetching data from ${tableName}:`, error);
throw error;
@@ -63,7 +99,7 @@ function normalizeRecord(record: any, tableSchema: any): any {
}
/**
- * PostgreSQL에 데이터 삽입 (삭제 후 재삽입)
+ * PostgreSQL에 데이터 삽입 (배치 처리)
*/
async function syncToPostgres<T extends TableName>(
tableName: T,
@@ -102,9 +138,24 @@ async function syncToPostgres<T extends TableName>(
DELETE FROM ${sql.identifier('nonsap')}.${sql.identifier(tableNameLower)}
`);
- // 2. 새 데이터 모두 삽입
- logger.info(`${tableName} - Inserting ${cleanData.length} new records`);
- await db.insert(tableSchema as any).values(cleanData);
+ // 2. 배치로 새 데이터 삽입
+ const totalBatches = Math.ceil(cleanData.length / BATCH_SIZE);
+ logger.info(`${tableName} - Inserting ${cleanData.length} new records in ${totalBatches} batches`);
+
+ for (let i = 0; i < totalBatches; i++) {
+ const start = i * BATCH_SIZE;
+ const end = Math.min(start + BATCH_SIZE, cleanData.length);
+ const batch = cleanData.slice(start, end);
+
+ await db.insert(tableSchema as any).values(batch);
+
+ logger.info(`${tableName} - Inserted batch ${i + 1}/${totalBatches} (${batch.length} records)`);
+
+ // 배치 간 짧은 대기 (메모리 해제 시간)
+ if (i < totalBatches - 1) {
+ await new Promise(resolve => setTimeout(resolve, BATCH_DELAY));
+ }
+ }
logger.success(`Successfully synced ${cleanData.length} records for ${tableName}`);
} catch (error) {
diff --git a/lib/vendor-pool/table/vendor-pool-excel-import-button.tsx b/lib/vendor-pool/table/vendor-pool-excel-import-button.tsx
index 704d4aff..0f51170f 100644
--- a/lib/vendor-pool/table/vendor-pool-excel-import-button.tsx
+++ b/lib/vendor-pool/table/vendor-pool-excel-import-button.tsx
@@ -18,6 +18,7 @@ import {
getAccessorKeyByHeader,
vendorPoolExcelColumns
} from '../excel-utils'
+import { decryptWithServerAction } from '@/components/drm/drmUtils'
interface ImportExcelProps {
onSuccess?: () => void
@@ -26,7 +27,7 @@ interface ImportExcelProps {
export function ImportVendorPoolButton({ onSuccess }: ImportExcelProps) {
const fileInputRef = useRef<HTMLInputElement>(null)
const [isImporting, setIsImporting] = React.useState(false)
- const { data: session, status } = useSession()
+ const { data: session } = useSession()
// 헬퍼 함수들은 excel-utils에서 import
@@ -37,10 +38,23 @@ export function ImportVendorPoolButton({ onSuccess }: ImportExcelProps) {
setIsImporting(true)
try {
- // Read the Excel file using ExcelJS
- const data = await file.arrayBuffer()
+ // DRM 복호화 처리
+ toast.info("파일을 복호화하고 있습니다...")
+ let decryptedData: ArrayBuffer
+
+ try {
+ decryptedData = await decryptWithServerAction(file)
+ toast.success("파일 복호화가 완료되었습니다.")
+ } catch (drmError) {
+ console.warn("DRM 복호화 실패, 원본 파일로 진행합니다:", drmError)
+ toast.warning("DRM 복호화에 실패했습니다. 원본 파일로 진행합니다.")
+ decryptedData = await file.arrayBuffer()
+ }
+
+ // 복호화된 데이터로 ExcelJS 워크북 로드
+ toast.info("엑셀 파일을 분석하고 있습니다...")
const workbook = new ExcelJS.Workbook()
- await workbook.xlsx.load(data)
+ await workbook.xlsx.load(decryptedData)
// Get the first worksheet
const worksheet = workbook.getWorksheet(1)