diff options
| author | joonhoekim <26rote@gmail.com> | 2025-07-22 03:45:58 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-07-22 03:45:58 +0000 |
| commit | e1b1b57b6bfcd18ba4daa44230e8a915b4e93a15 (patch) | |
| tree | 434ead1baf9aba787316f7cf129e7a447e9c98e7 /lib/knox-sync/employee-sync-service.ts | |
| parent | cb34c5e1a61a20c954e12a8219d82dbdfbe50e13 (diff) | |
(김준회) knox 동기화 로직 개선
Diffstat (limited to 'lib/knox-sync/employee-sync-service.ts')
| -rw-r--r-- | lib/knox-sync/employee-sync-service.ts | 130 |
1 files changed, 90 insertions, 40 deletions
diff --git a/lib/knox-sync/employee-sync-service.ts b/lib/knox-sync/employee-sync-service.ts index c517be14..e9d422c7 100644 --- a/lib/knox-sync/employee-sync-service.ts +++ b/lib/knox-sync/employee-sync-service.ts @@ -5,13 +5,12 @@ import db from '@/db/db'; import { employee as employeeTable } from '@/db/schema/knox/employee'; import { searchEmployees, - getDepartmentsByCompany, Employee, } from '@/lib/knox-api/employee/employee'; import { sql } from 'drizzle-orm'; // 동기화 대상 회사 코드 (쉼표로 구분된 ENV) -const COMPANIES = (process.env.KNOX_COMPANY_CODES || 'P2') +const COMPANIES = (process.env.KNOX_COMPANY_CODES || 'D60') .split(',') .map((c) => c.trim()) .filter(Boolean); @@ -20,6 +19,17 @@ const CRON_STRING = process.env.KNOX_EMPLOYEE_SYNC_CRON || '0 4 * * *'; const DO_FIRST_RUN = process.env.KNOX_EMPLOYEE_SYNC_FIRST_RUN === 'true'; +// API 호출 제한 설정 (환경변수로 제어 가능) +const DAILY_API_LIMIT = parseInt(process.env.KNOX_API_DAILY_LIMIT || '100'); +const RATE_LIMIT_PER_MINUTE = parseInt(process.env.KNOX_API_RATE_LIMIT || '1000'); +const API_CALL_DELAY_MS = Math.max( + parseInt(process.env.KNOX_API_CALL_DELAY_MS || '100'), + Math.ceil(60000 / RATE_LIMIT_PER_MINUTE) // 분당 제한에서 자동 계산 +); + +// API 호출 제한을 위한 지연 함수 (분당 1000건 제한 준수) +const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + async function upsertEmployees(employees: Employee[]) { if (!employees.length) return; @@ -90,50 +100,88 @@ async function upsertEmployees(employees: Employee[]) { }); } +/** + * 회사별 임직원 동기화 - 최적화된 버전 + * 부서별 개별 호출 대신 회사 코드만으로 전체 조회하여 API 호출 수 최소화 + */ +async function syncEmployeesByCompany(companyCode: string): Promise<number> { + let totalApiCalls = 0; + let totalEmployees = 0; + + try { + let page = 1; + let totalPage = 1; + + console.log(`[KNOX-SYNC] ${companyCode}: 임직원 동기화 시작`); + + do { + // Rate limiting을 위한 지연 + if (totalApiCalls > 0) { + await delay(API_CALL_DELAY_MS); + } + + const resp = await searchEmployees({ + companyCode, + page: String(page), + resultType: 'basic', + }); + totalApiCalls++; + + if (resp.result === 'success') { + await upsertEmployees(resp.employees); + totalPage = resp.totalPage; + totalEmployees += resp.employees.length; + + console.log( + `[KNOX-SYNC] ${companyCode}: ${page}/${totalPage} 페이지 처리 완료 (${resp.employees.length}명, 누적: ${totalEmployees}명)` + ); + } else { + console.warn( + `[KNOX-SYNC] ${companyCode}: 페이지 ${page} 조회 실패` + ); + break; + } + + page += 1; + } while (page <= totalPage); + + console.log(`[KNOX-SYNC] ${companyCode}: 완료 - ${totalEmployees}명, API 호출 ${totalApiCalls}회`); + } catch (err) { + console.error( + `[KNOX-SYNC] ${companyCode}: 동기화 오류 (API 호출 ${totalApiCalls}회)`, + err + ); + } + + return totalApiCalls; +} + export async function syncKnoxEmployees(): Promise<void> { - console.log('[KNOX-SYNC] 임직원 동기화 시작'); + console.log('[KNOX-SYNC] Knox 임직원 동기화 시작'); + console.log(`[KNOX-SYNC] 대상 회사: ${COMPANIES.join(', ')}`); + + let totalApiCalls = 0; + const startTime = Date.now(); + // 각 회사별 순차 처리로 API 호출 제한 준수 for (const companyCode of COMPANIES) { - try { - const departments = await getDepartmentsByCompany(companyCode); - console.log(`[KNOX-SYNC] ${companyCode}: 부서 ${departments.length}개`); - - for (const dept of departments) { - let page = 1; - let totalPage = 1; - do { - const resp = await searchEmployees({ - companyCode, - departmentCode: dept.departmentCode, - page: String(page), - resultType: 'basic', - }); - - if (resp.result === 'success') { - await upsertEmployees(resp.employees); - totalPage = resp.totalPage; - console.log( - `[KNOX-SYNC] 임직원 동기화 ${companyCode}/${dept.departmentCode} ${page}/${totalPage} 페이지 처리` - ); - } else { - console.warn( - `[KNOX-SYNC] 임직원 동기화 실패: ${companyCode}/${dept.departmentCode} page ${page}` - ); - break; - } - - page += 1; - } while (page <= totalPage); - } - } catch (err) { - console.error( - `[KNOX-SYNC] 임직원 동기화 오류 (company ${companyCode})`, - err - ); + const apiCalls = await syncEmployeesByCompany(companyCode); + totalApiCalls += apiCalls; + + // 일일 호출 제한(100건) 근접 시 경고 + if (totalApiCalls >= DAILY_API_LIMIT - 10) { // 일일 제한에 가까워지면 경고 + console.warn(`[KNOX-SYNC] ⚠️ API 호출 ${totalApiCalls}회 - 일일 제한(${DAILY_API_LIMIT}건) 근접!`); + } + + // 일일 제한 초과 방지 + if (totalApiCalls >= DAILY_API_LIMIT) { + console.error(`[KNOX-SYNC] 🚨 일일 API 호출 제한(${DAILY_API_LIMIT}건) 초과로 중단`); + break; } } - console.log('[KNOX-SYNC] 임직원 동기화 완료'); + const duration = Math.round((Date.now() - startTime) / 1000); + console.log(`[KNOX-SYNC] 임직원 동기화 완료 - 총 ${totalApiCalls}회 API 호출, ${duration}초 소요`); } export async function startKnoxEmployeeSyncScheduler() { @@ -148,4 +196,6 @@ export async function startKnoxEmployeeSyncScheduler() { }); console.log(`[KNOX-SYNC] 임직원 동기화 스케줄러 등록 (${CRON_STRING})`); + console.log(`[KNOX-SYNC] API 제한사항: 분당 ${RATE_LIMIT_PER_MINUTE}건, 일일 ${DAILY_API_LIMIT}건`); + console.log(`[KNOX-SYNC] 호출 간격: ${API_CALL_DELAY_MS}ms`); } |
