1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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 ? '활성화' : '비활성화 (테스트 모드)'}`);
}
|