diff options
| author | joonhoekim <26rote@gmail.com> | 2025-08-25 07:18:26 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-08-25 07:18:26 +0000 |
| commit | 4a10344c046a8746744c311804f46bd60c0d8bd8 (patch) | |
| tree | 06c46c0e7c61c057b1308d68efd79e1d58513113 /lib/soap/sender.ts | |
| parent | 4249d57849ee4e9a39fce41a7dd434e7ca0b35e9 (diff) | |
(김준회) ECC 인터페이스 관련 작업사항
Diffstat (limited to 'lib/soap/sender.ts')
| -rw-r--r-- | lib/soap/sender.ts | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/lib/soap/sender.ts b/lib/soap/sender.ts new file mode 100644 index 00000000..580d0c5a --- /dev/null +++ b/lib/soap/sender.ts @@ -0,0 +1,287 @@ +'use server' + +import { withSoapLogging } from "@/lib/soap/utils"; +import { XMLBuilder } from 'fast-xml-parser'; + +// 기본 인증 정보 타입 +export interface SoapAuthConfig { + username?: string; + password?: string; +} + +// SOAP 전송 설정 타입 +export interface SoapSendConfig { + endpoint: string; + envelope: Record<string, any>; + soapAction?: string; + timeout?: number; + retryCount?: number; + retryDelay?: number; +} + +// 로깅 정보 타입 +export interface SoapLogInfo { + direction: 'INBOUND' | 'OUTBOUND'; + system: string; + interface: string; +} + +// 전송 결과 타입 +export interface SoapSendResult { + success: boolean; + message: string; + responseText?: string; + statusCode?: number; + headers?: Record<string, string>; +} + +// 기본 환경변수에서 인증 정보 가져오기 +function getDefaultAuth(): SoapAuthConfig { + return { + username: process.env.MDG_SOAP_USERNAME, + password: process.env.MDG_SOAP_PASSWORD + }; +} + +// 공통 XML 빌더 생성 +function createXmlBuilder() { + return new XMLBuilder({ + ignoreAttributes: false, + format: true, + attributeNamePrefix: '@_', + textNodeName: '#text', + suppressEmptyNode: true, + suppressUnpairedNode: false, + indentBy: ' ', + processEntities: false, + suppressBooleanAttributes: false, + cdataPropName: false, + tagValueProcessor: (name, val) => val, + attributeValueProcessor: (name, val) => val + }); +} + +// SOAP Envelope 생성 +function createSoapEnvelope( + namespace: string, + bodyContent: Record<string, any> +): Record<string, any> { + return { + 'soap:Envelope': { + '@_xmlns:soap': 'http://schemas.xmlsoap.org/soap/envelope/', + '@_xmlns:ns0': namespace, + 'soap:Body': bodyContent + } + }; +} + +// XML 생성 +export async function generateSoapXml( + envelope: Record<string, any>, + xmlDeclaration: string = '<?xml version="1.0" encoding="UTF-8"?>\n' +): Promise<string> { + const builder = createXmlBuilder(); + const xmlBody = builder.build(envelope); + return xmlDeclaration + xmlBody; +} + +// SOAP XML 전송 공통 함수 +export async function sendSoapXml( + config: SoapSendConfig, + logInfo: SoapLogInfo, + auth?: SoapAuthConfig +): Promise<SoapSendResult> { + try { + // 인증 정보 설정 (기본값 사용) + const authConfig = auth || getDefaultAuth(); + + // XML 생성 + const soapEnvelope = createSoapEnvelope( + 'http://shi.samsung.co.kr/P2_MD/MDZ', + config.envelope + ); + + const xmlData = await generateSoapXml(soapEnvelope); + + console.log('📤 SOAP XML 전송 시작'); + console.log('🔍 전송 XML (첫 500자):', xmlData.substring(0, 500)); + + const result = await withSoapLogging( + logInfo.direction, + logInfo.system, + logInfo.interface, + xmlData, + async () => { + // 헤더 설정 + const headers: Record<string, string> = { + 'Content-Type': 'text/xml; charset=utf-8', + 'SOAPAction': config.soapAction || 'http://sap.com/xi/WebService/soap1.1', + }; + + // Basic Authentication 헤더 추가 + if (authConfig.username && authConfig.password) { + const credentials = Buffer.from(`${authConfig.username}:${authConfig.password}`).toString('base64'); + headers['Authorization'] = `Basic ${credentials}`; + console.log('🔐 Basic Authentication 헤더 추가 완료'); + } else { + console.warn('⚠️ SOAP 인증 정보가 설정되지 않았습니다.'); + } + + // fetch 옵션 설정 + const fetchOptions: RequestInit = { + method: 'POST', + headers, + body: xmlData, + }; + + // 타임아웃 설정 + if (config.timeout) { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), config.timeout); + + try { + const response = await fetch(config.endpoint, { + ...fetchOptions, + signal: controller.signal + }); + clearTimeout(timeoutId); + return response; + } catch (error) { + clearTimeout(timeoutId); + if (error instanceof Error && error.name === 'AbortError') { + throw new Error(`요청 타임아웃 (${config.timeout}ms)`); + } + throw error; + } + } else { + return await fetch(config.endpoint, fetchOptions); + } + } + ); + + // 응답 처리 + const response = result as Response; + const responseText = await response.text(); + + console.log('📥 SOAP 응답 수신:', response.status, response.statusText); + console.log('🔍 응답 XML (첫 500자):', responseText.substring(0, 500)); + + // HTTP 상태 코드 확인 + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + // SOAP Fault 검사 + if (responseText.includes('soap:Fault') || responseText.includes('SOAP:Fault')) { + throw new Error(`SOAP Fault: ${responseText}`); + } + + // 응답 헤더 수집 + const responseHeaders: Record<string, string> = {}; + response.headers.forEach((value, key) => { + responseHeaders[key] = value; + }); + + return { + success: true, + message: '전송 성공', + responseText, + statusCode: response.status, + headers: responseHeaders + }; + + } catch (error) { + console.error('❌ SOAP XML 전송 실패:', error); + return { + success: false, + message: error instanceof Error ? error.message : 'Unknown error' + }; + } +} + +// 재시도 로직이 포함된 SOAP 전송 함수 +export async function sendSoapXmlWithRetry( + config: SoapSendConfig, + logInfo: SoapLogInfo, + auth?: SoapAuthConfig +): Promise<SoapSendResult> { + const maxRetries = config.retryCount || 3; + const retryDelay = config.retryDelay || 1000; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + console.log(`🔄 SOAP 전송 시도 ${attempt}/${maxRetries}`); + + const result = await sendSoapXml(config, logInfo, auth); + + if (result.success) { + return result; + } + + // 마지막 시도가 아니면 재시도 + if (attempt < maxRetries) { + console.log(`⏳ ${retryDelay}ms 후 재시도...`); + await new Promise(resolve => setTimeout(resolve, retryDelay)); + } + + } catch (error) { + console.error(`❌ SOAP 전송 시도 ${attempt} 실패:`, error); + + if (attempt === maxRetries) { + return { + success: false, + message: `최대 재시도 횟수 초과: ${error instanceof Error ? error.message : 'Unknown error'}` + }; + } + + // 마지막 시도가 아니면 재시도 + console.log(`⏳ ${retryDelay}ms 후 재시도...`); + await new Promise(resolve => setTimeout(resolve, retryDelay)); + } + } + + return { + success: false, + message: '알 수 없는 오류로 전송 실패' + }; +} + +// 간단한 SOAP 전송 함수 (기본 설정 사용) +export async function sendSimpleSoapXml( + endpoint: string, + bodyContent: Record<string, any>, + logInfo: SoapLogInfo, + options?: { + namespace?: string; + soapAction?: string; + auth?: SoapAuthConfig; + timeout?: number; + } +): Promise<SoapSendResult> { + const config: SoapSendConfig = { + endpoint, + envelope: bodyContent, + soapAction: options?.soapAction, + timeout: options?.timeout || 30000, // 기본 30초 + }; + + const auth = options?.auth || getDefaultAuth(); + + return await sendSoapXml(config, logInfo, auth); +} + +// MDG 전용 SOAP 전송 함수 (기존 action.ts와 호환) +export async function sendMdgSoapXml( + bodyContent: Record<string, any>, + logInfo: SoapLogInfo, + auth?: SoapAuthConfig +): Promise<SoapSendResult> { + const config: SoapSendConfig = { + endpoint: "http://shii8dvddb01.hec.serp.shi.samsung.net:50000/sap/xi/engine?type=entry&version=3.0&Sender.Service=P2038_Q&Interface=http%3A%2F%2Fshi.samsung.co.kr%2FP2_MD%2FMDZ%5EP2MD3007_AO&QualityOfService=ExactlyOnce", + envelope: bodyContent, + soapAction: 'http://sap.com/xi/WebService/soap1.1', + timeout: 60000, // MDG는 60초 타임아웃 + }; + + return await sendSoapXml(config, logInfo, auth); +} |
