From 8ccf52fd662d8021a62970d2885ca9fad5fbf130 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Mon, 13 Jul 2026 15:30:08 +0900 Subject: [PATCH] =?UTF-8?q?-=20JSP:=20OpenAPI=20Spec=20=EB=AA=A8=EB=8B=AC?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC=20=EA=B0=9C=EC=84=A0=20-=20=EB=8F=99?= =?UTF-8?q?=EC=A0=81=20=ED=81=AC=EA=B8=B0=20=EC=A1=B0=EC=A0=88=20=EB=B0=8F?= =?UTF-8?q?=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=B6=94=EA=B0=80=20-=20Servi?= =?UTF-8?q?ce:=20GET/DELETE=20=EC=9A=94=EC=B2=AD=20=EB=B0=94=EB=94=94=20?= =?UTF-8?q?=ED=95=AD=EB=AA=A9=E2=86=92=EC=BF=BC=EB=A6=AC=20=ED=8C=8C?= =?UTF-8?q?=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EB=A7=A4=ED=95=91=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80=20-=20JS:=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20=EC=A7=84=EC=9E=85=20=EC=8B=9C=20?= =?UTF-8?q?=ED=83=AD=20=EC=9E=90=EB=8F=99=20=EC=A0=84=ED=99=98=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81(firstReqTabWithData)=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebContent/js/djb/apispec/app.js | 15 +- WebContent/js/djb/apispec/sample-data.js | 301 ++---------------- .../apim/apiInterfaceManDetail.jsp | 14 +- build.gradle | 15 +- .../transaction/apim/ApiSpecManService.java | 47 ++- 5 files changed, 106 insertions(+), 286 deletions(-) diff --git a/WebContent/js/djb/apispec/app.js b/WebContent/js/djb/apispec/app.js index bfe9476..e2142f4 100644 --- a/WebContent/js/djb/apispec/app.js +++ b/WebContent/js/djb/apispec/app.js @@ -301,7 +301,7 @@ function goStep(n) { if (n < 1 || n > STEPS.length) return; state.step = n; - if (n === 2) state.reqInfoTab = 'body'; // 요청/응답 정보: 진입 시 항상 Body 먼저 + if (n === 2) state.reqInfoTab = firstReqTabWithData(); // 요청 정보: 값 있는 탭(Header→Parameter→Body 순) 먼저 if (n === 3) state.resInfoTab = 'body'; renderStepper(); renderForm(); @@ -439,6 +439,19 @@ + ''; } + // 요청 정보 진입 시 먼저 보일 서브탭 = 값이 든 첫 탭(Header→Parameter→Body 순). + // 보통 GET 은 Parameter(query), POST 는 Body 에만 값이 있음. 전부 비면 Body 로. + function firstReqTabWithData() { + var params = (state.data && state.data.parameters) || []; + var hasHeader = params.some(function (p) { return p.in === 'header'; }); + var hasParam = params.some(function (p) { return p.in === 'query'; }); + var hasBody = !!(state.data && state.data.requestBody && state.data.requestBody.schema && state.data.requestBody.schema.length); + if (hasHeader) return 'header'; + if (hasParam) return 'param'; + if (hasBody) return 'body'; + return 'body'; + } + // Step 2 — 요청 정보 (Header / Parameter / Body / API 설명(HTML)) function renderReqInfo(root) { const tab = state.reqInfoTab || 'body'; diff --git a/WebContent/js/djb/apispec/sample-data.js b/WebContent/js/djb/apispec/sample-data.js index 99f09c6..2ed5b72 100644 --- a/WebContent/js/djb/apispec/sample-data.js +++ b/WebContent/js/djb/apispec/sample-data.js @@ -1,298 +1,63 @@ -// 계좌이체 API 모의 데이터 -// locked: true 인 필드는 게이트웨이가 자동 주입한 값(회색 + 자물쇠) -// locked: false / 미지정 인 필드는 사용자가 자유롭게 편집 +// OpenAPI 에디터 초기 상태 골격(빈 껍데기). +// - specToData() 가 deepClone 하여 서버 spec 값으로 덮어쓰는 구조 기준값. +// - 실제 데모/샘플 값은 두지 않는다(팝업은 항상 서버 spec 로드 후 렌더). +// - docOptions 만 기능 기본값으로 유지(문서 미리보기 옵션). +// locked:true = 게이트웨이 자동 주입(회색+자물쇠) / locked:false = 사용자 편집. window.SAMPLE_DATA = (function () { + function f() { return { value: '', locked: false }; } return { info: { - title: { value: '계좌이체 API', locked: false }, - version: { value: '1.0.0', locked: false }, - summary: { value: '', locked: false }, - description: { value: '', locked: false }, - termsOfService: { value: '', locked: false }, - contact: { - name: { value: 'DJB API Team', locked: false }, - email: { value: 'api@djb.co.kr', locked: false }, - url: { value: '', locked: false } - }, - license: { - name: { value: '', locked: false }, - url: { value: '', locked: false } - } + title: f(), + version: f(), + summary: f(), + description: f(), + termsOfService: f(), + contact: { name: f(), email: f(), url: f() }, + license: { name: f(), url: f() } }, - tags: [ - { name: 'transfer', description: '계좌이체 관련 API' } - ], + tags: [], - externalDocs: { - description: { value: '', locked: false }, - url: { value: '', locked: false } - }, + externalDocs: { description: f(), url: f() }, - servers: [ - { - url: { value: 'https://api-dev.djb.co.kr', locked: true }, - env: 'development', - description: { value: '개발 환경', locked: false } - }, - { - url: { value: 'https://api-stg.djb.co.kr', locked: true }, - env: 'staging', - description: { value: '스테이징 환경', locked: false } - }, - { - url: { value: 'https://api.djb.co.kr', locked: true }, - env: 'production', - description: { value: '운영 환경', locked: false } - } - ], + servers: [], - serverVariables: [ - { - name: { value: 'basePath', locked: true }, - default: { value: '/v1', locked: false }, - enumStr: { value: '/v1,/v2', locked: false }, - description: { value: 'API 버전 경로', locked: false } - } - ], + serverVariables: [], operation: { - method: { value: 'POST', locked: true }, - path: { value: '/v1/accounts/transfer', locked: true }, - operationId: { value: 'transferAccount', locked: false }, - summary: { value: '', locked: false }, - description: { value: '', locked: false }, - tags: { value: ['transfer'], locked: false }, - deprecated: { value: false, locked: false } + method: f(), + path: f(), + operationId: f(), + summary: f(), + description: f(), + tags: { value: [], locked: false }, + deprecated: { value: false, locked: false } }, // 파라미터 (Path/Query/Header/Cookie) - parameters: [ - { - in: 'header', - name: { value: 'X-Request-Id', locked: true }, - type: { value: 'string', locked: true }, - required: { value: true, locked: true }, - format: { value: 'uuid', locked: false }, - maxLength: { value: '', locked: false }, - pattern: { value: '', locked: false }, - description: { value: '', locked: false }, - example: { value: '', locked: false } - } - ], + parameters: [], - // Request Body 스키마 (중첩 3단) + // Request Body 스키마 requestBody: { mediaType: 'application/json', - required: { value: true, locked: true }, - schema: [ - { - name: { value: 'transactionId', locked: true }, - type: { value: 'string', locked: true }, - required: { value: true, locked: true }, - format: { value: '', locked: false }, - maxLength: { value: '40', locked: false }, - pattern: { value: '^tx-[0-9a-zA-Z-]+$', locked: false }, - description: { value: '', locked: false }, - example: { value: 'tx-20260522-001', locked: false }, - children: null - }, - { - name: { value: 'sender', locked: true }, - type: { value: 'object', locked: true }, - required: { value: true, locked: true }, - format: { value: '', locked: false }, - maxLength: { value: '', locked: false }, - pattern: { value: '', locked: false }, - description: { value: '송금인 정보', locked: false }, - example: { value: '', locked: false }, - children: [ - { - name: { value: 'accountNumber', locked: true }, - type: { value: 'string', locked: true }, - required: { value: true, locked: true }, - format: { value: '', locked: false }, - maxLength: { value: '20', locked: false }, - pattern: { value: '', locked: false }, - description: { value: '', locked: false }, - example: { value: '110-1234-567890', locked: false }, - children: null - }, - { - name: { value: 'bankCode', locked: true }, - type: { value: 'string', locked: true }, - required: { value: true, locked: true }, - format: { value: '', locked: false }, - maxLength: { value: '3', locked: false }, - pattern: { value: '', locked: false }, - description: { value: '', locked: false }, - example: { value: '088', locked: false }, - children: null - }, - { - name: { value: 'holderName', locked: true }, - type: { value: 'string', locked: true }, - required: { value: true, locked: true }, - format: { value: '', locked: false }, - maxLength: { value: '60', locked: false }, - pattern: { value: '', locked: false }, - description: { value: '', locked: false }, - example: { value: '김철수', locked: false }, - children: null - } - ] - }, - { - name: { value: 'receiver', locked: true }, - type: { value: 'object', locked: true }, - required: { value: true, locked: true }, - format: { value: '', locked: false }, - maxLength: { value: '', locked: false }, - pattern: { value: '', locked: false }, - description: { value: '수취인 정보', locked: false }, - example: { value: '', locked: false }, - children: [ - { - name: { value: 'accountNumber', locked: true }, - type: { value: 'string', locked: true }, - required: { value: true, locked: true }, - format: { value: '', locked: false }, - maxLength: { value: '20', locked: false }, - pattern: { value: '', locked: false }, - description: { value: '', locked: false }, - example: { value: '111-2345-678901', locked: false }, - children: null - }, - { - name: { value: 'bankCode', locked: true }, - type: { value: 'string', locked: true }, - required: { value: true, locked: true }, - format: { value: '', locked: false }, - maxLength: { value: '3', locked: false }, - pattern: { value: '', locked: false }, - description: { value: '', locked: false }, - example: { value: '020', locked: false }, - children: null - }, - { - name: { value: 'holderName', locked: true }, - type: { value: 'string', locked: true }, - required: { value: true, locked: true }, - format: { value: '', locked: false }, - maxLength: { value: '60', locked: false }, - pattern: { value: '', locked: false }, - description: { value: '', locked: false }, - example: { value: '이영희', locked: false }, - children: null - } - ] - }, - { - name: { value: 'amount', locked: true }, - type: { value: 'object', locked: true }, - required: { value: true, locked: true }, - format: { value: '', locked: false }, - maxLength: { value: '', locked: false }, - pattern: { value: '', locked: false }, - description: { value: '이체 금액', locked: false }, - example: { value: '', locked: false }, - children: [ - { - name: { value: 'value', locked: true }, - type: { value: 'integer', locked: true }, - required: { value: true, locked: true }, - format: { value: 'int64', locked: false }, - maxLength: { value: '', locked: false }, - pattern: { value: '', locked: false }, - description: { value: '', locked: false }, - example: { value: '50000', locked: false }, - children: null - }, - { - name: { value: 'currency', locked: true }, - type: { value: 'string', locked: true }, - required: { value: true, locked: true }, - format: { value: '', locked: false }, - maxLength: { value: '3', locked: false }, - pattern: { value: '^[A-Z]{3}$', locked: false }, - description: { value: 'ISO 4217 통화코드', locked: false }, - example: { value: 'KRW', locked: false }, - children: null - } - ] - }, - { - name: { value: 'memo', locked: true }, - type: { value: 'string', locked: true }, - required: { value: false, locked: true }, - format: { value: '', locked: false }, - maxLength: { value: '100', locked: false }, - pattern: { value: '', locked: false }, - description: { value: '', locked: false }, - example: { value: '5월 회비', locked: false }, - children: null - } - ] + required: { value: false, locked: false }, + schema: [] }, // 응답 스키마 (상태코드 별) responses: { - '200': { - description: { value: '이체 성공', locked: false }, - schema: [ - { name: { value: 'transactionId', locked: true }, type: { value: 'string', locked: true }, required: { value: true, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: 'tx-20260522-001', locked: false }, children: null }, - { name: { value: 'status', locked: true }, type: { value: 'string', locked: true }, required: { value: true, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: 'COMPLETED', locked: false }, children: null }, - { name: { value: 'completedAt', locked: true }, type: { value: 'string', locked: true }, required: { value: true, locked: true }, format: { value: 'date-time', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: '2026-05-22T10:00:00Z', locked: false }, children: null }, - { - name: { value: 'balance', locked: true }, type: { value: 'object', locked: true }, required: { value: true, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '잔액 정보', locked: false }, example: { value: '', locked: false }, - children: [ - { name: { value: 'before', locked: true }, type: { value: 'integer', locked: true }, required: { value: true, locked: true }, format: { value: 'int64', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: '1000000', locked: false }, children: null }, - { name: { value: 'after', locked: true }, type: { value: 'integer', locked: true }, required: { value: true, locked: true }, format: { value: 'int64', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: '950000', locked: false }, children: null } - ] - } - ] - }, - '400': { - description: { value: '잘못된 요청', locked: false }, - schema: [ - { - name: { value: 'error', locked: true }, type: { value: 'object', locked: true }, required: { value: true, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: '', locked: false }, - children: [ - { name: { value: 'code', locked: true }, type: { value: 'string', locked: true }, required: { value: true, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: 'INVALID_AMOUNT', locked: false }, children: null }, - { name: { value: 'message', locked: true }, type: { value: 'string', locked: true }, required: { value: true, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: '금액이 올바르지 않습니다', locked: false }, children: null }, - { name: { value: 'details', locked: true }, type: { value: 'array', locked: true }, required: { value: false, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: '', locked: false }, itemsType: { value: 'string', locked: false }, children: null } - ] - } - ] - }, - '401': { description: { value: '인증 실패', locked: false }, schema: [] }, - '500': { description: { value: '서버 오류', locked: false }, schema: [] } + '200': { description: f(), schema: [] } }, // 보안 스킴 - securitySchemes: [ - { - key: 'ApiKeyAuth', - type: 'apiKey', - locked: true, - name: { value: 'X-API-Key', locked: true }, - in: { value: 'header', locked: true }, - description: { value: '', locked: false } - } - ], - globalSecurity: ['ApiKeyAuth'], + securitySchemes: [], + globalSecurity: [], // 예제 examples: { - request: { - 'application/json': { - default: '{\n "transactionId": "tx-20260522-001",\n "sender": {\n "accountNumber": "110-1234-567890",\n "bankCode": "088",\n "holderName": "김철수"\n },\n "receiver": {\n "accountNumber": "111-2345-678901",\n "bankCode": "020",\n "holderName": "이영희"\n },\n "amount": {\n "value": 50000,\n "currency": "KRW"\n },\n "memo": "5월 회비"\n}' - } - }, - response: { - '200': { body: '{\n "transactionId": "tx-20260522-001",\n "status": "COMPLETED"\n}', headers: [ { name: 'X-RateLimit-Remaining', type: 'integer', description: '남은 요청 횟수', example: '99' } ] } - } + request: {}, + response: {} }, // 문서 옵션 diff --git a/WebContent/jsp/onl/transaction/apim/apiInterfaceManDetail.jsp b/WebContent/jsp/onl/transaction/apim/apiInterfaceManDetail.jsp index 59aa604..d3a6bf7 100644 --- a/WebContent/jsp/onl/transaction/apim/apiInterfaceManDetail.jsp +++ b/WebContent/jsp/onl/transaction/apim/apiInterfaceManDetail.jsp @@ -77,7 +77,10 @@ display: inline-block; vertical-align: middle; } - + + /* iframe 모달(showModal) 콘텐츠 패딩 제거 — iframe 이 다이얼로그 폭을 최대한 사용해 내부 가로스크롤 방지 */ + .ui-dialog .ui-dialog-content { padding: 0 !important; } +