summaryrefslogtreecommitdiff
path: root/lib/knox-sync/common.ts
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-07-24 10:32:34 +0000
committerjoonhoekim <26rote@gmail.com>2025-07-24 10:32:34 +0000
commited0d6fcc98f671280c2ccde797b50693da88152e (patch)
tree6ea4bc8b13546fbd9de949a378fd2efb22c9cbdd /lib/knox-sync/common.ts
parenta50bc9baea332f996e6bc3a5d70c69f6d2d0f194 (diff)
(김준회) knox 임직원, 조직도, 직급 동기화 로직 수정 (delete & insert) 및 시간당 호출제한 반영, SSO 운영 변경 대응 (버튼 텍스트에 Stage 인 경우에만 Stage 표시), 부서 트리뷰에서 '하위' 뱃지 제거
Diffstat (limited to 'lib/knox-sync/common.ts')
-rw-r--r--lib/knox-sync/common.ts162
1 files changed, 162 insertions, 0 deletions
diff --git a/lib/knox-sync/common.ts b/lib/knox-sync/common.ts
new file mode 100644
index 00000000..0b027a4f
--- /dev/null
+++ b/lib/knox-sync/common.ts
@@ -0,0 +1,162 @@
+// Knox API 공통 설정 및 유틸리티 함수들
+
+// 동기화 대상 회사 코드 (쉼표로 구분된 ENV)
+export const KNOX_COMPANIES = (process.env.KNOX_COMPANY_CODES || 'D60')
+ .split(',')
+ .map((c) => c.trim())
+ .filter(Boolean);
+
+// API 호출 제한 설정 (환경변수로 제어 가능)
+export const HOURLY_API_LIMIT = parseInt(process.env.KNOX_API_HOURLY_LIMIT || '90');
+export const API_CALL_DELAY_MS = Math.max(
+ parseInt(process.env.KNOX_API_CALL_DELAY_MS || '0'),
+ Math.ceil(3600000 / HOURLY_API_LIMIT) // 시간당 제한에서 자동 계산 (3600초 = 1시간)
+);
+
+// Knox API 시간대 제한 강제 적용 여부 (테스트용)
+export const FORCE_TIME_LIMIT = process.env.KNOX_API_FORCE_LIMIT === 'true';
+
+// API 호출 시간 추적을 위한 인터페이스
+interface ApiCallTracker {
+ startTime: number;
+ currentHourCalls: number;
+ lastResetTime: number;
+}
+
+// 전역 API 호출 추적기
+let apiTracker: ApiCallTracker = {
+ startTime: Date.now(),
+ currentHourCalls: 0,
+ lastResetTime: Date.now()
+};
+
+// API 호출 제한을 위한 지연 함수 (시간당 제한 준수)
+export const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
+
+/**
+ * Knox API 시간대 제한 체크
+ * 주간 시간대(06:00-22:00)에 실행 시 경고 또는 중단
+ */
+export function checkTimeRestriction(): boolean {
+ const currentHour = new Date().getHours();
+ if (currentHour >= 6 && currentHour < 22) {
+ if (FORCE_TIME_LIMIT) {
+ console.error('[KNOX-SYNC] 🚨 주간 시간대(06:00-22:00) 대량 호출 금지 - 야간에 실행하세요');
+ console.log('[KNOX-SYNC] 💡 테스트용 실행을 원하면 KNOX_API_FORCE_LIMIT 환경변수를 제거하세요');
+ return false; // 실행 중단
+ } else {
+ console.warn('[KNOX-SYNC] ⚠️ 주간 시간대 대량 호출 - Knox Portal과 사전 협의 권장 (테스트 모드)');
+ return true; // 경고만 출력하고 계속 실행
+ }
+ }
+ return true; // 야간 시간대는 정상 실행
+}
+
+/**
+ * API 호출 제한 상태 체크 및 동적 delay 조절
+ * 실제 시간 기반으로 1시간마다 리셋, 강제 중단 없이 delay만 조절
+ */
+export function checkApiLimitStatus(newApiCalls: number = 1): {
+ shouldWarn: boolean;
+ recommendedDelay: number;
+ resetInfo?: string;
+} {
+ const now = Date.now();
+ const hoursPassed = (now - apiTracker.lastResetTime) / (1000 * 60 * 60);
+
+ // 1시간이 지났으면 카운터 리셋
+ if (hoursPassed >= 1.0) {
+ const resetInfo = `[KNOX-SYNC] 📊 API 제한 리셋 - 이전 시간: ${apiTracker.currentHourCalls}회 → 0회`;
+ apiTracker.currentHourCalls = 0;
+ apiTracker.lastResetTime = now;
+ console.log(resetInfo);
+ }
+
+ // 새로운 API 호출 추가
+ apiTracker.currentHourCalls += newApiCalls;
+
+ const shouldWarn = apiTracker.currentHourCalls >= HOURLY_API_LIMIT - 10;
+
+ // 제한 근접 시 delay 동적 조절
+ let recommendedDelay = API_CALL_DELAY_MS;
+
+ if (apiTracker.currentHourCalls >= HOURLY_API_LIMIT) {
+ // 제한 초과 시 더 긴 delay (남은 시간을 남은 호출로 분배)
+ const remainingTimeMs = (1000 * 60 * 60) - (now - apiTracker.lastResetTime);
+ const estimatedRemainingCalls = Math.max(10, HOURLY_API_LIMIT * 0.1); // 최소 10회는 남겨둠
+ recommendedDelay = Math.max(recommendedDelay, Math.ceil(remainingTimeMs / estimatedRemainingCalls));
+
+ console.warn(`[KNOX-SYNC] ⚠️ 시간당 제한(${HOURLY_API_LIMIT}회) 초과! delay를 ${recommendedDelay}ms로 증가`);
+ } else if (shouldWarn) {
+ // 제한 근접 시 적당한 delay 증가
+ recommendedDelay = Math.max(recommendedDelay, API_CALL_DELAY_MS * 2);
+ console.warn(`[KNOX-SYNC] ⚠️ API 호출 ${apiTracker.currentHourCalls}회 - 시간당 제한(${HOURLY_API_LIMIT}회) 근접! delay 증가`);
+ }
+
+ // 현재 상태 로그 (10회마다)
+ if (apiTracker.currentHourCalls % 10 === 0 && apiTracker.currentHourCalls > 0) {
+ const elapsed = Math.round((now - apiTracker.lastResetTime) / (1000 * 60));
+ console.log(`[KNOX-SYNC] 📊 현재 시간대 API 호출: ${apiTracker.currentHourCalls}/${HOURLY_API_LIMIT}회 (경과시간: ${elapsed}분)`);
+ }
+
+ return {
+ shouldWarn,
+ recommendedDelay,
+ resetInfo: hoursPassed >= 1.0 ? `리셋됨 (${Math.round(hoursPassed * 10) / 10}시간 경과)` : undefined
+ };
+}
+
+/**
+ * 동기화 시작 시 API 추적기 초기화
+ */
+export function initializeApiTracker(): void {
+ apiTracker = {
+ startTime: Date.now(),
+ currentHourCalls: 0,
+ lastResetTime: Date.now()
+ };
+ console.log('[KNOX-SYNC] 📊 API 호출 추적기 초기화 완료');
+}
+
+/**
+ * 현재 API 사용량 정보 반환
+ */
+export function getApiUsageInfo(): { currentCalls: number; limit: number; timeUntilReset: number } {
+ const now = Date.now();
+ const timeUntilReset = (1000 * 60 * 60) - (now - apiTracker.lastResetTime);
+
+ return {
+ currentCalls: apiTracker.currentHourCalls,
+ limit: HOURLY_API_LIMIT,
+ timeUntilReset: Math.max(0, timeUntilReset)
+ };
+}
+
+/**
+ * 동기화 시작 로그 출력
+ */
+export function logSyncStart(syncType: string): void {
+ console.log(`[KNOX-SYNC] Knox ${syncType} 동기화 시작`);
+ console.log(`[KNOX-SYNC] 대상 회사: ${KNOX_COMPANIES.join(', ')}`);
+}
+
+/**
+ * 동기화 완료 로그 출력
+ */
+export function logSyncComplete(syncType: string, totalApiCalls: number, startTime: number): void {
+ const duration = Math.round((Date.now() - startTime) / 1000);
+ const usage = getApiUsageInfo();
+ console.log(`[KNOX-SYNC] ${syncType} 동기화 완료 - 총 ${totalApiCalls}회 API 호출, ${duration}초 소요`);
+ console.log(`[KNOX-SYNC] 📊 현재 시간대 사용량: ${usage.currentCalls}/${usage.limit}회`);
+}
+
+/**
+ * 스케줄러 등록 로그 출력
+ */
+export function logSchedulerInfo(syncType: string, cronString: string): void {
+ console.log(`[KNOX-SYNC] ${syncType} 동기화 스케줄러 등록 (${cronString})`);
+ console.log(`[KNOX-SYNC] API 제한사항: 시간당 ${HOURLY_API_LIMIT}건 (실제 시간 기반 리셋)`);
+ console.log(`[KNOX-SYNC] 기본 호출 간격: ${API_CALL_DELAY_MS}ms (동적 조절)`);
+ console.log(`[KNOX-SYNC] Knox API 권장: 대량 호출 시 야간(22시 이후) 실행`);
+ console.log(`[KNOX-SYNC] 시간대 제한 강제 적용: ${FORCE_TIME_LIMIT ? '활성화' : '비활성화 (테스트 모드)'}`);
+} \ No newline at end of file