summaryrefslogtreecommitdiff
path: root/lib/soap/sender.ts
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-08-25 07:18:26 +0000
committerjoonhoekim <26rote@gmail.com>2025-08-25 07:18:26 +0000
commit4a10344c046a8746744c311804f46bd60c0d8bd8 (patch)
tree06c46c0e7c61c057b1308d68efd79e1d58513113 /lib/soap/sender.ts
parent4249d57849ee4e9a39fce41a7dd434e7ca0b35e9 (diff)
(김준회) ECC 인터페이스 관련 작업사항
Diffstat (limited to 'lib/soap/sender.ts')
-rw-r--r--lib/soap/sender.ts287
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);
+}