개발툴 사용가이드 · 삼카 확장
화면 구성요소에 12… 번호를 붙여 설명해요. 프레임워크 설정·SPI는 코어 개발가이드, 데모 확장·커스텀(Redis·MQ·ES 목업 포함)은 데모 확장·커스텀 카탈로그를 보세요. 접속: /framework-devtools(local·dev 프로파일).
화면 전체 구성
#vo, #csr, #batch, #tg로 바로 열 수 있어요.① VO/DTO 생성 — 상단 툴바
sc-excel이면 자동으로 체크돼요.codegen.output-dir에 저장(local 전용).② I/O 정의서 편집 표
I/O 탭 — I/O가 1개여도 표 위에 탭이 보여요. 다른 탭은 클릭으로 전환, 선택된 탭을 한 번 더 클릭(또는 더블클릭)하면 그 자리에서 I/O 이름을 고칠 수 있어요. Enter=저장, Esc=취소, 바깥 클릭=저장 — JSON 정의서의 "name"에 바로 반영돼요.
| I/O | 필드 1 | 필드명 | 타입 2 | 길이 | 소수 3 | 4 |
|---|---|---|---|---|---|---|
| IN | amount | 금액 | 15 | ✕ | ||
| IN | cnt | 건수 | 3 | ✕ | ||
| IN | items | 목록 | ↳+ ✕ | |||
| IN | └ itemCd | 품목 | 10 | ✕ |
Long, 1 이상이면 BigDecimal로 생성돼요.③ 중첩 VO(SubNN) 규칙
ARRAY·OBJECT의 하위 필드는 같은 패키지의 별도 파일로 생성되고, 이름은 {메인 이름}Sub01{접미사}, Sub02… 순서예요. 하위 안의 하위도 같은 번호를 이어 써요.
ApcMIss00021InVo.java ← 메인
├─ List<ApcMIss00021InSub01Vo> items → ApcMIss00021InSub01Vo.java
└─ ApcMIss00021InSub02Vo addr → ApcMIss00021InSub02Vo.java
③-1 기존 타입 참조(생성 대신 참조)
ARRAY·OBJECT 타입은 타입 선택박스 옆에 「기존 타입 참조」 입력칸이 생겨요. 이미 있는 VO/DTO를 그대로 쓰고 싶을 때 단순명이나 FQCN을 넣으세요.
| 필드 | 타입 1 | 편집 |
|---|---|---|
| cnt | + ✕ | |
| items | 2 | + ✕ 3 |
List<ItemVo>, OBJECT는 ItemVo로 생성돼요(FQCN이면 import 추가). 하위 항목이 있었다면 지울지 물어보고, 취소하면 정의서에 보존만 되고 생성엔 안 쓰여요.{"name":"items","type":"ARRAY","ref":"com.acme.common.ItemVo"} // = "attributes":{"ref":"..."}
@TelegramField(kind=GRID|OBJECT)로 순번이 매겨지고, 고정길이면 ARRAY 앞 건수(cnt) 검사도 똑같이 해요. 참조 타입 자체의 레이아웃 어노테이션은 그 클래스에 있어야 해요.④ 고정길이·건수(cnt) 검사
고정길이 전문은 반복부 길이를 알 수 없어서 ARRAY 바로 앞 필드가 반복 건수(cnt)여야 해요. 「고정길이」를 체크하면:
- 타입을 ARRAY로 바꿀 때 앞 필드가 숫자 타입(NUMBER·INT·LONG·DECIMAL)이 아니면 알림이 떠요.
- 미리보기/소스에 쓰기 직전 전체 정의서를 검사해, 위반 필드 경로(예:
ApcMIss00021.in.items)를 보여주고 생성을 멈춰요.
⑤ 엑셀 불러오기
sc-excel을 고르면 전문용 VO·고정길이가 자동 체크돼요(일반 양식은 excel)./api/excel/template에서 양식 다운로드).⑥ 업무단위 Controller·Service·Repository
업무명·패키지를 넣고 레이어를 고르면 규칙(/api/generate-layer/rules)에 맞는 이름으로 생성해요. 미리보기로 경로를 먼저 확인하세요.
| Content-Type (메소드 행 선택) | 본문(in 타입) 어노테이션 | 매핑 |
|---|---|---|
| JSON (기본) | @RequestBody | POST/PUT/PATCH 에 consumes = MediaType.… 자동 |
| Form (x-www-form-urlencoded) | DTO면 @ModelAttribute, 없으면 @RequestParam Map | |
| Multipart (form-data) | @RequestPart("body") + 추가 part 파라미터 |
추가 파라미터 칸에 쉼표로 여러 개를 붙일 수 있어요: param page:Integer, header X-User-Id, path id:Long, part file → @RequestParam·@RequestHeader·@PathVariable(URI의 {id} 타입 지정)·@RequestPart MultipartFile. 타입 생략 시 String(part는 MultipartFile). GET에서 in 타입은 @ModelAttribute 쿼리 객체예요.
⑦ 배치 잡 스켈레톤
잡 이름·Reader/Writer 유형을 고르면 Job/Step 설정 골격을 만들어요. 실행·이력은 /framework-devtools/batch.
⑧ 전문 테스트
상세 레퍼런스(docs/DEVTOOLS-GUIDE.md 전문)
위 화면 설명과 같은 내용을 정본 수준으로 펼쳐 볼 수 있어요. 섹션을 눌러 펼치세요.
대상: 프레임워크를 쓰는 업무 개발자. 화면을 위→아래 순서로 따라가며 단계별·필드별로 설명하고, 같은 기능의 REST API 를 §12 에 정리한다. 설정 키는
ongo.framework.devtools.*(→ CUSTOMIZATION.md §14), 확장 코드는 CUSTOMIZATION-EXAMPLES.md §D. 기준 코드: 화면ongo-framework-devtools/src/main/resources/ongo/devtools/index.html, APIDevtoolsController·DynamicTelegramController(devtools),BatchDevtoolsController(batch). 예시 값(카드번호·주문번호 등)은 모두 임의 placeholder 다.
목차 0. 접속·켜는 조건 · 1. 전체 흐름 · 2. 공통 I/O 정의서 카드(유형·옵션·생성) · 3. 샘플 JSON → 정의서 · 4. 기존 VO·엑셀·유형별 본문 파싱 · 5. JSON 정의서 ↔ 펼친 표(양방향) · 6. 암호화/마스킹 · 7. 생성 결과 편집·저장 · 8. 업무단위 C/S/R + 메소드 폼 · 9. 타입 색인 · 10. 배치 스켈레톤 · 11. 전문 테스트(실송신/시뮬레이션) · 12. REST API · 13. 모바일 UX · 14. 트러블슈팅
0. 접속·켜는 조건
| 환경 | URL | 비고 |
|---|---|---|
| 로컬(데모) | ./gradlew :ongo-framework-sc-demo:bootRun → http://localhost:8080/framework-devtools/ |
삼카 확장 포함(데모가 samsung-card 의존) |
| 로컬(일반 데모) | 삼카 비의존 — 코어 유형만 | |
| 로컬(custom 프로필) | --args='--spring.profiles.active=custom' |
sample-csv 유형·sample-layout 데코레이터 추가 |
| 개발계(일반) | https://devfw21.ongosoft.com/framework-devtools/ | 쓰기 금지 → zip 다운로드 사용 |
| 개발계(삼카) | https://devfw21-sc.ongosoft.com/framework-devtools/ | 삼카 유형·표준전문 코덱 포함 |
| 배치 실행 화면 | /framework-devtools/batch |
ongo-framework-batch 의존 앱에서만 |
켜는 조건(모두 yml):
ongo:
framework:
devtools:
enabled: true # 기본 true
profiles: [local, dev] # 이 중 하나가 활성일 때만 화면·API 노출 (prod 에는 아무 것도 없음)
codegen:
profiles: [local] # "소스에 쓰기"가 의미 있는 프로필. dev 서버는 전문 테스트·미리보기·zip 위주
output-dir: src/main/java
write-enabled: true # false 면 쓰기 요청 403 devtools.codegen.write-disabled
- 화면은 정적 리소스가 아니라 컨트롤러(
GET /framework-devtools)가 내준다. 프로필이 안 맞으면 404 이다(보안상 정상). - 공통 I/O 정의서 카드 상단(입력 유형 옆) 정보줄에
데코레이터: … · 출력: … · 쓰기 허용/금지가 표시된다(GET /api결과). 쓰기 버튼을 누르기 전에 먼저 확인한다.
1. 전체 흐름
화면은 hash 탭 4개: #vo VO/DTO 생성 · #csr 업무단위 Controller·Service·Repository · #batch 배치 잡 스켈레톤 · #tg 전문 테스트. 공통 I/O 정의서 카드는 #vo에서 편집하고(숨겨도 내용은 유지) #tg 전문 테스트가 그 정의서를 그대로 쓴다. 생성 미리보기(#files)와 하단 로그(pre#out)는 탭마다 따로 둔다. 미리보기·로그는 그 작업을 시작한 탭에만 붙고, 다른 탭으로 갔다가 돌아오면 그 탭에서 보던 파일(고친 경로·본문 포함)과 로그만 다시 보인다. 전문 테스트 탭에는 VO/DTO 탭에서 만든 소스가 남지 않는다. [이 탭 로그 지우기]는 보고 있는 탭의 로그만 비우고 미리보기는 그대로다.
[입력] 샘플 JSON │ 기존 VO(FQCN) │ 정의서 본문(유형별) │ 엑셀(.xlsx)
└──────────────► JSON 정의서(공통 I/O) ◄──► 펼친 항목 표 (양방향 동기화)
│ 암호화/마스킹 체크·정책명, 스웨거 on/off
┌────────────────┼──────────────────────┐
▼ ▼ ▼
[미리보기] VO 생성 [전문 송수신] 동적 VO (업무단위 C/S/R / 배치 는 별도 입력)
▼
파일별 경로·내용 편집 → [수정본 소스에 쓰기] / [zip 다운로드]
원칙: 생성·전문 테스트는 항상 "공통 I/O 정의서" 카드 안의 "JSON 정의서" 칸 본문 + 같은 카드 상단의 입력 유형 으로 동작한다. 표는 그 본문을 편하게 고치는 편집기일 뿐이다.
화면 배치 이후): 예전 최상단 툴바(유형·덮어쓰기·스웨거·미리보기/소스에 쓰기)는 없어졌고, 모두 강조 테두리(.speccard)의 "공통 I/O 정의서 — 입력 유형 선택 → 불러오기·펼침·편집 → 생성" 카드 하나로 합쳐졌다.
┌ 공통 I/O 정의서 카드 ───────────────────────────────┐
│ 입력 유형 [select] · 정보줄(데코레이터/출력/쓰기 허용) │
│ ① 샘플 JSON 칸(유형=샘플 JSON 입력일 때) │
│ 기존 VO · 엑셀 · JSON 정의서 위 [본문 파싱] │
│ JSON 정의서 칸 ↔ 펼친 항목 표 │
│ 정의서로 코드 생성: □덮어쓰기 □스웨거 [미리보기][소스에 쓰기] │
└──────────────────────────────────────────────────────┘
업무단위 C/S/R 카드 · 배치 카드 · 전문 테스트 카드(정의서·유형 = 위 카드)
2. 공통 I/O 정의서 카드 — 유형·옵션·생성
| 요소 | 설명 | 주의 |
|---|---|---|
| 입력 유형 드롭다운(카드 상단) | 정의서 파서(SpecParseService.type()) 선택. 맨 위 샘플 JSON 입력 은 가상 유형(샘플 칸 표시용) |
처음 로드 시 json 이 기본 선택 |
| 덮어쓰기(카드 하단 "정의서로 코드 생성") | 같은 경로 파일이 있으면 덮어씀. 끄면 SKIPPED_EXISTS 로 건너뜀 |
업무단위·배치·[수정본 저장]에도 같은 체크가 적용 |
| 스웨거(OpenAPI) 주석(카드 하단) | 전체 on/off. 켜면 OpenApiCodeGenDecorator 가 클래스·필드에 @Schema(description=…) 부여 |
필드별 on/off 는 없다. 설명(description/label)이 비면 주석도 비어 있음 |
| 미리보기(카드 하단) | POST /api/generate/{type}?write=false — 디스크 미변경 |
결과는 §7 편집 영역에 펼쳐짐 |
| 소스에 쓰기(카드 하단) | write=true — output-dir 아래에 기록 |
write-enabled=false 면 403. 출력 루트 밖 경로·.. 는 거부 |
덮어쓰기·스웨거·미리보기/소스에 쓰기는 이 카드의 DTO/VO 생성 전용이다. 업무단위 C/S/R·배치는 각 카드의 별도 미리보기/쓰기를 쓴다(덮어쓰기 체크는 공유). 폭 700px 이하에서는 카드 안 체크박스는 한 줄씩, 버튼은 전체 폭으로 쌓인다.
2.1 정의서 유형
| 유형 ID | 모듈 | 입력 형태 | 화면 흐름 |
|---|---|---|---|
sample-json |
(화면 전용) | 실제 요청/응답 데이터 | §3 — 샘플 칸에서 파싱 → json 으로 전환 |
json |
코어 | JSON 정의서(§5) | 본문 직접 편집 ↔ 표. 기본 유형 |
excel |
코어 | 코어 기본 양식 .xlsx | §4.2 — [기본 양식(xlsx)] 으로 템플릿 받기 |
sc-excel |
삼카 | 삼카 인터페이스정의서(다중 시트, IN/OUT 시트 짝) | §4.2 + IN/OUT 시트 패턴 지정 |
samsung-card-snippet |
삼카 | 레거시 코드조각(@ChannelAnnotation 필드 / WebtUtil.getString(buf,off,len)) |
§4.3 — 본문 붙여넣기 → [본문 파싱] |
samsung-card-io |
삼카 | | 구분 텍스트(placeholder 양식) |
§4.3 |
sample-csv |
데모 custom | CSV(첫 줄 이름,kind,패키지) |
커스텀 파서 예제 |
generic/삼카 분기: 화면은
/api의decorators·specTypes를 보고samsung-card데코레이터가 있을 때만sc-excel·삼성 유형 안내와 배치 「삼카」 체크를 보여 준다. 일반 데모(ongo-framework-generic-demo)에서는 코어 유형(json·excel·sample-json)만 보인다. API 경로는 같다.kind(DTO/VO): 「정의서로 코드 생성」의 kind 칸에서
DTO/VO를 고르거나 직접 입력(codegen.suffixes키)하면 정의서 JSON의kind(배열이면 문서마다)가 바뀐다. 삼카sc-excel은 이 칸을 숨기고 기본VO로 두며, 클래스명In/Out등은vo-naming의prefix·in-suffix·out-suffix(예InVo/OutVo)로 맞춘다.
유형별 "어떤 입력칸을 쓰는가":
| 정의서 종류 | 쓰는 입력칸 | 버튼 |
|---|---|---|
| 공통 I/O(JSON) | JSON 정의서 칸 | (자동 동기화) → 미리보기 |
| 샘플 데이터 | 샘플 칸(유형=샘플 JSON 입력 일 때만 보임) |
[샘플 파싱 → 정의서 생성] |
| 기존 VO | 라벨 기존 VO + FQCN |
[불러오기] |
| 엑셀 계열 | 파일 선택 + (선택) 매핑 JSON | [시트·컬럼 분석] → [엑셀 펼치기] |
| 텍스트 계열(snippet/io/csv) | JSON 정의서 칸에 원문 그대로 | 본문 파싱 |
| 업무단위 C/S/R | "업무단위" fieldset | 미리보기/쓰기(§8) |
| 배치 | "배치" fieldset | 스켈레톤/프로젝트 미리보기(§10) |
| 전문 | 정의서 칸 + 전문 테스트 fieldset | 전문 송수신 |
3. 샘플 요청/응답 JSON → JSON 정의서
- 유형에서
샘플 JSON 입력선택 → "① 샘플 요청/응답 JSON" 칸이 나타난다(다른 유형일 때는 숨김). - 실제 데이터(또는 그 모양의 임의 값)를 붙여 넣는다. 요청/응답은
in/out키로 구분:json {"in":{"cardNo":"1234567812345678","amount":10000}, "out":{"resultCode":"0000","items":[{"seq":1,"name":"a"}]}}구분 없이 주면 전체가 하나의 VO:{"cardNo":"1234567812345678"} - 이름(
CARD01— 클래스명·인터페이스ID 기준) 입력 → [샘플 파싱 → 정의서 생성] (POST /api/import/sample-json?name=CARD01). - 성공: JSON 정의서·표가 채워지고 샘플 칸은 비워지며 안내
✔ 정의서 생성 — 샘플은 비웠어요. 이후 편집은 정의서/표에서 한다(샘플은 일회성). - 실패: 브라우저에서 먼저
JSON.parse검사 →⚠ JSON 오류: …. 서버 오류는 VO/DTO 탭 하단 로그(pre#out)에 응답 전체.
타입 추론 규칙:
| 샘플 값 | 추론 type | 보정 포인트 |
|---|---|---|
| int 범위 정수 | INT |
금액이면 LONG/DECIMAL 로 |
| 큰 정수 | LONG |
|
| 소수 | DECIMAL |
length/scale 지정 |
| true/false | BOOLEAN |
|
| 문자열 | STRING |
숫자처럼 보이는 문자열(카드번호)은 STRING 유지, length 필수 지정 |
| 객체 | OBJECT + 하위 fields |
|
| 배열 | ARRAY(첫 원소 기준) |
원소가 섞여 있으면 첫 원소 모양만 반영 |
추론값은 기본값일 뿐이다. 길이·필수·설명·필드명(한글)은 표에서 반드시 채운다.
4. 기존 VO · 엑셀 · 유형별 본문 파싱
4.1 기존 VO 불러오기
- 라벨
기존 VO (어노테이션/POJO, className)(선택지가 하나라 셀렉트가 아닌 고정 라벨) + FQCN(com.example.card.CARD01In) → [불러오기] =POST /api/import/vo?className=…. - 앱 클래스패스에서 로드한다.
@ScField(order·length),@Encrypted/@Masked, POJO 필드·레코드 컴포넌트를 읽어 정의서로 만든다. - 없는 클래스: 404
devtools.import.class-not-found.
4.2 엑셀
- 파일 선택(.xlsx). 유형이
excel/sc-excel가 아니면 자동으로excel로 처리한다. - [시트·컬럼 분석(매핑 제안)] (
POST /api/excel/inspect/{type}) → 시트별 헤더 행·헤더 목록·자동 매핑 제안이 드롭다운으로 표시된다. - 드롭다운으로 필드 키(
name·label·type·length·scale·required·description·encrypted·masking·io·level등)↔엑셀 컬럼을 확정하면 매핑 JSON 칸이 자동 갱신된다. -IN 시트/OUT 시트패턴(예*_IN/*_OUT)과기본 타입(예STRING)도 지정 가능. - [엑셀 펼치기] → 정의서·표. (업로드한 .xlsx 를 읽는다. JSON 정의서 칸 본문을 읽는 [본문 파싱]과 구분)
매핑 JSON 직접 입력 예(컬럼은 헤더명 또는 열 문자):
{"headerRow":1,"columns":{"name":"항목명","label":"한글명","length":"D","encrypted":"암호화여부","masking":"마스킹여부"},
"ioSheets":{"in":"*_IN","out":"*_OUT"},"defaults":{"type":"STRING"}}
코어 기본 양식 헤더: 항목명·한글명·타입·길이·소수·필수·설명·암호화여부·마스킹여부·구분(IN/OUT)·레벨. 레벨 2 이상은 바로 위 OBJECT/ARRAY 의 하위 필드.
4.3 텍스트 계열 정의서(삼카 snippet·io, CSV 등)
- 공통 I/O 정의서 카드 상단 입력 유형을 고른다(예
samsung-card-snippet). - JSON 정의서 칸에 원문을 그대로 붙여 넣는다(JSON 아니어도 된다 — 이때 표 동기화는
⚠ JSON 오류를 띄우지만 무시). - [본문 파싱] (
POST /api/import/{type}) → 공통 I/O JSON 으로 바뀌어 칸이 덮어써지고 표가 펼쳐진다. 이후 유형은json으로 다뤄진다.
#interface|CARD01|Card01In|DTO|com.example.card ← samsung-card-io (placeholder 양식)
cardNo|String|16|Y|카드번호
#interface|CARD01|Card01Out|DTO|com.example.card|RESPONSE
resultCode|String|4|Y|결과코드
// #interface|CARD01|Card01Out|DTO|com.example.card|RESPONSE ← samsung-card-snippet
@ChannelAnnotation(order = 1, dataType = IIMSDataType.NUMERIC, length = 8, scale = 0) private long msgLength;
// 또는 오프셋 패턴(블록당 한 패턴만): sysCode = WebtUtil.getString(byteArray, 8, 4);
snippet 규칙: (A) 어노테이션 패턴은 order 정렬, NUMERIC → LONG/DECIMAL. (B) 오프셋 패턴은 offset 정렬, 빈틈·겹침은 오류, parseInt/parseLong → LONG. ARRAY(그리드)는 아직 미지원(오류).
새 유형 추가: SpecParseService 빈 등록 → 드롭다운에 자동 노출(EXAMPLES §D1).
5. JSON 정의서 ↔ 펼친 항목 표 (양방향 동기화)
{"name":"Card01","kind":"DTO","package":"com.example.card","description":"카드 조회",
"in":{"fields":[{"name":"cardNo","label":"카드번호","type":"STRING","length":16,"required":true,"encrypted":"card","masking":"card"}]},
"out":{"fields":[{"name":"items","type":"ARRAY","fields":[{"name":"seq","type":"LONG"},{"name":"amount","type":"DECIMAL","length":15,"scale":2}]}]}}
| 속성 | 의미 / 생성 결과 |
|---|---|
name(정의서) |
클래스 기본명. in/out 이 있으면 {name}In/{name}Out(접미사 규칙) |
kind |
DTO/VO/… → codegen.suffixes 로 접미사 |
package |
비면 codegen.default-package |
필드 name |
Java 식별자 검사(위반 시 400 devtools.codegen.invalid) |
type |
STRING·INT·LONG·DECIMAL·NUM·BOOLEAN·DATE·DATETIME·OBJECT·ARRAY 또는 FQCN |
length/scale |
문자열 length → @Size(max)(validation 데코레이터), 전문 레이아웃 길이 |
required |
@NotNull |
label/description |
Javadoc·@Schema 설명. 표에서 "필드명"/"설명" 칸 |
encrypted/masking |
§6 |
fields |
OBJECT/ARRAY 하위(표에서 · 들여쓰기로 표시) |
attributes |
고객사 확장 자유 속성(interfaceId, offset 등) — 데코레이터가 읽음 |
동기화 규칙(코드 기준):
- 정의서 → 표: 입력 후 300ms 디바운스로 JSON.parse → 표 재구성, ✔ 정의서 → 표 반영. JSON 이 깨지면 ⚠ JSON 오류(표는 마지막 정상값 유지) — 표는 바뀌지 않는다.
- 표 → 정의서: 필드명·설명·암호·마스킹 칸을 고치면(change) 즉시 정의서 전체를 다시 직렬화하고 유형을 json 으로 전환, ✔ 표 → 정의서 반영.
- 표에서 고칠 수 있는 것: 필드명(label)·설명·암호·마스킹. name·type·length·구조(추가/삭제)는 정의서에서 고친다.
- 값을 비우면 그 속성은 삭제된다(빈 문자열로 남지 않음).
- 정의서를 표에서 고친 뒤 원문(텍스트 유형)으로 되돌릴 수는 없다 — 원문이 필요하면 따로 보관.
6. 암호화·마스킹 체크와 정책 입력
| 표 조작 | 정의서 결과 | 생성 코드 |
|---|---|---|
| 체크만 | "encrypted": true |
@Encrypted |
옆 칸에 card 입력(체크 자동) |
"encrypted": "card" |
@Encrypted("card") |
| 체크 해제 | 속성 삭제 | 없음 |
| 마스킹 동일 | "masking": true / "규칙" |
@Masked / @Masked("규칙") |
- 정책·규칙명은
ColumnAttributeService.ColumnMeta.encrypted()/masking()문자열로 그대로 전달된다 → 정책별 분기 구현은 EXAMPLES §E4. - 런타임 동작: DB VO 의
@Encrypted컬럼은 MyBatis 저장 시 암호화·조회 시 복호화, 응답 DTO 의@Masked는 응답 직렬화 시 마스킹, 전문 코덱은 encode 전/decode 후ColumnAttributeService훅(CUSTOMIZATION §14.3). - 엑셀
암호화여부/마스킹여부컬럼 값Y→true, 그 외 문자열 → 정책명.
7. 생성 결과 편집·저장
미리보기 후 파일마다 경로 입력칸 + 내용 textarea 가 펼쳐진다. 목록은 그 생성을 시작한 탭에만 붙는다. 다른 탭으로 옮기면 그 탭의 파일만 보이고, 돌아왔을 때 고친 경로·내용이 남아 있다. [이 탭 로그 지우기]는 이 목록을 지우지 않는다.
- 경로·내용을 직접 고칠 수 있다(패키지 이동 시 경로와 package 선언을 함께 고칠 것).
- [수정본 소스에 쓰기] POST /api/files/save?overwrite= — output-dir 기준 상대경로만. 절대경로·C:·.. 는 400.
- [zip 다운로드] POST /api/files/download → generated-sources.zip. 서버에 쓰지 않으므로 개발계(쓰기 금지)에서도 동작.
- 결과(쓰기 결과 WRITTEN/OVERWRITTEN/SKIPPED_EXISTS 등)는 그 작업을 시작한 탭의 하단 로그에 JSON 으로 남는다. 다른 탭 로그와 섞이지 않는다.
8. 업무단위 Controller + Service + Repository 생성
| 필드 | 예 | 규칙 |
|---|---|---|
| 업무 루트 패키지 | com.example.order |
하위 패키지는 codegen.layer.<유형>.package(기본 controller/service/repository) |
| 업무명 | Order |
클래스명 = prefix + 업무명 + suffix → OrderController·OrderService·OrderRepository |
| 설명 | 주문 |
Javadoc·스웨거 태그, 기본 메소드 설명 |
| 경로 | /api/order |
컨트롤러 @RequestMapping |
| 스웨거 | 체크 | @Tag/@Operation 부여(정의서 카드 하단 스웨거와 별개) |
현재 규칙은 화면 하단 작은 글씨(GET /api/generate-layer/rules)로 표시된다.
8.1 메소드 폼
행 하나 = 메소드 하나: 메소드명 · HTTP(GET/POST/PUT/PATCH/DELETE) · URI · in 타입 · out 타입 · 설명 · [✕]
- [+ 메소드 추가] 로 행 추가, [✕] 로 삭제.
- 메소드명이 빈 행은 무시. 유효한 행이 하나도 없으면 기본
list(GET "")·get(GET /{id})·create(POST "")생성 — 자세한 규칙은 §8.2. - URI 를 비우면 베이스 경로 그대로.
{orderId}는@PathVariable String orderId로. - in 타입: GET/DELETE 는 쿼리 파라미터 객체(
@ModelAttribute), POST/PUT/PATCH 는 Content-Type 선택에 따름 — JSON(기본)@RequestBody, Form@ModelAttribute(DTO 없으면@RequestParam Map), Multipart@RequestPart("body"). 본문 메소드에는consumes = MediaType.…가 붙음. out 타입: 반환형(List<OrderDto>가능). - 추가 파라미터(쉼표 구분, 여러 개):param page:Integer, header X-User-Id, path id:Long, part file→@RequestParam/@RequestHeader/@PathVariable(URI 변수 타입 지정)/@RequestPart MultipartFile. 타입 생략 시 String(part 는 MultipartFile). Service 시그니처에도 같은 순서로 전달. - 한 줄 입력에서는ct:json|form|multipart토큰으로 Content-Type 지정(JSON 기본이면 생략), 추가 파라미터는 공백 없이param:page:Integer header:X-User-Id path:id:Long part:file토큰으로. - in/out 칸은 입력 즉시 자동완성 후보(datalist)가 뜬다. 행을 한 번 클릭하면 "선택 행"이 된다 → 아래 타입 찾기 검색 후 [선택 행 in에 넣기]/[선택 행 out에 넣기].
- 한 줄 텍스트 ↔ 폼 양방향: 폼 행을 입력·수정·삭제하면 "한 줄 텍스트" 칸이 실시간으로 같은 내용으로 변환된다(Content-Type·추가 파라미터 포함, 복사해 공유·재사용 가능). 반대로 텍스트를 고치거나 붙여넣고 [폼으로 가져오기(폼 교체)] 를 누르면 폼 행이 텍스트 내용으로 교체된다:
cancel PUT /{orderId}/cancel in:OrderCancelDto out:OrderDto 주문 취소 search GET /search in:OrderSearchDto out:List<OrderDto> 주문 검색 ping GET - 상태 확인형식:이름 HTTP URI(없으면 -) [in:타입] [out:타입] 설명…. - [미리보기] → 파일 3개(+경고). [쓰기] 는 경고가 하나라도 있으면 400 으로 중단(
타입 확인 필요(쓰기 중단)).
타입 규칙: in/out 은 프로젝트에 이미 있는 클래스만(단순명·FQCN·제네릭). 없거나 단순명이 둘 이상 겹치면 ⚠ 타입 경고(생성 안 함) — 그 메소드는 빠진다. 먼저 VO 를 생성·저장하고 §9 재색인.
8.2 메소드를 하나도 정의하지 않으면 — 기본 뼈대(list·get·create)
메소드 폼에 유효한 메소드가 하나도 없으면 아래 세 API 가 자동으로 생성된다. 버그가 아니라 의도된 기본 뼈대(와꾸) 다.
| 메소드 | HTTP | URI(베이스 경로 기준) | 컨트롤러 반환형 | Service 본문 | 설명(Javadoc·@Operation) |
|---|---|---|---|---|---|
list |
GET | ""(베이스 경로 그대로) |
List<Map<String, Object>> |
return repository.findAll(); |
{설명} 목록 |
get |
GET | /{id} (@PathVariable String id) |
Map<String, Object> |
return repository.findById(id); |
{설명} 단건 |
create |
POST | "" (@RequestBody Map<String, Object> body) |
Map<String, Object> |
@Transactional + return repository.insert(body); |
{설명} 등록 |
{설명}은 상단 설명 칸 값, 비우면 업무명(예Order).- 경로 칸을 비우면 베이스 경로는
/api/{업무명 kebab}(예OrderItem→/api/order-item). - 예) 업무명
Order, 경로/api/order→GET /api/order,GET /api/order/{id},POST /api/order.
"유효한 메소드가 없다"의 정확한 기준
1. 화면: 메소드 행이 0개이거나, 있더라도 메소드명 칸이 전부 비어 있으면 화면이 그 행들을 버리고(methodRows() 가 name 빈 행 제외) 빈 목록을 보낸다 → 서버가 기본 3개를 붙인다.
2. 서버(LayerCodeGenerator.generate): methods 가 null 이거나 빈 배열이면 defaultMethods(설명) 사용.
3. REST 로 직접 호출할 때(§12)는 화면 필터가 없으므로 methods 에 이름이 빈 항목을 넣으면 400(메소드명 오류/중복). 기본 뼈대를 원하면 methods 를 아예 빼거나 [] 로 보낼 것.
하나라도 정의하면 기본 3개는 붙지 않는다
- 메소드명이 채워진 행이 1개라도 있으면 그 행들만 생성된다(기본 list/get/create 와 합쳐지지 않음).
- 기본 3개에 하나를 더하고 싶다면 list·get·create 행도 직접 넣는다. 한 줄 붙여넣기 예:
list GET - 주문 목록
get GET /{id} 주문 단건
create POST - 주문 등록
cancel PUT /{orderId}/cancel in:OrderCancelDto out:OrderDto 주문 취소
- 직접 넣은 행이라도 이름이 list/get/create 이고 in/out 타입을 비우면 Service 가 위 표처럼 Repository 를 호출한다. in/out 타입을 지정하면 Service 본문은 // TODO + return null;.
- in/out 타입 경고가 난 메소드는 빠지고, 경고가 하나라도 있으면 [쓰기]는 400 으로 중단(§8.1 7번, 타입 규칙) — 기존 규칙 그대로.
왜 기본 뼈대를 주나 — 생산성
- 메소드를 정하지 않았어도 Controller / Service / Repository 3 클래스, 패키지, import, 생성자 주입(DI), @RestController·@Service·@Repository·@Transactional 구조가 한 번에 완성된다.
- 당장 쓰지 않는 API 라도, 나중에 메소드명·URI·타입만 바꾸거나 지우면 되므로 처음부터 타이핑할 필요가 없다.
- Repository 는 findAll/findById/insert 를 빈 값으로 반환하는 임시 구현(TODO: MyBatis Mapper·JPA 등으로 교체)이다. 실제 저장소로 교체할 것.
- 필요 없는 기본 API 는 생성 후 지우면 된다. 운영 전 남겨 둔 list/get/create 가 의도치 않게 노출되지 않는지 확인할 것.
미리보기 · 쓰기 · 수정본 저장 (§7 과 동일) - [미리보기]: 서버에 쓰지 않고 3개 파일(+경고)만 보여 준다. 기본 뼈대 여부를 여기서 먼저 확인. - [쓰기]: 현재 폼 값으로 다시 생성해서 디스크에 쓴다. 미리보기 textarea 에서 고친 내용은 반영되지 않는다. - 미리보기에서 직접 고친 내용은 §7 [수정본 소스에 쓰기](또는 [zip 다운로드])으로만 저장된다. 개발계(쓰기 금지)는 zip 만 가능.
9. 타입 색인 · 재색인
- 색인 대상:
@SpringBootApplication패키지 +codegen.type-packages의 클래스패스.class+ output-dir 의.java소스. 내부 클래스($) 제외.java.lang·java.util·java.time·BigDecimal등 내장 타입은 항상 인식. - 첫 조회 때 한 번 스캔·메모리 캐시 → 이후 검색은 메모리 필터. 입력 250ms 디바운스, 최대 30건(API 는
limit최대 200). - 입력할 때마다
resolve로 검증:✔ import FQCN또는⚠ 경고. - [재색인] =
refresh=true— 캐시를 비우고 다시 스캔. 방금 저장한 클래스를 후보에 넣을 때. - 전문 테스트의 동적 VO(런타임 컴파일)는 색인 대상이 아니다(디스크/클래스패스 파일 필요). 존재 확인만 클래스로더 리소스도 본다.
- 클래스가 많아 첫 색인이 느리면
type-packages를 업무 루트로 좁힌다.
ongo.framework.devtools.codegen:
type-packages: [com.example.order, com.example.common]
layer:
controller: { package: web, suffix: Controller }
service: { package: service, suffix: Service }
repository: { package: repository, prefix: "", suffix: Mapper }
10. 배치 잡 스켈레톤 · 배치 프로젝트
| 필드 | 예 | 설명 |
|---|---|---|
| 이름 | cardLimitSync |
영문 시작 영숫자. → CardLimitSyncJobConfig, 잡 cardLimitSyncJob |
| 패키지 | com.example.batch |
|
| chunk | 100 |
커밋 단위 |
| Reader | list / custom |
list = ListItemReader 예제, custom = 직접 구현 자리(TODO) |
| item 타입 | String |
Reader→Writer 항목 타입 |
| 전문 채널 / 인터페이스ID | mci / SCDEMO01 |
둘 다 채우면 Writer 에 ChannelService.send(채널, ID, 값) 호출 코드 |
- 생성 스텝은
BatchFailurePolicyService.apply(...)로 skip/retry 정책을 적용한다(정책 교체: EXAMPLES §B). - [스켈레톤 미리보기]
POST /api/generate-batch— 잡 설정 클래스 1개. - [프로젝트 미리보기]
POST /api/generate-batch-project— Gradle 단일 모듈(build.gradle·settings.gradle·application.yml·Application·잡). 프로젝트명·프레임워크 버전(0.1.0-SNAPSHOT)·Nexus URL(비우면 OnGoSoft 기본),삼카체크(삼카 데코레이터가 있을 때만 표시) 시ongo-framework-samsung-card의존 추가. zip 으로 받아 바로 빌드. - 참고 구현:
ongo-framework-batch-demo/.../job/ScCardLimitSyncJobConfig.java(형식 오류 카드번호 skip, 삼카 레이아웃·loopback 전송). - 수동 실행:
/framework-devtools/batch화면 또는POST /framework-devtools/api/batch/jobs/{name}/run(batch.launch.allowed-jobs에 있는 잡만).
11. 전문 테스트 (동적 VO → encode → 송신 → decode)
정의서로 VO 를 즉석 컴파일·로드해 전문 바이트를 만들고 송신·응답을 파싱한다(코드생성 없이 레이아웃 검증).
송신 방식
- 시뮬레이션(기본): loopback — 서버 밖으로 나가지 않고 보낸 전문을 그대로 응답으로 받는다. MCI / EAI / FEP 버튼은 시뮬레이션에서도 그 채널 이름과 yml 문자셋(MCI·EAI UTF-8, FEP EUC-KR)으로 전문을 만든다. 누른 버튼은 밝은 배경·굵은 글씨·체크 표시로 남고, 다른 채널을 누르면 표시가 옮겨진다. 클릭과 키보드(Enter/Space) 동작은 같다.
- 실송신: yml ongo.framework.channel.channels.<name> 의 transport·url·timeout 으로 실제 채널에 송신(데모 ChannelService 와 같은 엔드포인트). 채널 목록은 GET /api/dynamic/channels(문자셋 포함). 문자셋은 화면에서 고르지 않는다. channel.charset · charset-defaults · channels.<이름>.charset 을 바꾸고 서버를 재기동한다.
- 안전장치: devtools 자체가 devtools.profiles(기본 local·dev)에서만 켜짐. 추가로 ongo.framework.devtools.dynamic.real-send-enabled=false 면 실송신 금지, real-send-channels: [mci-dev] 로 허용 채널 제한(비우면 전체).
- 공통 I/O 정의서 카드의 정의서 칸 본문 + 입력 유형(전문 테스트 카드에 "정의서·유형 = 위 공통 I/O 정의서 카드" 로 표시)(텍스트 유형이면 원문 그대로도 가능).
- 코덱 선택(
GET /api/dynamic/codecs): 코어 고정길이(기본) / 삼카 표준전문(standard-telegram.enabled=true일 때) 등DynamicCodecOption빈. - 인터페이스ID(생략 시 첫 VO).
- 값(JSON):
{"cardNo":"1234567812345678","amount":10000}— 없는 키는 공백/0 채움. - 방향: 송신(요청 HEADER) / 수신 응답(타 시스템 요청에 대한 회신,
@direction=response). 실송신 채널이mci/eai면 UTF-8,fep면 EUC-KR(channel.charset-defaults, 채널별channels.<이름>.charset로 변경). 예: 채널mci+ HEADER{"rcvSysDvC":"MCI"}/ 채널eai+{"rcvSysDvC":"EAI"}/ 채널fep+{"rcvSysDvC":"FEP"}. - 표준전문 코덱이면 HEADER·MESSAGE JSON 으로 헤더값 지정(설정 기본값보다 우선) → 내부적으로
header.이름/message.이름키:{"sendSysDvC":"ONG","akRspDvC":"S"}/{"stdMsgPrntTpc":"1"}값이null/""면 설정·내장 기본값 유지," "(length>0)는 공백 그대로. 우선순위 HEADER·MESSAGE 입력 > 업무단ScTelegramContext> (수신 응답) 요청 HEADER 에코 > yml 기본값 > 내장 기본값 — 상세·수신 처리기 응답 맵 예시는 CUSTOMIZATION 「표준전문」절. - [전문 송수신] → 결과: VO 목록(클래스·방향·총 길이·필드별 offset/length/type·
existing=기존 VO 사용 여부), 교환 결과(요청/응답 문자열·바이트 수·디코드 값). 이 출력은 전문 테스트 탭 로그에만 남는다. VO/DTO·업무단위·배치 탭으로 이동하면 그 탭의 로그가 보이고, 다시 전문 테스트 탭으로 오면 이전 전문 로그가 있다. [이 탭 로그 지우기]는 현재 탭만 비운다.
sc-excel: 인터페이스ID는 원형(APC_M_ISS_00027)으로 attributes.interfaceId(@ScTelegram·전문 테스트 칸)에, VO 이름은 카멜(ApcMIss00027)+suffix. 소스/타겟 IO처럼 같은 클래스명의 정의가 여러 벌이면 [엑셀 펼치기] 후 「생성에 쓸 I/O」(attributes.ioRole=시트명)에서 하나를 골라 미리보기·소스에 쓰기.
VO 선택 규칙: prefer-existing-vo=true 면 정의서 FQCN 또는 vo-naming 규칙(기본 {package}.{prefix}{interfaceIdCamel}{suffix} — 예 APC_M_ISS_00027 → ApcMIss00027In/Out, prefix·in/out-suffix 설정, 생성 클래스명도 동일) 클래스가 있으면 그대로 사용, 없으면 동적 생성. 작업 디렉터리 devtools.dynamic.work-dir(기본 build/ongo-devtools/dynamic, 개발 서버는 쓰기 가능한 경로를 환경변수로), fat jar 실행 시 dynamic.extra-classpath 보강.
12. REST API (자동화·CI 용)
기본 경로 /framework-devtools. JSON 응답은 프레임워크 공통 응답 envelope 로 감싸진다, 단 xlsx·zip 은 @NoEnvelope.
| 메서드 | 경로 | 요청 | 응답 요약 |
|---|---|---|---|
| GET | /framework-devtools |
— | 화면 HTML |
| GET | /api |
— | specTypes[{type,description}], decorators[], outputDir, writeEnabled |
| POST | /api/parse/{type} |
본문 = 정의서 원문(text) | SpecModel[] |
| POST | /api/import/{source} |
vo?className= · sample-json?name=(본문 샘플) · {유형}(본문 원문) · excel/sc-excel(본문 {"file":"data:…base64","mapping":{…}}) |
편집용 공통 I/O JSON |
| POST | /api/generate/{type}?write=&overwrite=&swagger= |
정의서 원문 | specs, files[{path,content}], written[] |
| POST | /api/generate-layer?write=&overwrite= |
{"rootPackage","name","description","basePath","swagger","methods":[{"name","http","uri","description","paramType","returnType"}]} |
files, written, warnings |
| GET | /api/generate-layer/types?q=&limit=30&refresh=false |
— | FQCN 목록 |
| GET | /api/generate-layer/resolve?type=List<OrderDto> |
— | {type, imports[], warnings[]} |
| GET | /api/generate-layer/rules |
— | {controller:{packageName,prefix,suffix},…} |
| POST | /api/generate-batch?write=&overwrite= |
{"name","packageName","chunk","itemType","outputType","reader","channel","interfaceId"} |
files, written |
| POST | /api/generate-batch-project?write=&overwrite= |
{"projectName","packageName","frameworkVersion","repositoryUrl","samsungCard","job":{…위와 동일}} |
files, written |
| POST | /api/excel/inspect/{type} |
{"file":"data:…"} |
fieldKeys[], sheets[{name,headerRow,headers,suggested}] |
| GET | /api/excel/template |
— | ongo-spec-template.xlsx |
| POST | /api/files/save?overwrite= |
{"files":[{path,content}]} |
쓰기 결과 |
| POST | /api/files/download |
동일 | generated-sources.zip |
| GET | /api/dynamic/codecs |
— | [{name,label}] |
| POST | /api/dynamic/{type} |
{"content","interfaceId","values":{},"codec","mode":"simulate|real","channel"} |
buildId, dir, vos[], exchange(mode·channel·transport 포함) |
| GET | /api/dynamic/channels |
— | 실송신 후보 [{name,type,transport,url}] |
| GET | /api/batch/jobs |
— | [{name,restartable,manualRunAllowed}] |
| POST | /api/batch/jobs/{name}/run |
잡 파라미터 맵 | 실행 정보 |
| GET | /api/batch/jobs/{name}/executions?limit= |
— | 최근 실행 |
오류 코드: 미등록 유형 404 devtools.spec.unknown, 형식 오류 400 devtools.spec.invalid/devtools.codegen.invalid, 쓰기 금지 403 devtools.codegen.write-disabled, VO 없음 404 devtools.import.class-not-found, import 실패 400 devtools.import.invalid.
XSS: 개발툴 경로(/framework-devtools/**)는 XSS 파라미터·JSON 본문 정화를 건너뜁니다(ongo.framework.devtools.xss-skip=false 로 끔). 타입식 List<OrderDto> 가 < 로 이스케이프돼 와도 검증 전에 원문으로 복원합니다.
curl 예(미리보기):
curl -s -X POST 'http://localhost:8080/framework-devtools/api/generate/json?swagger=true' \
-H 'Content-Type: text/plain' \
-d '{"name":"SampleOrder","kind":"DTO","package":"com.example.sample","fields":[{"name":"orderNo","type":"STRING","length":12,"required":true}]}'
13. 모바일 UX (폭 720px 이하)
- 모든 입력·선택·버튼이 한 줄 전체 폭·최소 높이 44px, 글꼴 16px(iOS 확대 방지), safe-area 여백 적용, 가로 스크롤 없음.
- I/O 표는 카드형: 한 필드 = 카드 한 장, 각 칸 앞에 라벨(
I/O·필드·필드명·설명·타입·길이·암호·마스킹). 암호/마스킹 체크박스 26px 로 터치 가능, 정책명 칸이 옆에 붙는다. - JSON 정의서 칸은 펼친 표 바로 위에 있어 정의서↔표를 번갈아 보며 고칠 수 있다.
- 메소드 폼 행은 세로 스택(메소드명→HTTP→URI→in→out→설명→✕).
<br>은 숨겨지고small안내문은 블록으로 내려간다.
14. 트러블슈팅
| 증상 | 원인 / 조치 |
|---|---|
| 화면 404 | 활성 프로필이 devtools.profiles 에 없음 / devtools.enabled=false |
| [소스에 쓰기] 403 | write-enabled=false(개발계) → [zip 다운로드] |
체크했는데 @Encrypted 없음 |
생성은 정의서 기준. 정의서에 encrypted 가 들어갔는지, 유형이 json 인지 확인 |
| 표가 안 바뀜 | 정의서 JSON 오류(표는 마지막 정상값 유지). specinfo 메시지 확인 |
텍스트 유형 붙였더니 ⚠ JSON 오류 |
정상. 유형 선택 후 [본문 파싱] |
| in/out 타입 경고·쓰기 중단 | 클래스 없음/모호. VO 먼저 생성·저장 → [재색인] → FQCN 으로 지정 |
| 샘플 칸이 안 보임 | 유형이 샘플 JSON 입력 일 때만 보인다 |
| 동적 전문 컴파일 실패(개발 서버) | dynamic.work-dir 쓰기 권한, fat jar 면 extra-classpath |
| 표준전문 코덱이 목록에 없음 | ongo.framework.samsung-card.standard-telegram.enabled=true 필요 |
| 모바일에서 표가 잘림 | 구버전 캐시 — 새로고침. 현재 버전은 카드형(§13) |
| 화면 HTML 에 JS 코드가 본문으로 보임 | 인라인 <script> 안 문자열 리터럴에 </script> 가 들어가면 태그가 조기 종료된다. 예시 문자열은 <\/script> 또는 URL 인코딩(%3C%2Fscript%3E)으로 쓸 것(데모 쇼케이스 수정 사례: 97b0f4d) |
| 한글 길이가 안 맞음 | 전문 길이는 문자셋 바이트 기준(MS949 한글 2바이트, UTF-8 3바이트) — channel.charset 확인 |
데모 확장·커스텀 카탈로그: 데모의 모든 SPI 교체 예시(DemoCustomizations·DevtoolsDemoCustomizations·DemoExtensionMocks)와 Redis·MQ(Rabbit/Kafka)·ES 목업은 코어 개발가이드
/framework-devtools/guide#demoext·#extmock및 CUSTOMIZATION-EXAMPLES §M 참고. 코어에는 SPI 계약만 있고 클라이언트 의존성은 없다(일반 공통, 삼카 비의존).
자주 묻는 질문
Q. 소스에 쓰기가 실패해요
ongo.framework.devtools.codegen.profiles에 현재 프로파일(local)이 있는지 확인하세요.
Q. 공통 VO를 여러 전문에서 같이 쓰고 싶어요
ARRAY/OBJECT의 「기존 타입 참조」에 그 클래스를 넣으세요(③-1).
Q. 예전 GRID 정의서는?
그대로 불러오면 ARRAY로 바뀌어요.
Q. 중첩 VO를 한 파일로 받고 싶어요
API 직접 호출 시 POST /api/generate/{type}?separateNested=false.