From c19c12df70d204e18b307fba8599fe674d0a0a59 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Tue, 11 Aug 2026 16:56:33 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9D=91=EB=8B=B5=EC=9C=A0=ED=98=95=EB=B3=84?= =?UTF-8?q?=20=EC=84=9C=EB=B2=84=20=EC=8B=A4=EC=A3=BC=EC=86=8C=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EA=B0=9C=EC=84=A0=20-=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=EB=B3=B8=EC=97=90=20sentinel=20=EC=9C=A0=EC=A7=80=20=EB=B0=8F?= =?UTF-8?q?=20=ED=8E=B8=EC=A7=91=EA=B8=B0=20=EC=82=AC=EC=9A=A9=20=EC=8B=9C?= =?UTF-8?q?=20=EC=8B=A4=EC=A3=BC=EC=86=8C=EB=A1=9C=20=EC=B9=98=ED=99=98=20?= =?UTF-8?q?-=20=EC=9D=91=EB=8B=B5=EC=9C=A0=ED=98=95=EB=B3=84=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EC=8B=A4=EC=A3=BC=EC=86=8C=20=EA=B3=84=EC=82=B0=20?= =?UTF-8?q?=EC=9C=A0=ED=8B=B8=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebContent/js/djb/apispec/app.js | 62 +++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/WebContent/js/djb/apispec/app.js b/WebContent/js/djb/apispec/app.js index 543ce4c..7a62ceb 100644 --- a/WebContent/js/djb/apispec/app.js +++ b/WebContent/js/djb/apispec/app.js @@ -29,6 +29,10 @@ // (enricher 는 oauth·api_key 를 모두 apiKey-in-header 로 모델링하므로 scheme.type 만으로 구분 불가) var djbAuthType = ''; + // 저장 스펙의 서버 자리표시자. 포탈이 제공 시점에 응답유형(sample/mock/gw)별 실주소로 치환한다. + // (포탈 DjbTestbedSpecServerRewriter.SERVER_SENTINEL 과 동일 문자열 — 양쪽을 같이 바꿔야 한다) + const SERVER_SENTINEL = 'http://swagger-server-url'; + // ============================================================ // 2. 유틸 // ============================================================ @@ -108,7 +112,13 @@ // ============================================================ // 3. OpenAPI 스펙 빌더 (state.data → OpenAPI 3.1 객체) // ============================================================ - function buildOpenApiSpec() { + /** + * state.data → OpenAPI 3.1 객체. + * + * @param resolveHost true 면 응답유형별 실주소를 서버에 부착(편집기 미리보기·다운로드용), + * false/생략이면 서버를 sentinel 로 남긴다(저장본 — 포탈이 제공 시점에 치환). + */ + function buildOpenApiSpec(resolveHost) { const d = state.data; syncResponseExamplesFromSchema(); // 응답 예제 = 응답 정보(스키마·예제값) 파생 — 저장/미리보기 일관 const spec = { @@ -195,21 +205,19 @@ // - gw : djb.gateway.base-url (PortalProperty) → [gw호스트 + 어댑터경로 + 경로] // - mock : Mock Server URL(전체 URL) → [mock URL 그대로] (어댑터/오퍼레이션 경로 미부착) // - 그 외 : 호스트 없음 → [어댑터경로 + 경로] - var _rt = (d.portal && d.portal.responseType) || 'sample'; - var _host; - var _pathKey = op.path.value; - if (_rt === 'gw') { - _host = (window.DJB_CTX && window.DJB_CTX.gwAddress) || 'http://127.0.0.1:39310'; - } else if (_rt === 'mock') { - // Mock 은 전체 호출 URL. origin=서버, pathname(+query)=경로로 분해해 경로가 뒤에 덧붙지 않도록 한다. - var _mk = splitFullUrl((d.portal && d.portal.mockUrl) || ''); - _host = _mk.origin; - _pathKey = _mk.path; + // + // 단 이 실주소 부착은 "미리보기/다운로드" 표현에만 적용한다(resolveHost=true). + // 저장본은 서버를 sentinel 로 남겨 환경 독립을 유지하고, 포탈이 제공 시점에 응답유형별 실주소로 + // 치환한다(DjbTestbedSpecServerRewriter). 저장본에 실주소를 굳히면 포탈의 재치환이 무력화되고 + // 저장 환경(dev)의 GW 주소가 다른 환경까지 따라간다. + if (resolveHost) { + var _resolved = resolveSpecServer(); + if (_resolved.host) spec.servers = [{ url: _resolved.host }]; + spec.paths[_resolved.path] = { [op.method.value.toLowerCase()]: operation }; } else { - _host = ''; + spec.servers = [{ url: SERVER_SENTINEL }]; + spec.paths[op.path.value] = { [op.method.value.toLowerCase()]: operation }; } - if (_host) spec.servers = [{ url: _host }]; - spec.paths[_pathKey] = { [op.method.value.toLowerCase()]: operation }; // 보안 스킴 d.securitySchemes.forEach(s => { @@ -981,6 +989,24 @@ } } + // 응답유형별 실주소 { host, path }. 편집기 미리보기·다운로드에서만 사용(저장본은 sentinel). + // - gw : DJB_CTX.gwAddress + [어댑터경로 + 경로] + // - mock : Mock Server URL 을 origin/path 로 분해 → 경로가 뒤에 덧붙지 않음 + // - 그 외 : 호스트 없음 + [어댑터경로 + 경로] + function resolveSpecServer() { + var d = state.data; + var rt = (d.portal && d.portal.responseType) || 'sample'; + var path = d.operation.path.value; + if (rt === 'gw') { + return { host: (window.DJB_CTX && window.DJB_CTX.gwAddress) || 'http://127.0.0.1:39310', path: path }; + } + if (rt === 'mock') { + var mk = splitFullUrl((d.portal && d.portal.mockUrl) || ''); + return { host: mk.origin, path: mk.path }; + } + return { host: '', path: path }; + } + // 전체 URL → { origin, path }. Mock Server URL 을 서버+경로로 분해(경로를 별도로 덧붙이지 않기 위함). function splitFullUrl(u) { u = (u || '').trim(); @@ -1103,7 +1129,8 @@ } function mountSwagger() { - const spec = buildOpenApiSpec(); + // 미리보기는 실주소로 — sentinel 이 가짜 주소로 보이면 안 된다. + const spec = buildOpenApiSpec(true); swaggerInstance = SwaggerUIBundle({ spec: spec, domNode: $('#preview-swagger'), @@ -1121,7 +1148,7 @@ function updateMonacoFromState() { if (!monacoReady) return; - const spec = buildOpenApiSpec(); + const spec = buildOpenApiSpec(true); // YAML 미리보기도 실주소 표시 const yaml = jsyaml.dump(spec, { noRefs: true, lineWidth: 120 }); if (monacoEditor.getValue() !== yaml) monacoEditor.setValue(yaml); } @@ -1620,7 +1647,7 @@ // 16. Export // ============================================================ function downloadSpec(format) { - const spec = buildOpenApiSpec(); + const spec = buildOpenApiSpec(true); // 편집기 다운로드는 실주소 산출물 if (format === 'yaml') { doDownload(jsyaml.dump(spec, { noRefs: true }), 'text/yaml', 'yaml'); return; } if (format === 'json') { doDownload(JSON.stringify(spec, null, 2), 'application/json', 'json'); return; } // html — 폐쇄망 자립: 벤더 swagger css/js 를 fetch 해 인라인 @@ -2023,6 +2050,7 @@ function saveSpec(temp) { var ctx = window.DJB_CTX || {}; if (!ctx.saveUrl || !ctx.eaiSvcName) { toast('저장 대상이 없습니다', 'error'); return; } + // 저장본은 sentinel 유지 — 실주소는 포탈이 제공 시점에 응답유형별로 치환한다. var spec = buildOpenApiSpec(); var d = state.data; var reqEx = (d.examples && d.examples.request && d.examples.request[d.requestBody && d.requestBody.mediaType || 'application/json'] || {}).default || '';