Ch 4. 가상 코드 관리 — 신규 메뉴 만들기¶
이 챕터를 마치면
- hera-v4에서 신규 CRUD 메뉴를 만드는 데 필요한 파일 목록과 순서를 말할 수 있습니다.
- 각 레이어(Entity → Mapper → Service → Controller → Client → ViewRouter → HTML/JS)의 역할과 연결 방식을 설명할 수 있습니다.
- 실제 코드를 작성하고 브라우저에서 화면을 확인할 수 있습니다.
1. 시나리오 — PT0101 가상 코드 관리¶
이 챕터에서는 가상의 PT 모듈에 코드 관리 화면을 만듭니다. 운영 코드는 그대로 두고, hera-v4 패턴을 따라 만드는 연습용 메뉴입니다.
| 항목 | 값 |
|---|---|
| 화면 코드 | PT0101 |
| URL | /pt/0101 |
| DB 테이블 | pt_code |
| PK | pt_code_id (UUID v7) |
| 주요 컬럼 | code (코드), name (코드명), use_yn (사용여부) |
| 화면 구성 | 단일 그리드 (조회/등록/수정/삭제) |
| API 경로 | /api/v1/sys/pt-codes |
왜 SYS API를 재사용하나?
hera-v4의 API Gateway 라우팅 설정에서 /api/v1/sys/**는 이미 hera-sys 서비스로 연결됩니다. PT 모듈이 독립 서비스를 가질 만큼 크지 않다면, 기존 sys 인프라를 재사용해서 Gateway 설정 변경 없이 새 API를 추가합니다.
2. 파일 구성 전체 조감¶
만들 파일은 12개, 수정 파일은 1개입니다. 의존성 순서대로 작성합니다.
[1] hera-commons Domain.java ← PT_0101 상수 추가 (수정)
[2] hera-domain-data PtCodeVO.java ← ViewObject
[3] hera-domain-data PtCodeSP.java ← SearchParams
[4] hera-system PtCode.java ← Entity
[5] hera-system PtCodeMapper.java ← Mapper (MinuBaseMapper 상속)
[6] hera-system PtCodeService.java ← Service interface
[7] hera-system PtCodeServiceImpl.java ← ServiceImpl
[8] hera-system PtCodeApiController.java ← System API Controller
[9] hera-api-client PtCodeClient.java ← Feign Client
[10] hera-webapp Pt0101ViewRouter.java ← View Router
[11] hera-webapp pt0101.html ← Thymeleaf 템플릿
[12] hera-webapp pt0101.js ← 화면 JS
[13] hera-webapp Pt0101ApiController.java ← Webapp API Controller
아래 섹션에서 각 파일의 코드를 순서대로 작성합니다.
3. [1] Domain.java 수정¶
hera-commons 모듈의 Domain.java에 PT_0101 상수를 추가합니다.
// hera-commons/.../code/Domain.java
public enum Domain {
// ... 기존 상수들 ...
SYS_0101("SYS0101"),
SYS_0102("SYS0102"),
// ...
PT_0101("PT0101"), // ← 추가
;
private final String value;
Domain(String value) { this.value = value; }
public String getValue() { return value; }
}
Domain 상수 역할
Domain.PT_0101은 ViewRouter에서 prepareBaseInfo(model, PT_0101) 형태로 사용합니다. 이 메서드가 내부적으로 화면 코드 → 메뉴 정보 → 권한 정보를 모델에 주입합니다. 상수를 빠뜨리면 화면이 올바른 권한·메뉴 정보를 받지 못합니다.
4. [2] PtCodeVO.java¶
hera-domain-data 모듈에 ViewObject를 작성합니다. VO는 화면과 API 레이어 사이에서 데이터를 주고받는 객체로, AuditVO를 상속해 감사 필드를 포함합니다.
// hera-domain-data/.../pt/PtCodeVO.java
package com.dandisoft.hera.domain.data.pt;
import com.dandisoft.hera.core.audit.vo.AuditVO;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class PtCodeVO extends AuditVO {
private String ptCodeId;
private String code;
private String name;
private String useYn;
}
AuditVO vs AuditModel
AuditVO: 화면/Feign 전달용. 직렬화 가능. DB 어노테이션 없음.AuditModel: DB Entity.@Table,@Column등 MyBatis-Flex 어노테이션 사용.
5. [3] PtCodeSP.java¶
SearchParams는 조회 조건을 담는 객체입니다. BaseSP를 상속하면 페이징·정렬 파라미터가 자동으로 따라옵니다.
// hera-domain-data/.../pt/PtCodeSP.java
package com.dandisoft.hera.domain.data.pt;
import com.dandisoft.hera.core.model.BaseSP;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class PtCodeSP extends BaseSP {
private String code;
private String name;
private String useYn;
}
6. [4] PtCode.java (Entity)¶
hera-system 모듈에 DB Entity를 작성합니다. MyBatis-Flex의 @Table, @Column 어노테이션으로 테이블·컬럼을 매핑합니다.
// hera-system/.../pt/PtCode.java
package com.dandisoft.hera.system.pt;
import com.dandisoft.hera.core.audit.model.AuditModel;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.Table;
import com.dandisoft.hera.core.audit.listener.AuditingEntityListener;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
@Table(
value = "pt_code",
onInsert = AuditingEntityListener.class,
onUpdate = AuditingEntityListener.class
)
public class PtCode extends AuditModel<String> {
@Id
@Column(value = "pt_code_id", onInsertValue = "uuidv7()")
private String ptCodeId;
@Column("code")
private String code;
@Column("name")
private String name;
@Column("use_yn")
private String useYn;
}
PK — uuidv7()
onInsertValue = "uuidv7()" 를 지정하면 INSERT 시 DB 함수 uuidv7()이 자동 호출됩니다. Java 코드에서 PK를 생성해 주입하지 않아도 됩니다.
AuditModel 타입 파라미터
AuditModel<String>의 타입 파라미터는 PK 타입입니다. pt_code_id 가 VARCHAR(UUID)이므로 String을 사용합니다.
7. [5] PtCodeMapper.java¶
Mapper는 MyBatis-Flex의 MinuBaseMapper를 상속하기만 하면 됩니다. 기본 CRUD(findAll, findById, insert, update, deleteById 등)가 자동 제공됩니다.
// hera-system/.../pt/PtCodeMapper.java
package com.dandisoft.hera.system.pt;
import com.dandisoft.hera.core.mapper.MinuBaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PtCodeMapper extends MinuBaseMapper<PtCode> {
}
커스텀 SQL이 필요한 경우
복잡한 조인이나 집계가 필요하면 이 인터페이스에 메서드를 추가하고, PtCodeMapper.xml에 SQL을 작성합니다. 단순 CRUD만 사용하는 지금은 추가 코드 없이 끝입니다.
8. [6] PtCodeService.java¶
Service 인터페이스는 AuditService를 상속해 기본 CRUD 메서드 시그니처를 확보합니다.
// hera-system/.../pt/PtCodeService.java
package com.dandisoft.hera.system.pt;
import com.dandisoft.hera.core.service.AuditService;
import com.dandisoft.hera.domain.data.pt.PtCodeSP;
import com.dandisoft.hera.domain.data.pt.PtCodeVO;
public interface PtCodeService
extends AuditService<PtCode, PtCodeVO, PtCodeSP> {
}
9. [7] PtCodeServiceImpl.java¶
AuditServiceImpl을 상속하고 searchCondition() 하나만 구현하면 됩니다. 나머지 CRUD 로직은 부모 클래스가 처리합니다.
// hera-system/.../pt/PtCodeServiceImpl.java
package com.dandisoft.hera.system.pt;
import com.dandisoft.hera.core.service.AuditServiceImpl;
import com.dandisoft.hera.domain.data.pt.PtCodeSP;
import com.dandisoft.hera.domain.data.pt.PtCodeVO;
import com.mybatisflex.core.query.If;
import com.mybatisflex.core.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class PtCodeServiceImpl
extends AuditServiceImpl<PtCode, PtCodeVO, PtCodeSP, PtCodeMapper>
implements PtCodeService {
@Override
protected QueryWrapper searchCondition(PtCodeSP sp) {
return QueryWrapper.create()
.like(PtCode::getCode, sp.getCode(), If::hasText)
.like(PtCode::getName, sp.getName(), If::hasText)
.eq(PtCode::getUseYn, sp.getUseYn(), If::hasText);
}
}
If::hasText 조건
If::hasText는 SP 필드가 null 또는 빈 문자열이면 해당 조건을 쿼리에서 뺍니다. 조건 필드가 비어 있으면 자동으로 전체 조회가 되므로, null 체크 코드를 따로 쓰지 않아도 됩니다.
10. [8] PtCodeApiController.java (hera-system)¶
System 레이어의 API Controller입니다. AuditApiController를 상속하고 @RequestMapping과 Security 어노테이션만 추가하면 CRUD API가 완성됩니다.
// hera-system/.../pt/PtCodeApiController.java
package com.dandisoft.hera.system.pt;
import com.dandisoft.hera.core.controller.AuditApiController;
import com.dandisoft.hera.core.model.PageRequest;
import com.dandisoft.hera.core.model.PageResponse;
import com.dandisoft.hera.domain.data.pt.PtCodeSP;
import com.dandisoft.hera.domain.data.pt.PtCodeVO;
import com.dandisoft.hera.hera_api_client.config.UrlConstant;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(UrlConstant.SYS_API_V1)
@RequiredArgsConstructor
public class PtCodeApiController
extends AuditApiController<PtCode, PtCodeVO, PtCodeSP, PtCodeService> {
@PreAuthorize("hasRole('ROLE_PT0101_READ')")
@GetMapping("/pt-codes/pageable")
public PageResponse<PtCodeVO> findAllOnPageable(
@RequestBody PageRequest<PtCodeSP> pageRequest) {
return super.doFindAllOnPageable(pageRequest);
}
@PreAuthorize("hasRole('ROLE_PT0101_CREATE')")
@PostMapping("/pt-codes")
public PtCodeVO save(@RequestBody PtCodeVO vo) {
return super.doSave(vo);
}
@PreAuthorize("hasRole('ROLE_PT0101_UPDATE')")
@PutMapping("/pt-codes/{id}")
public PtCodeVO update(@PathVariable String id, @RequestBody PtCodeVO vo) {
return super.doUpdate(id, vo);
}
@PreAuthorize("hasRole('ROLE_PT0101_DELETE')")
@DeleteMapping("/pt-codes/{id}")
public void delete(@PathVariable String id) {
super.doDelete(id);
}
}
권한 코드 패턴 — ROLE_{도메인코드}_{액션}
hera-v4의 권한 코드는 ROLE_PT0101_READ 형태입니다. 도메인 코드(PT0101)와 액션(READ/CREATE/UPDATE/DELETE)을 조합합니다. 이 권한은 Ch 5에서 시스템 권한 관리 화면을 통해 DB에 등록합니다.
GET + @RequestBody
hera-v4는 페이징 조회에 GET 메서드와 @RequestBody를 함께 사용합니다. 페이징·정렬·검색조건이 섞인 복잡한 조회 조건을 쿼리스트링이 아니라 요청 본문에 실어서 URL 길이 제한 문제를 피하려는 의도적 패턴입니다.
11. [9] PtCodeClient.java (hera-api-client)¶
hera-api-client 모듈에 Feign Client를 작성합니다. hera-webapp이 System 서비스를 호출할 때 이 Client를 거칩니다.
// hera-api-client/.../pt/PtCodeClient.java
package com.dandisoft.hera.hera_api_client.pt;
import com.dandisoft.hera.core.model.PageRequest;
import com.dandisoft.hera.core.model.PageResponse;
import com.dandisoft.hera.domain.data.pt.PtCodeSP;
import com.dandisoft.hera.domain.data.pt.PtCodeVO;
import com.dandisoft.hera.hera_api_client.config.UrlConstant;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
@FeignClient(
name = "msa-api-gateway",
path = UrlConstant.SYS_API_V1,
url = "${api-gateway.sys:}",
contextId = "ptCodeClient" // ← 필수: 동일 서비스를 향하는 Feign Client가 복수일 때 충돌 방지
)
public interface PtCodeClient {
@GetMapping("/pt-codes/pageable")
PageResponse<PtCodeVO> findAllOnPageable(
@RequestBody PageRequest<PtCodeSP> pageRequest);
@PostMapping("/pt-codes")
PtCodeVO save(@RequestBody PtCodeVO vo);
@PutMapping("/pt-codes/{id}")
PtCodeVO update(@PathVariable("id") String id, @RequestBody PtCodeVO vo);
@DeleteMapping("/pt-codes/{id}")
void delete(@PathVariable("id") String id);
}
contextId는 생략 불가
hera-api-client에는 msa-api-gateway를 향하는 Feign Client가 여러 개 있습니다. contextId가 없으면 Spring 컨텍스트 로드 시 ConflictingBeanDefinitionException이 발생합니다. Client 이름에 맞춰 ptCodeClient처럼 고유한 이름을 지정합니다.
12. [10] Pt0101ViewRouter.java¶
hera-webapp 모듈에 View Router를 작성합니다. 브라우저의 /pt/0101 요청을 받아 Thymeleaf 템플릿을 반환합니다.
// hera-webapp/.../pt/Pt0101ViewRouter.java
package com.dandisoft.hera.webapp.pt;
import com.dandisoft.hera.webapp.core.controller.BaseController;
import static com.dandisoft.hera.domain.code.Domain.PT_0101;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/pt")
@RequiredArgsConstructor
public class Pt0101ViewRouter extends BaseController {
@GetMapping("/0101")
public String view(ModelMap model) throws Exception {
prepareBaseInfo(model, PT_0101);
return "pt/pt0101";
}
}
prepareBaseInfo(model, domain)
이 한 줄이 화면 코드 → DB 메뉴 정보 조회 → 권한 정보 → Thymeleaf 모델 주입까지 전 과정을 처리합니다. 반환 문자열 "pt/pt0101"은 templates/pt/pt0101.html에 대응합니다.
13. [11] pt0101.html¶
Thymeleaf 템플릿입니다. hera-v4의 단일 그리드 화면은 대부분 동일한 구조를 따릅니다.
<!-- hera-webapp/src/main/resources/templates/pt/pt0101.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base}">
<head>
<title th:text="${pageTitle}">PT0101</title>
</head>
<body>
<section layout:fragment="content">
<!-- 검색 영역 -->
<div class="card card-search">
<div class="card-body">
<div class="row g-2">
<div class="col-md-3">
<label class="form-label">코드</label>
<input type="text" id="searchCode" class="form-control form-control-sm">
</div>
<div class="col-md-3">
<label class="form-label">코드명</label>
<input type="text" id="searchName" class="form-control form-control-sm">
</div>
<div class="col-md-3">
<label class="form-label">사용여부</label>
<select id="searchUseYn" class="form-select form-select-sm">
<option value="">전체</option>
<option value="Y">사용</option>
<option value="N">미사용</option>
</select>
</div>
<div class="col-md-3 d-flex align-items-end">
<button id="btnSearch" class="btn btn-primary btn-sm">
<i class="bi bi-search"></i> 조회
</button>
</div>
</div>
</div>
</div>
<!-- 그리드 영역 -->
<div class="card mt-2">
<div class="card-header d-flex justify-content-between align-items-center">
<span>코드 목록</span>
<div>
<button id="btnAdd" class="btn btn-sm btn-success">추가</button>
<button id="btnSave" class="btn btn-sm btn-primary">저장</button>
<button id="btnDelete" class="btn btn-sm btn-danger">삭제</button>
</div>
</div>
<div class="card-body p-0">
<div id="grid"></div>
</div>
</div>
</section>
<th:block layout:fragment="script">
<script th:src="@{/js/pt/pt0101.js}"></script>
</th:block>
</body>
</html>
14. [12] pt0101.js¶
화면 동작을 담당하는 JS 파일입니다. API 호출 URL, 그리드 컬럼 정의, 버튼 이벤트를 작성합니다.
// hera-webapp/src/main/resources/static/js/pt/pt0101.js
const apiUrl = contextPath + '/api/sys'; // Webapp API Controller 경로
/* ── 조회 ─────────────────────────────── */
const findAll = (params) =>
$.get(apiUrl + '/pt-codes/pageable', params, function (res) {
grid.resetData(res.data);
});
/* ── 그리드 초기화 ───────────────────── */
const grid = new tui.Grid({
el : document.getElementById('grid'),
rowHeaders: ['checkbox'],
columns : [
{ header: '코드ID', name: 'ptCodeId', hidden: true },
{ header: '코드', name: 'code', editor: 'text' },
{ header: '코드명', name: 'name', editor: 'text' },
{ header: '사용여부', name: 'useYn',
editor: { type: 'select', options: { listItems: [
{ text: '사용', value: 'Y' },
{ text: '미사용', value: 'N' }
]}}
},
],
pageOptions: { useClient: false, perPage: 20 },
});
/* ── 버튼 이벤트 ─────────────────────── */
$('#btnSearch').on('click', function () {
const params = {
'searchParam.code' : $('#searchCode').val(),
'searchParam.name' : $('#searchName').val(),
'searchParam.useYn' : $('#searchUseYn').val(),
page : 1,
size : 20,
};
findAll(params);
});
$('#btnAdd').on('click', function () {
grid.addRow({ useYn: 'Y' }, { at: 0 });
});
$('#btnSave').on('click', function () {
const rows = grid.getModifiedRows();
rows.createdRows.forEach(row => {
$.post(apiUrl + '/pt-codes', JSON.stringify(row),
() => findAll({}), 'json');
});
rows.updatedRows.forEach(row => {
$.ajax({
url : apiUrl + '/pt-codes/' + row.ptCodeId,
method : 'PUT',
data : JSON.stringify(row),
contentType: 'application/json',
success: () => findAll({}),
});
});
});
$('#btnDelete').on('click', function () {
grid.getCheckedRows().forEach(row => {
$.ajax({
url : apiUrl + '/pt-codes/' + row.ptCodeId,
method : 'DELETE',
success: () => findAll({}),
});
});
});
/* ── 페이지 로드 시 초기 조회 ─────────── */
$(document).ready(function () {
findAll({});
});
15. [13] Pt0101ApiController.java (hera-webapp)¶
hera-webapp 안의 API Controller입니다. 브라우저 JS → 이 Controller → PtCodeClient (Feign) → API Gateway → hera-system 순으로 연결됩니다.
// hera-webapp/.../pt/Pt0101ApiController.java
package com.dandisoft.hera.webapp.pt;
import com.dandisoft.hera.core.model.PageRequest;
import com.dandisoft.hera.core.model.PageResponse;
import com.dandisoft.hera.domain.data.pt.PtCodeSP;
import com.dandisoft.hera.domain.data.pt.PtCodeVO;
import com.dandisoft.hera.hera_api_client.pt.PtCodeClient;
import com.dandisoft.hera.hera_api_client.config.UrlConstant;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(UrlConstant.SYS_API)
@RequiredArgsConstructor
public class Pt0101ApiController {
private final PtCodeClient ptCodeClient;
@GetMapping("/pt-codes/pageable")
public PageResponse<PtCodeVO> findAllOnPageable(
PageRequest<PtCodeSP> pageRequest) {
return ptCodeClient.findAllOnPageable(pageRequest);
}
@PostMapping("/pt-codes")
public PtCodeVO save(@RequestBody PtCodeVO vo) {
return ptCodeClient.save(vo);
}
@PutMapping("/pt-codes/{id}")
public PtCodeVO update(@PathVariable String id, @RequestBody PtCodeVO vo) {
return ptCodeClient.update(id, vo);
}
@DeleteMapping("/pt-codes/{id}")
public void delete(@PathVariable String id) {
ptCodeClient.delete(id);
}
}
Webapp Controller vs System Controller — API 경로 차이
| 레이어 | @RequestMapping | 실제 경로 | 호출 주체 |
|---|---|---|---|
| hera-webapp | SYS_API = /api/sys | /api/sys/pt-codes/... | 브라우저 JS |
| hera-system | SYS_API_V1 = /api/v1/sys | /api/v1/sys/pt-codes/... | Feign (API Gateway 경유) |
브라우저는 /api/sys로 호출하고, Feign은 /api/v1/sys로 재전달합니다. API Gateway는 /api/v1/sys/**를 hera-sys 서비스로 라우팅합니다.
16. 전체 요청 흐름 확인¶
작성한 코드를 따라 전체 흐름을 한 번 더 짚어 봅니다.
sequenceDiagram
participant B as 브라우저 (pt0101.js)
participant W as Pt0101ApiController
(hera-webapp)
participant F as PtCodeClient
(Feign)
participant G as API Gateway
participant S as PtCodeApiController
(hera-system)
participant SV as PtCodeServiceImpl
participant M as PtCodeMapper
B->>W: GET /api/sys/pt-codes/pageable
W->>F: findAllOnPageable(pageRequest)
F->>G: GET /api/v1/sys/pt-codes/pageable
G->>S: 라우팅 (lb://hera-sys)
S->>SV: doFindAllOnPageable(pageRequest)
SV->>M: selectPageByQuery(searchCondition)
M-->>SV: Page
SV-->>S: PageResponse
S-->>G: JSON
G-->>F: JSON
F-->>W: PageResponse
W-->>B: JSON
17. DB 테이블 생성¶
로컬 PostgreSQL(사내 개발 서버)에서 테이블을 생성합니다.
CREATE TABLE pt_code (
pt_code_id VARCHAR(36) NOT NULL DEFAULT uuidv7(),
code VARCHAR(50) NOT NULL,
name VARCHAR(200) NOT NULL,
use_yn CHAR(1) NOT NULL DEFAULT 'Y',
-- AuditModel 공통 컬럼 (AuditingEntityListener가 자동 채움)
created_at TIMESTAMP,
created_by VARCHAR(36),
updated_at TIMESTAMP,
updated_by VARCHAR(36),
CONSTRAINT pk_pt_code PRIMARY KEY (pt_code_id)
);
COMMENT ON TABLE pt_code IS '가상 코드';
COMMENT ON COLUMN pt_code.pt_code_id IS 'PK';
COMMENT ON COLUMN pt_code.code IS '코드';
COMMENT ON COLUMN pt_code.name IS '코드명';
COMMENT ON COLUMN pt_code.use_yn IS '사용여부 (Y/N)';
18. 빌드 및 동작 확인¶
18.1 빌드¶
빌드가 끝나면 hera-webapp을 실행합니다.
18.2 브라우저 확인¶
https://localhost:8000/pt/0101 에 접속합니다.
| 확인 항목 | 기대 결과 |
|---|---|
| 화면 표시 | 검색 영역 + 그리드 렌더링 |
| 초기 조회 | 그리드에 데이터 표시 (비어 있어도 정상) |
| 추가/저장 | 행 추가 후 저장 → 그리드 갱신 |
| 삭제 | 체크 후 삭제 → 행 제거 |
18.3 Network 탭으로 흐름 확인¶
브라우저 개발자 도구 → Network 탭에서 다음 요청을 확인합니다.
GET /pt/0101→ 200, HTML 응답 (ViewRouter 정상)GET /api/sys/pt-codes/pageable→ 200, JSON 응답 (Feign → System 정상)
404 응답 시 체크리스트
| 증상 | 원인 | 확인 위치 |
|---|---|---|
/pt/0101 404 | ViewRouter 미등록 | Pt0101ViewRouter.java @GetMapping |
/api/sys/pt-codes/pageable 404 | Webapp Controller 미등록 | Pt0101ApiController.java @GetMapping |
/api/v1/sys/pt-codes/pageable 404 | System Controller 미등록 | PtCodeApiController.java @GetMapping |
| 500 응답 | Feign Bean 충돌 | PtCodeClient.java contextId 누락 확인 |
| 403 응답 | 권한 미등록 | Ch 5에서 DB에 권한 등록 필요 |
19. 이 챕터 요약¶
| 파일 | 모듈 | 역할 |
|---|---|---|
Domain.java | hera-commons | 화면 코드 상수 등록 |
PtCodeVO.java | hera-domain-data | 화면 ↔ API 데이터 전달 객체 |
PtCodeSP.java | hera-domain-data | 조회 조건 객체 |
PtCode.java | hera-system | DB Entity |
PtCodeMapper.java | hera-system | DB 접근 (MinuBaseMapper 상속) |
PtCodeService.java | hera-system | Service 인터페이스 |
PtCodeServiceImpl.java | hera-system | 조회 조건 구현 (searchCondition) |
PtCodeApiController.java | hera-system | System REST API |
PtCodeClient.java | hera-api-client | Feign Client |
Pt0101ViewRouter.java | hera-webapp | URL → Thymeleaf 매핑 |
pt0101.html | hera-webapp | 화면 템플릿 |
pt0101.js | hera-webapp | 화면 동작 (그리드/CRUD) |
Pt0101ApiController.java | hera-webapp | 브라우저 ↔ Feign 브릿지 |
다음 챕터 예고
Ch 5에서는 만든 화면을 메뉴에 노출하고 권한을 적용하기 위해 DB에 무엇을 등록해야 하는지 다룹니다. 시스템 관리 화면(sys0101 ~ sys0105)을 통해 메뉴·권한·메시지를 등록하는 과정을 따라갑니다.