diff options
| author | joonhoekim <26rote@gmail.com> | 2025-11-12 11:45:59 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-11-12 11:45:59 +0900 |
| commit | ddd644eb2c5ef47ef32e299c647e781f9f8b3a0a (patch) | |
| tree | 67a9d2c6d58bc646097c5fece0be8e841ce78dee /lib | |
| parent | bbb13a1ea700f3be7ac12459b63804b843e93dc4 (diff) | |
(김준회) S-EDP 429 Error Fix: 토큰 요청에 캐싱 기능 추가 (5분)
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sedp/sedp-token.ts | 125 |
1 files changed, 85 insertions, 40 deletions
diff --git a/lib/sedp/sedp-token.ts b/lib/sedp/sedp-token.ts index 0aa3b185..bd9451c1 100644 --- a/lib/sedp/sedp-token.ts +++ b/lib/sedp/sedp-token.ts @@ -3,64 +3,109 @@ const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.sh const SEDP_API_USER_ID = process.env.SEDP_API_USER_ID || 'EVCPUSER'; const SEDP_API_PASSWORD = process.env.SEDP_API_PASSWORD || 'evcpusr@2025'; +// 토큰 캐시 (1시간 동안 유효) +let cachedToken: string | null = null; +let tokenExpiresAt: number | null = null; +const TOKEN_VALIDITY_MS = 5 * 60 * 1000; // 5분 (밀리초) + +/** + * SEDP API에서 인증 토큰을 요청합니다. + * 내부 헬퍼 함수로, 실제 API 호출을 수행합니다. + */ +async function requestNewToken(): Promise<string> { + const response = await fetch( + `${SEDP_API_BASE_URL}/Security/RequestTokenWithMembership`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'accept': '*/*' + }, + body: JSON.stringify({ + UserID: SEDP_API_USER_ID, + Password: SEDP_API_PASSWORD + }) + } + ); + + if (!response.ok) { + throw new Error(`SEDP 토큰 요청 실패: ${response.status} ${response.statusText}`); + } + + // 응답이 직접 토큰 문자열인 경우 + const tokenData = await response.text(); + + // 응답이 JSON 형식이면 파싱 + try { + const jsonData = JSON.parse(tokenData); + if (typeof jsonData === 'string') { + return jsonData; // JSON 문자열이지만 내용물이 토큰 문자열인 경우 + } else if (jsonData.Token) { + return jsonData.Token; // { Token: "..." } 형태인 경우 + } else { + console.warn('예상치 못한 토큰 응답 형식:', jsonData); + // 가장 가능성 있는 필드를 찾아봄 + for (const key of ['token', 'accessToken', 'access_token', 'Token', 'jwt']) { + if (jsonData[key]) return jsonData[key]; + } + // 그래도 없으면 문자열로 변환 + return JSON.stringify(jsonData); + } + } catch { + // 파싱 실패 = 응답이 JSON이 아닌 순수 토큰 문자열 + return tokenData.trim(); + } +} + /** * SEDP API에서 인증 토큰을 가져옵니다. - * 매 호출 시마다 새로운 토큰을 발급받습니다. + * 토큰을 1시간 동안 캐싱하여 재사용합니다. + * 캐시된 토큰이 없거나 만료된 경우에만 새로운 토큰을 요청합니다. */ export async function getSEDPToken(): Promise<string> { try { - const response = await fetch( - `${SEDP_API_BASE_URL}/Security/RequestTokenWithMembership`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'accept': '*/*' - }, - body: JSON.stringify({ - UserID: SEDP_API_USER_ID, - Password: SEDP_API_PASSWORD - }) - } - ); + // 현재 시간 + const now = Date.now(); - if (!response.ok) { - throw new Error(`SEDP 토큰 요청 실패: ${response.status} ${response.statusText}`); + // 캐시된 토큰이 있고 아직 유효한 경우 + if (cachedToken && tokenExpiresAt && now < tokenExpiresAt) { + console.log(`SEDP 토큰 캐시 사용 (만료까지 ${Math.round((tokenExpiresAt - now) / 1000 / 60)}분 남음)`); + return cachedToken; } - // 응답이 직접 토큰 문자열인 경우 - const tokenData = await response.text(); + // 새로운 토큰 요청 + console.log('SEDP 새 토큰 요청 중...'); + const token = await requestNewToken(); - // 응답이 JSON 형식이면 파싱 - try { - const jsonData = JSON.parse(tokenData); - if (typeof jsonData === 'string') { - return jsonData; // JSON 문자열이지만 내용물이 토큰 문자열인 경우 - } else if (jsonData.Token) { - return jsonData.Token; // { Token: "..." } 형태인 경우 - } else { - console.warn('예상치 못한 토큰 응답 형식:', jsonData); - // 가장 가능성 있는 필드를 찾아봄 - for (const key of ['token', 'accessToken', 'access_token', 'Token', 'jwt']) { - if (jsonData[key]) return jsonData[key]; - } - // 그래도 없으면 문자열로 변환 - return JSON.stringify(jsonData); - } - } catch (e) { - // 파싱 실패 = 응답이 JSON이 아닌 순수 토큰 문자열 - return tokenData.trim(); - } + // 캐시에 저장 + cachedToken = token; + tokenExpiresAt = now + TOKEN_VALIDITY_MS; + + console.log('SEDP 토큰 발급 완료 (1시간 유효)'); + return token; } catch (error) { console.error('SEDP 토큰 가져오기 실패:', error); + // 오류 발생 시 캐시 초기화 + cachedToken = null; + tokenExpiresAt = null; throw error; } } /** + * 캐시된 토큰을 강제로 무효화합니다. + * 토큰이 만료되었거나 인증 오류가 발생한 경우 호출할 수 있습니다. + */ +export function invalidateSEDPToken(): void { + console.log('SEDP 토큰 캐시 무효화'); + cachedToken = null; + tokenExpiresAt = null; +} + +/** * SEDP API에 인증된 요청을 보냅니다. */ -export async function fetchSEDP(endpoint: string, options: RequestInit = {}): Promise<any> { +export async function fetchSEDP(endpoint: string, options: RequestInit = {}): Promise<unknown> { try { // 토큰 가져오기 const token = await getSEDPToken(); |
